It actually works rather well in practice. `Either` and `Maybe` are monads. In hand-wavy terms, this means you can control how functions returning Either values (that is, functions that can have an error) are composed. This means that the semantics for propagating an error are basically encoded in the `Either` type.
In practice, this means that if you have code that should either act on a valid value or just return the error if given an error, you don't have to write anything special at all. A concrete example: let's imagine yourFunc needs to get a bunch of values that could be errors; you could write code like this:
do a <- couldError "1"
b <- couldError "2"
c <- couldError "3"
return [a, b, c]
This will then work much like an exception: if either a, b or c are an error, the end result will be that error; if they are all normal values, the end result will be a normal value. You only ever have to pattern match when you want to handle errors in some different way and extract the normal type from the Either type. This is exactly the same as a catch statement for exceptions.
Since these types are first-class citizens, it's also really easy to add other functions to operate on them. For example, you can use the existing <|> operator to alternate between possible cases.
result = one <|> two <|> three <|> Left "all failed"
(Left is the part of Either that is used to hold errors; the reason it isn't named error is that Either can actually be used for other sorts of control flow as well.) This code will return the first of one, two, three that is not an error or give you "all failed" as the error.
Another useful function is `optional`. This does exactly what it sounds like: it allows you to mark some particular function as "optional", so that if it errors out that error gets ignored.
I think code like this is clearer and easier to read than the alternative with exceptions would be.
However, the important part is not the particular functions, but that you can add your own. Both <|> and optional are library functions that are not even specific to error handling--they work on a bunch of other types like parsers.
I think having error handling be first class like this is very useful and makes writing code with it much nicer.
In practice, this means that if you have code that should either act on a valid value or just return the error if given an error, you don't have to write anything special at all. A concrete example: let's imagine yourFunc needs to get a bunch of values that could be errors; you could write code like this:
This will then work much like an exception: if either a, b or c are an error, the end result will be that error; if they are all normal values, the end result will be a normal value. You only ever have to pattern match when you want to handle errors in some different way and extract the normal type from the Either type. This is exactly the same as a catch statement for exceptions.Since these types are first-class citizens, it's also really easy to add other functions to operate on them. For example, you can use the existing <|> operator to alternate between possible cases.
(Left is the part of Either that is used to hold errors; the reason it isn't named error is that Either can actually be used for other sorts of control flow as well.) This code will return the first of one, two, three that is not an error or give you "all failed" as the error.Another useful function is `optional`. This does exactly what it sounds like: it allows you to mark some particular function as "optional", so that if it errors out that error gets ignored.
I think code like this is clearer and easier to read than the alternative with exceptions would be.
However, the important part is not the particular functions, but that you can add your own. Both <|> and optional are library functions that are not even specific to error handling--they work on a bunch of other types like parsers.
I think having error handling be first class like this is very useful and makes writing code with it much nicer.