What is the difference between `Raise<MyFailure...
# arrow
f
What is the difference between
Raise<MyFailure>
and java checked exceptions
throws MyFailure
? A colleague of mine rightfully asked me this and I don't really have an answer. Indeed java checked exceptions are a typed effect system too, right? If they have been abandoned by K because they are considered a counterproductive feature, why would we want to reintroduce it using
Raise
(or
Either
)? If we look at the doc of kotlin, the example of
Appendable
and
StringBuilder
would have the same problem with
Either
and
Raise
, no? I'm trying to get this straight. Thanks a lot!
s
I haven’t played with Raise yet but my intuition is that a lot of the normal reasoning for preferring Either to Exceptions applies. Raise should be referentially transparent and require explicit control flow for handling error conditions. It should also be much cheaper as it doesn’t create a stack trace. We can apply operations over collections of expressions that Raise in a much more natural and explicit way than expressions that throw.
s
The example you linked to in the docs isn't a very good one, IMO. Sure,
Appendable
declares
IOException
, but they imply that
StringBuilder
methods therefore have to declare that exception too, which is false. It's true that both with checked exceptions and with
Either
, a method signature has to declare the broadest type of error that implementations of that method may emit. Subclasses can narrow that signature as they wish.
StringBuilder
does not declare any exceptions. In that regard you could actually argue that checked exceptions are better, because you can narrow the signature to throw no exceptions at all, whereas with
Either
, every implementation still has to know what an
Either
(or at least a
Right
) is even if that particular implementation has no failure modes. But functional style is where
Either
really shines. Whether you like checked exceptions or not, they did not work well with Java's lambda functions and streams. Whereas using
Either
to encode failures in the return type makes it trivial to call side-effectful functions from lambdas and have predictable failure handling. In short: to be useful, a solution has to be better than the problem it's trying to solve. Checked exceptions do solve a big problem, but something about their design seems to lead to sloppy usage and bad discipline. But that doesn't mean the problem doesn't need solving. So
Raise
+
Either
is an attempt to provide a more disciplined and robust solution.
r
The main differences are: Generics support With Raise you can raise any value regardless of hierarchy. Exceptions for example must extend Throwable and have limitations on generics and other uses.
Copy code
context(Raise<String>) 
fun foo(): Int {
  raise("boom")
  return 1
}
Here we say that we can't run the effects on
foo
unless we have a
Raise<String>
handler in scope. If we were using checked exceptions you would have to create your own exception type. If you wanted to introduce a value to raise as alternative you would have to make that exception instance take an
Any?
because exceptions can't have generics.
Copy code
class GenericException<A>(value: A) : Exception(value.toString()) 
// error: subclasses of Throwable can't have type parameters
Safety and Ergonomics Functions in java that declared typed exceptions can't be adapted to lambdas that are pure and do not declare those exceptions. This is one of the reasons why many langs avoid typed exceptions and use just runtime ones. An example is the java Streams api that does not allow functions with throws clauses https://stackoverflow.com/questions/23548589/java-8-how-do-i-work-with-exception-throwing-methods-in-streams In Kotlin this is not the case because you can declare
Raise
as context receiver and use the function in an inline lambda or similar if the calling scope also declares the
Raise
requirement.