4 ms·
I am probably in the minority here but I think Java was on the right track with checked exceptions.
by quelltext 7y ago
I am probably in the minority here but I think Java was on the right track with checked exceptions.
- bedobi 7y agoI used to think so, but * Java also has unchecked exceptions, and, incredibly, there's still no consensus as to which exceptions should be used for what and when, though these days the most common approach is to simply stick only to RuntimeExceptions, which is terrible. * Functional error handling using Option, Either, Try etc are arguably much simpler, safer and more powerful than exceptions. Simpler because they don't rely on dedicated syntax- they're just regular objects no different to any other object. Safer because unlike exceptions, they force callers to handle all potential outcomes, but no more. (no risk of ignoring errors and no risk of catching a higher level of error than desired, ubiquitous bugs in exception based error handling) Powerful because they support map, flatmap, applicative etc, making it easy to eg chain multiple computations together in desired ways, which is unwieldy and bug prone when using exceptions. It could be that, when learning Java, Python and any other language, we learn that methods return objects... and that's that. No weird dedicated syntax and magic, special treatment for returning anything other than the happy path, and the HUGE complexity that comes with it, eg the dedicated syntax itself and how it behaves, differences between checked and unchecked exceptions, hierarchies of exceptions etc etc. When lists and booleans also implement map, flatMap etc, you can actually reduce syntax of languages even further- there's no need for looping syntax like for, while etc, and no need for if else either. This is probably too extreme for most people, but think about it!
- dkersten 7y agoSlightly off topic, but what should a map or flatMap implementation on boolean do? Presumably run on true and skip on false? That’s not really what I expect from map (that it’s filtered the input). I could imagine book.filter.map as a way to conditionally execute
- bedobi 7y agoHaha you're putting me on the spot here, I guess I haven't really thought too deeply about the specifics! Maybe flatMap would return true only if all chained booleans are true? Bimap would allow you to provide functions for what to do in true and false case? Fold would allow you to turn true or false into a single type? I'm sure someone more experienced in FP can weigh in with something more correct! :p
- karmakaze 7y agoI rather like that, `if condition` is a 0/1 iteration with no input. flatMap can return empty, what should map return when run on false, None?
- mrgriffin 7y agoI'd probably think of Bool as being isomorphic to Maybe (), which would mean your map implementation looks a bit like: map :: Bool -> (() -> b) -> Maybe b map True f = Just (f ()) map False _ = Nothing Or, in Haskell because you've got laziness you could go with: map :: Bool -> b -> Maybe b Which already exists as a combination of Control.Monad.guard and Data.Functor.(<$). map tf b = b <$ guard tf
- quelltext 7y agoChecked exceptions really aren't much different from dedicated return type for errors. The Either type in Haskell and its Monad is really not doing anything fundamentally different: Bubbling up errors until the point where someone cares/needs to handle them (though I have to say I hate the whole Left / Right convention thing, just define a dedicated type with proper constructors for value vs. error already). In fact I'd say checked exceptions are a richer formalism since they essentially provide something akin to intersection types for errors. The fact that there are also RuntimeExceptions comes up a lot as an argument against Java/checked exceptions but that doesn't convince me. Haskell also has runtime errors that can happen (division by 0) that are outside the Either or Option/Maybe world. The problem is maybe rather that people are lazy and the trend became to decide that everything should be a RuntimeException because who needs all that noise and checked exception handling, right? I'm fairly certain that people who rejected checked exceptions would also reject the more functional approaches. My argument is that no consensus around using checked exceptions correctly ever developed and so they never really had a chance to shine. Having said that, sure I think having more usage of idioms we find in FP for error handling/propagation or early aborting is great. It's better than just raising runtime exceptions. However we are still in a world where no widely known language actually enforces (!) error handling in that fashion. It's all convention in the end. And so my point was that if you want a language that tries to encourage error handling, with known failure modes communicates in the method signature then Java's checked exceptions sound pretty close. Sure they are not perfect but the question is whether it's worth exploring better formalisms along the lines of first class errors with dedicates language support or piggybacking on the existing type system features and introduce conventions? The latter sort if works but I find it less ...exciting.
- erik_seaberg 7y agostream.map(f).collect(…) is where checked exceptions failed. #map is a generic method that should obviously throw each of the checked exceptions f can throw, but without some kind of generic sum type Java can't express that. Everyone is forced to choose between "can throw any exception" (e.g., Callable<T>) and "can only throw unchecked exceptions" (e.g., Function<T, U>), each of which spreads virally and punishes anyone using specific throws clauses.
- lokedhs 7y agoThere is no need to get rid of checked exceptions to fix that. You need generics for exceptions. If the inner function can throw some exception, then it should be possible to declare the stream method that it will throw the same exception. I believe that the JVM doesn't actually support this, so the reasons were got the horrible situation we're in is because they didn't want to sacrifice backward compatibility.
- erik_seaberg 7y agoI think we're saying the same thing. If f(T) can return U or throw A or B or C, there needs to be a way to declare that Stream<T>#map takes a Function<T, U, A|B|C> and returns a Stream<U> or throws A or B or C. But that sum type A|B|C doesn't exist in Java if there's no common base class.
- pjmlp 7y agoSomething that Java typically gets the blame for, but was actually introduced in CLU, adopted by Modula-3 and C++ before Java was even born. And with my .NET hat, I hate having to dig into outdated documentation to discover what exceptions might come my way, so one ends up catching any kind of exception, just in case it might blow, in critical code paths.
- quelltext 7y agoAFAIK no C++ does not have checked exceptions like Java does. There's no static check enforcing that a caller needs to handle (or also declare throwing) an Exception declared to be thrown by the called method.
- trixie_ 7y agoComing from C# to Java, checked exceptions are the worst. There are so many layers that can fail even in a simple API call - network, database, permissions, memory, etc.. We're not going to handle each of those cases differently, if we were going to we could always catch the specific exception type explicitly. But usually it's fail the API call, catch the base exception, and report the relevant failure message. Fail fast, report, move on. Maybe there are other programming situations where you want to catch every possible type of exception and run some custom logic for each one? I'd be interested to hear about it.
- quelltext 7y agoMy point was that if one wants a language that is explicit about what error types a method can cause (see the top-level comment I replied to) then checked exceptions aren't far off and in fact on the same track. The problem you describe may be an issue with checked exceptions but it would equally be one in any language with the aforementioned feature. > We're not going to handle each of those cases differently, if we were going to we could always catch the specific exception type explicitly. But you don't have to handle them differently. You can though and your method signatures will tell you what type of errors you might have to deal with. Some make sense to handle on the spot others don't. You don't get that with runtime only exceptions. With those you really don't know and can get all kinds of errors and so you expect all and then just catch all and deal with it or blow up/abort whatever is going on (which let's be honest is what most web services do). Is that good? Yeah, sometimes. It certainly is easier to do that instead of thinking about possible failure modes when you know you'll only end up logging the error and give up anyway. I'm not trying to be cheeky, it really is overkill to make full use of checked exception in some cases. Granted I haven't seen many great examples of checked exception usages so far but I wouldn't dismiss them because they don't mesh with what you're used to or with what's common in a given domain.
- warpspin 7y agoAlways hated checked exceptions because there seems the basic assumption underlying them, that exceptions can usually be handled as near to the code throwing it as possible, while in every real world project I ever was involved in, 90% of the possible exceptions usually had to be handled "far away". So this means, you will have a whole bunch of re-throws, or exceptions wrapped in "higher design level" exceptions which you then throw to finally communicate the error condition to the proper component, polluting all signatures on the way with "throws". Or you catch it as early as possible, wrap it in a RuntimeException, re-throw it unchecked and let the high level component properly handle it, effectively bypassing the "checked exceptions" system :D Just my two cents, I know this might be a controversial position.
- dllthomas 7y agoI think the problem with checked exceptions was that we got a woefully limited language for talking about them. Checked exceptions with generics (and inference?) might be a very different experience. For instance, Java's checked exceptions break higher-order functions. A map function should be able to say "I can throw anything my argument can throw". Some wrapper functions might say "anything my argument can throw, minus FooException because I catch it, plus BarException because I might raise that myself."
- bedobi 7y agoWith functional error handling, you don't need neither checked nor unchecked exceptions, everything just returns objects, and the language and syntax used for talking about them is the same as the language and syntax used for everything else (map, filter, flatMap, fold etc etc).
- dllthomas 7y agoI was speaking to checked exceptions vs unchecked. "Exceptions at all" is a separate question. If we're going to dig into it, first we need to call out the occasional need to interrupt running pure code (eg. "the user hit ctrl-c"). As it can happen at any point, I think it's uncontroversial to say that return values are inappropriate? These are Haskell's "asynchronous exceptions", I think they do their job fairly well, and I don't know of a compelling exception-free alternative (although I'd be very interested to learn). So now we can restrict ourselves to "synchronous exceptions" - those raised because of the function we've called. I agree that we don't need them. They bring to the table early termination and transparent error propagation. There are contexts where these are desirable - if you disagree on that point let me know and we can dig in. In sufficiently expressive languages, we can do it ourselves - so that's plenty to show we don't need them. But we may want them. When we do it ourselves, we usually lack a way of telling the compiler which paths to privilege, and we often find some overhead compared with native exceptions. When exceptions are provided in the language, we get special syntax to distinguish them. You describe this as a drawback, but nonuniform syntax can help with legibility - if different things look different, I can more easily figure out what I'm looking at. Obviously there are tradeoffs here. Putting exceptions in the language also provides a distinction to the compiler in type checking, allowing different rules to apply. For instance, Java methods throw some set of exceptions, which get unioned (&c) in ways you can't speak about sets of types otherwise. Simply applying the ordinary value-level rules would be problematic (I hit this in Haskell sometimes). Ideally you can broaden your type rules to support what you need for all of it in one system, but it's not always clear they combine well. Even if it all can be made to fit, I could easily see a situation where assumptions need to be made for inference to work, and the most ergonomic assumptions are different for most values vs. bags of errors.