-
Couldn't load subscription status.
- Fork 1.4k
Expose Error Type In Console Operators #5034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
🙏 |
|
I've been playing with this and it isn't working as I hoped because
So it is true that |
|
We could call |
|
I'm questioning if it is really necessary to add an Error-Type here.. The only place I could imagine this requirement would be useful is if someone would create a pure CLI/jLine/ncurses like application or something where the console is a actual console, e.g. interfacing over a serial line with some legacy system, 80x25ish... but if one would do that, then console.putStrLn also would need to be wrapped in a blocking {} as those connections are slow i/o. |
|
I've had issues where the out stream is broken in some way (due to something mutating System.out or some system console issue). It doesn't happen often but when it does, having a way to handle that failure is sure nice. Yeah, this does add some pain though so I'm not sure if it is worth it. I like representing reality as accurately as possible, but sometimes reality is really messy. |
|
Maybe what we should do is reflect |
|
I wonder if we should do the same thing with |
|
The underlying APIs aren't homogeneous. |
|
Understood, but it seems like from the user perspective there are two classes of users: (1) most people just want to do console operations with the assumption that they can't fail, (2) a smaller number of people want to really expose and possibly handle the different ways that console operations can fail. So it seems like maybe we need two classes of operators. The first would be "simple" ones and I think would not expose a typed error at all. The second class would expose an error type and would potentially do additional work to surface the error like in the case of printing to the console. |
|
Yeah, that sounds good. |
|
Yes. We could certainly do that and it was the behavior before. I am wondering if |
|
How about instead of changing |
|
Then the remaining issue is how to expose the fallible variation of |
|
I think it is fine to keep it as is. I'm not sure we even need a separate symbolic operator. People have been fine with calling |
|
Depends on what "actually fail" means. If an API we are wrapping hides failure, do we expose the actual possible failures or continue hiding them? For me, 99% of the time if what I'm building needs to write to stdout and can't, I want to know and possibly the only way to know is to fail. But it would be interesting to see how this impacts logging. Cause for most server stuff that'd be my interface to stdout. |
|
Yes, what I am saying is I think we want to provide the interface that we think is the "right" interface versus just the interface that lower levels give to us. So I think that means exposing the possibility of failure in both reading and writing and if necessary changing the implementation to support that. Then there is this issue that some people aren't interested in having this failure possibility being exposed, and we would like to do something to make that convenient. So I think we have either: trait Console {
def printLine(line: String): UIO[Unit]
def printLineOrFail(line: String): IO[IOException, Unit]
def readLine: IO[IOException, Unit]
}Or: trait Console {
def printLine(line: String): UIO[Unit]
def printLineOrFail(line: String): IO[IOException, Unit]
def readLine: UIO[Unit]
def readLineOrFail: IO[IOException, Unit]
}The second seems slightly more consistent to me (there are two things that can fail, the more general signatures of each reflect that, convenience variants of each are provided that ignore that possibility) but they have the same power since you can always call |
|
Yeah, the uniformity of the second is nice. What about the defaults being the failable ones though? def println(line: String): IO[IOException, Unit]
def printlnUnsafe(line: String): UIO[Unit] |
|
I propose to keep the methods for the 1.0.x branch like they are (so revert this change) and we can change them in 1.1.x or 2.0.x with a "migration guide". at the moment people get unexpected compile errors when they just "changed a minor version" |
|
@domdorn The change was already reverted. |
|
then we should release a 1.0.9 before too many people upgrade to 1.0.8, change their code, later upgrade to 1.0.9 and have to change it again. if we do that soon, people will just skip 1.0.8 altogether. |
|
I think these are good candidate changes for 2.x, and maybe the default should be the fallible ones because it is pretty easy to call |
|
PR for However, seems like it could be good to get some alignment on what we want to do here. I have been working on the assumption that we wanted two versions of these operators, but if we want to just have the fallible versions and let people call I do think the people who want the fallible version are in the substantial minority though and it does create some friction. |
This is a good point. We're at the following place:
If we make any major change, it should be pushed to 2.x. In fact, I think the change with the exception was source incompatible, so maybe we should have pushed that to 2.x (but too late now). |
thats why I proposed to make a 1.0.9 release ASAP, then probably a big percentage of users will just skip 1.0.8 and everything will work fine for them like with 1.0.7 (without any need to change any code) |
|
Yeah I think the question is just whether the ship has already sailed on that one. If we think that the right end state is for the default versions of these operators to use the |
|
I think with @adamgfraser's recent change in #5128, it means that users who do not want exceptions in e.g.: for {
_ <- putStrLn("What is your name?")
name <- getStrLn
_ <- putStrLn("Hello, " + name)
} yield ()versus: for {
_ <- putStrLn("What is your name?")!
name <- getStrLn!
_ <- putStrLn("Hello, " + name)!
} yield ()This seems like a low enough overhead that maybe we don't need two separate versions? The "multiple versions" path just seems like it is fraught with ever-expanding complexity (do we do that for all the time methods? all the system methods? do we encourage all users to do that for all their services?). Not 100% sure, however... |
|
That is what I'd lean towards. :) First, make it honest. Then make it easy. |
|
I think it is definitely good to keep these interfaces thin if possible so would be in favor of that. |
|
@adamgfraser Another consideration is that a lot of people who don't like typed errors just use |
|
So if I can summarize:
|
|
@jdegoes Yes I think that is right. It might be nice to have a variant of |
|
@adamgfraser I love that idea! |
|
Yeah! Maybe the primary use case for |
|
hmm... if we have a |
|
No it doesn't have any environmental dependencies. |
|
I've re-read this @jdegoes's thread https://twitter.com/jdegoes/status/1370822616844529671 and I think a checked Java exception shouldn't be put into the |
Resolves #5032. This is a little annoying in toy examples but it is hard to argue that there is a possibility of failure and in a more complex application you would like to be aware of that and be able to handle it. Also we have kind of already crossed this bridge with the signature of
getStrLn.