1

Please ignore performance issues, I am interesting in data flow, safety, modelling, reasoning.

I wonder what are the limitations of exception approach to error reporting implemented like in Java compared to Haskell-like (OCaml, and Rust use same/similar approach AFAIK).

As far as I know in both cases error is part of the function signature, but in Haskell you have to explicitly "handle" the error even if entire "handling" is just passing it further. I see a difference for the reader, in Java:

try
{
  a = foo();
  b = bar();
}

it is impossible to tell (just by looking at the code) if foo and/or bar can end up with error, also in both cases the error can be silently passed. If I understand correctly in Haskell the the counterpart code would be put into monad:

do a <- foo();
   b = bar();

(I am new to Haskell, so forgive me the errors) and it is clear where the drop occurs and which line can fail.

BUT -- this is for human, compiler knows it in both cases.

So when it comes to, say, reasoning about the data flow (for compiler, not human), what are the limitations of Java approach? In other words, what is not doable in Java that is possible in Haskell? Or what information Haskell has which is not present in Java?

10
  • 3
    I think this question is too broad - exceptions in Java and in Haskell are completely different things due to the languages being completely different. I don't think it makes sense to enumerate the differences since it would come down to enumerate fundamental differences between the two languages in general.
    – JacquesB
    May 22, 2016 at 10:26
  • @JacquesB, assuming there are million differences it is sane to say that million has to start somewhere, correct? So what are 3 differences when it comes to reasoning about errors? May 22, 2016 at 10:34
  • Java supports both checked and unchecked exceptions. Haskell exceptions are mostly comparable to checked exceptions, while unchecked exceptions are more like Haskell "errors", which are completely different from exceptions. In Java exceptions are baked into the core language but in Haskell they are a library feature, and you could create your own exception framework if you want.
    – JacquesB
    May 22, 2016 at 10:49
  • @JacquesB, .... and how this what you wrote translate to analysis of the data flow? May 22, 2016 at 11:33
  • 2
    Check out also Monad vs Exception. You are comparing two different approachs for the same goal.
    – Laiv
    May 22, 2016 at 12:42

2 Answers 2

2

I think the most interesting difference between the two, certainly from the perspective of a language designer, is that Haskell's error handling is not a language feature but is part of the library, and yet it manages to be just as expressive and easy to work with as Java's. The reason that this works so nicely is due to Haskell's widespread use of Monads, which allow two useful properties:

  • the code implementing the Monad type is passed blocks of code (i.e. functions that operate on the value(s) stored in the Monad and return a new Monad instance, which are conceptually comparible to the notion of either a code block or a statement in most imperative languages), and is allowed to call those one, none, or indeed as many times as it wants. In any order it wants (cf the continuation monad). This means that user level code can effectively implement just about any flow control pattern it needs.

  • the language provides easy-to-use syntax sugar that makes writing code using Monads particularly easy.

Another interesting feature of Haskell's reliance on a user-supplied data type for handling errors is that if you write data-type generic code, then the caller of that code can dictate how the errors are handled. For example, if I write a function with a signature like this:

myUsefulFunction :: Monad m => String -> String -> m String

I can call this function in a variety of ways... for example, using maybe defaultString id (myUsefulFunction str1 str2), which will give the result of the function on success or a default if it failed, or using runError (myUsefulFunction str1 str2) which will return an Either containing the error message or the result. This allows more choice in how to structure my code, allowing me to produce a better result with less boilerplate code (no catching exceptions and substituting a default value, for instance).

5
  • Thank you very much, I will keep my question open though (at least for a while), because while being useful it does misaddress my question a bit. I am not after differences like "this is in language/framework", but the the limitations of Java which Haskell overcomes when it comes to data flow analysis. Maybe an example from other area -- both languages probably can handle sequences, like counting them, but if one of them can handle infinite sequence and the other cannot, I say one overcame the limitation present in the other one. And the fact it is done via framework is irrelevant. May 23, 2016 at 18:45
  • Some background explanation -- Haskell, Ocaml, Rust (and probably many others) share similar way of handling errors. One quote (but I saw more) "Rust’s design allows to mechanically eliminate a nontrivial bug class" (as opposition to Java). Obviously some breakthrough, the problem is I don't see it :-). This is why I asked about limitations, not just differences, I know them, but I don't see where they lead. I hope I put my question in more meaningful perspective. May 23, 2016 at 18:51
  • 2
    Ok, having looked up a bit about Rust's error handling, I think I can see where you're coming from. The commonality between Rust and Haskell is that error conditions are (in the end) objects that can either contain a value or some kind of error indicator, and the language forces you to check which one is present before accessing the value. The benefit quite simply is that code that ignores errors (which is a very common problem) can be caught by the compiler and rejected, simply because the data types involved explicitly require that.
    – Jules
    May 23, 2016 at 19:02
  • Thank you :-) but I still don't see it -- for example here: lucumr.pocoo.org/2014/10/16/on-error-handling, initial code includes error "handling", but entire handling is passing, or you could say leaking. Since checked exceptions in Java are part of function signature you can also compute which exception is handled or leaked. The only difference I see, in Rust the leak is explicit, in Java is implicit, but the effect is the same, you could write profiler for Java which warns you about every implicit leak. May 24, 2016 at 11:48
  • 2
    It is possible to perform such analysis, even statically in most cases (case in point: Java 8's Optional class warns you if you use the get() method without first calling isPresent()). But as pointed out by Karl Bielefeldt in this answer to a related question, such an analysis is expensive and you want to avoid doing it too much otherwise you compilation process will take too long. In Haskell and Rust, the requirement to check whether or not the result is present is encoded in the type system, and therefore happens automatically.
    – Jules
    May 24, 2016 at 12:50
1

As @Jules points out, Haskell's monads can be implemented as a library, whereas Java's exceptions are built into the language.

This has implications in practice: With Monads in Haskell, you can abstract over any function regardless of the errors, whereas in with checked exceptions in Java you can't.

For example, you can pass a function returning an Either to map:

parseURL: string -> Either string URL
map parseURL ["https://stackexchange.com", "lorem ipsum"]
-- returns [Right (URL "https://stachexchange.com"), Left "invalid url"]

It works because an Either is just a value that encodes failure, not a special language construct for failures. And you can return any value from a function passed to map.

In Java, however, this doesn't work:

Stream.of("https://stackexchange.com", "lorem ipsum")
      .map(URI::new)

While you can also return any value from a function passed to map, you cannot throw checked exceptions. Stream::map takes a Function, a functional interface whose apply method doesn't declare any checked exceptions to be thrown. URI::new, on the other hand, throws an InvalidURISyntaxException, so it cannot be used as a Function.

There are several workarounds, but clearly Haskell wins here in terms of elegance.

Also, note how the results are collected into a list, so we can get inspect multiple failures at once.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.