franztesca
12/21/2022, 11:51 PMRaise<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!Sam Painter
12/22/2022, 6:10 AMSam
12/22/2022, 7:55 AMAppendable
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.raulraja
12/22/2022, 9:56 AMcontext(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.
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.