6 ms·
`Either<MyError, Foo> foo()` and `Foo foo() throws MyError` and are pretty much isomorphic. https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.
by ackfoobar 2y ago
`Either<MyError, Foo> foo()` and `Foo foo() throws MyError` and are pretty much isomorphic.
https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md#appendix-why-flatmap-is-missing https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/...
- bedobi 2y agothey're in no way even remotely similar Unchecked exception Foo foo = myClass.getFoo() // throws exception, but doesn't tell you, and nor does the compiler Checked exception // now we have to do this everywhere, in every layer, lol (or put throws) try { myClass.getFoo() } catch(Exception e){ //now we're catching ALL exceptions, yay // do what? who knows } Sane Either/Option Either<Error, Foo> foo = myClass.getFoo() ^^^ the compiler disallows treating this as a Foo - but also doesn't force us to handle anything, we can pass this around without handling anything, and only when we want to, call map, flatMap, fold etc Notably, it's just an object, it doesn't require weird special syntax, halting execution, hierarchies of exception (which mean you catch too much or too little etc etc etc) it's objectively better on every metric except familiarity but since Java devs are stuck in the 90s and refuse to listen to even their own language designers about how traditional Java ways of doing things are fundamentally broken (don't take my word for it, go listen to what Josh Bloch and Brian Goetz say), they refuse to even consider adopting it until they're forced to (like when Java introduced Option) (which itself is the worst implementation of that type in any language, but that's a side note)
- ackfoobar 2y ago> it doesn't require weird special syntax But to use that `Either` object ergonomically languages do add extra syntax. Rust with the `?` and Scala with the for-comprehension. The interactions with HOFs are also subtle. In Rust it requires a smart use of type params for `collect`. In Scala (following the Haskell tradition) there's the pair of coloured functions `traverse` and `map`. But `traverse` is only available in third-party functional programming libraries (Cats or Scalaz). So out of the box there isn't a way to cleanly `map` a function that returns the `Either` type. > now we're catching ALL exceptions, yay Your example is not that different from upcasting. val foo: Either<Exception, Foo> = // Either is covariant myClass.getFoo() // returns Either<MyError, Foo>
- vips7L 2y agoOdersky actually hates Either and refuses to use it in Scala for errors.
- mrkeen 2y agoI find this extremely weird. Why even use Scala then? Is it just for the implicits and long build times?
- bedobi 2y agothe difference is that in catch (Error e), you're actually catching the Error type, the superclass of exception in Either<Error, Foo>, Error is not that, it's usually a sealed class with the full range of things that can go wrong with that thing, no more, no less, eg sealed class ChangePasswordErrors { class NotAuthenticated() : ChangePasswordErrors() class ChangePasswordError() : ChangePasswordErrors() class ReusedPassword() : ChangePasswordErrors() } or whatever (but I could have made that clearer) and no, languages should not add weird syntax to deal with Either, Option, Try etc. Just call map, flatMap, fold.
- ackfoobar 2y ago> the Error type, the superclass of exception > in Either<Error, Foo>, Error is not that This is confusing. On the JVM `Error` and `Exception` are both subclasses of `Throwable`. If you meant a specific error type, give it another name. (I used `MyError` in the comments above.) > sealed class ChangePasswordErrors { If some kind of failure has to be handled, In Kotlin you can write: sealed class ChangePasswordResponse { class Success(): ChangePasswordResponse() class NotAuthenticated() : ChangePasswordResponse() ... > Just call map, flatMap, fold. Enjoy your callback hell.
- bedobi 2y agoGood poit re using MyError, that is indeed what I meant in the Either case Not sure what you mean by callback hell, I've never experienced that using Either. I just pass the Either from the db layer or whatever to the resource layer or whatever, where I fold it into a 200 with the requested thing or a 4xx or 5xx or whatever depending on the MyError (which is usually a sealed class that lists everything that can go wrong with the thing)
- lolinder 2y ago> it's objectively better on every metric except familiarity I find that as a rule, people who throw around the word "objectively" a lot tend to dramatically overestimate how much they know, usually because they're advanced beginners in the domain that they're so confident in and haven't yet run into the complexity of real-world implementation. There are very few features in programming languages that can be described as "objectively" better or worse than other features. The whole discipline is more art than science, and the only practitioners who actually accomplish anything meaningful in their work are the ones who acknowledge the inherent subjectivity of most of programming language design. Programming languages do not exist in a vacuum, they exist in an ecosystem of interrelated individuals and technology. Do not be so quick to dismiss the weight of generations of programmers and billions of lines of code as people just being "stuck in the 90s".
- mrkeen 2y ago> the weight of generations of programmers and billions of lines of code as people just being "stuck in the 90s". The weight of generations of programmers and billions of lines of code have come to the conclusion that: 1) Functions should declare/document what they return (including failure modes) 2) Checked exceptions are not worth it These are mutually exclusive. Result types genuinely are better.
- lolinder 2y agoI'm a fan of Result types in languages that incorporated them as part of the design (say, Rust), but I'm not convinced that they're the right answer for Java. Result types only really make sense in a highly expression-oriented language, and while Java has taken important steps in that direction it's really not there yet and likely never will be. I think a more fruitful path of exploration is improvements to the ergonomics of checked exceptions. Semantically there really is very little difference between checked exceptions and Result types (OP's argument to the contrary is a straw man and proves nothing), the main reason why people fought their use in the early days of Java was that the ergonomics were pretty poor. I suspect that there are a few tweaks that could be made with modern PL techniques that would make checked exceptions feel more comfortable (analogous to the addition of ? in Rust), and since they've been baked into the language for decades now through thousands of existing libraries the returns from making them easier to use will be much higher than the returns from creating a new "standard" way to handle errors in Java.
- kaba0 2y ago> or put throws everywhere Or put Result/Error etc. type everywhere. It’s literally analogous.
- bedobi 2y agoit's literally not, I'm sorry but folks who think this (and it's extremely common) simply haven't familiarized themselves with the differences
- kaba0 2y agoRepeating the same stuff won’t make it true.
- ackfoobar 2y agoGoing off-topic, I've been on both sides of seeing the analogousness or not. I think there might be two modes of thinking for deciding whether two things are analogous. Mathy people inspect the "shape" of, and interactions within the two things. Other people go for intuitive vibes. For more concrete stuff the two modes of thinking usually match up. Summation and multiplication are literally different things, but a lesson in group theory tells you (ℝ, +) and (ℝ⁺, ×) are isomorphic.
- kaba0 2y agoSomething being analogous to something else means, that there is a bidirectional function/mapping between the two. One can easily map any Result-typed expression to a checked exception one, and vice versa.
- deleted 2y ago[deleted]
- wiseowise 2y ago> Objectively better > is forced to pattern match on every interaction with object now Not sure if trolling.
- mrkeen 2y ago> Foo foo() throws MyError Why did you link to Kotlin? It doesn't appear to have `throws MyError`.
- ackfoobar 2y agoYou're right that was confusing. For this line, where the same can be said for Java. > Notice, that monad comprehension over Try monad is basically built into the Kotlin language.
- bedobi 2y ago...which is even worse, because now the compiler is saying "when you call this, you will get this" and you have NO way of knowing that the call actually returns any number of exceptions, as a matter of course, without reading EVERY line called by EVERY line in that method vs just plainly indicating you will get Either<Error, Foo>
- wiseowise 2y agoWhat are you talking about? You literally can with sealed classes.
- bedobi 2y agoI'm talking about the fact that every function call in Kotlin can return an exception (and it will always be undeclared, because Kotlin doesn't have checked exceptions) this has nothing to do with sealed classes
- wbl 2y agoThrows are nonlocal control transfer, not types. The interaction with the rest of the language is entirely different.
- ackfoobar 2y agoRather than looking at what the (abstract) machine does - throws are nonlocal control transfer - result types are adding a tag to the data or error Think about whether the two can express the same things, and whether one can be translated to another without losing information.
- kaba0 2y agoHow are throws non-local? They simply pop a stackframe, the exact same way as the `return` keyword. Then the calling method gets to decide what to do, exceptions just have an implicit “unwrap-or-return/bubble up”. Exceptions are nothing like gotos/long-jumps, it’s just “pressing escape key once vs keeping it pressed”, basically.
- vips7L 2y agoI watched a video from Martin Odersky (creator of Scala) and he was sort of the same opinion when talking about effects. A b() throws C; And A b()(using Throws[C]); are exactly the same.
- kelnos 2y agoIgnoring the much more annoying syntax needed to handle exceptions, I'd agree. But that's not the problem: you never know if foo() throws things other thank MyError. So in practice you still need an exception catch-all after your catch for MyError. After years of Java, handling errors in idiomatic Scala with Either was a breath of fresh air. (And you can even wrap things that might throw in Try() to save yourself the headache of a try/catch tree.) Rust did it right with Result, and leaving out exceptions entirely.