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?

greenoldman
  • 1,506
  • 1
  • 14
  • 27
  • 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 '16 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? – greenoldman May 22 '16 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 '16 at 10:49
  • @JacquesB, .... and how this what you wrote translate to analysis of the data flow? – greenoldman May 22 '16 at 11:33
  • I think it's a perfectly valid question – mb14 May 22 '16 at 11:46
  • What JacquesB is trying to say (I think) is that you are asking for control flow differences between 2 different paradimgs. OO vs Functional (heskell). Two languajes that works at different levels. Being heskell a high level languaje and interpreted. – Laiv May 22 '16 at 12:33
  • 2
    Check out also [Monad vs Exception](http://programmers.stackexchange.com/q/150837/222996). You are comparing two different approachs for the same goal. – Laiv May 22 '16 at 12:42
  • @Laiv, I know that my question is about two different languages, that's the whole point (I am surprised it is so "controversial"). Thank you for the link, I still don't see any case in Haskell like approach which is not doable in Java. Yes, sure, Java requires keywords, but this is not world shattering issue and secondly Haskell has to treat Either/Maybe in special way too, so it cannot blindly assume it is type as any other (not mentioning special syntax with `<-`). – greenoldman May 22 '16 at 13:36
  • I think the question would better asking about differences between these 2 paradigms instead of 2 languajes. May be you could get a better overview about their strengths and weakness. Im also interested on what does heskell/scala preferible over OO. As you pointed both paradigms can do the same things. – Laiv May 22 '16 at 14:38
  • @Laiv, but I am not asking about differences, only about the limitations of Java approach. I already read about both approaches and found only those 2 issues plus quite a lot of claims about Haskell but without any examples or explanations. Thus I wonder what I am missing. The languages are given to focus on something, and I mentioned Rust and Ocaml (on Haskell-like camp), as for Java AFAIK nobody used the same approach. – greenoldman May 23 '16 at 06:30

2 Answers2

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).

Jules
  • 17,614
  • 2
  • 33
  • 63
  • 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. – greenoldman May 23 '16 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. – greenoldman May 23 '16 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 '16 at 19:02
  • Thank you :-) but I still don't see it -- for example here: http://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. – greenoldman May 24 '16 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](http://programmers.stackexchange.com/a/318945/66037), 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 '16 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.

mperktold
  • 151
  • 6