This came up on Slack, and I found myself thinking about today, so here goes…
What are the best practices for error handling in Elm? - A language that does not have exceptions, which are a tool that a lot of more common languages have and that most Elmers are also likely to be aware of.
My thoughts; feel free to criticise, correct or share your understanding.
How do we represent errors in Elm?
What type?
There are 2 obvious ways, Maybe
or Result
. I prefer to think of Maybe
as for things that are optional. Result
is a better way of representing an error, because we can give an error message or other context to the error.
head : List a -> Maybe a
Is it really an error for a list to not have a head? It can be if we are expecting that to be the case, but then we should use a NonEmptyList
. Also, in this case there can only be one way things can go ‘wrong’, so the error should be obvious, and a Maybe
is sufficient to cover that. head
is also a better seen as a partial function that something that can produce an error.
What type arguments?
We could define an error by an error message which is a String, or we could provide some more structured data. Which is more appropriate in different situations?
Result String a
A convenient way of expression an error is to just describe in a String what went wrong, and wrap that as a Result
.
This seems most appropriate to me as a way of reporting an error to the developers than to the end user. End users might want error descriptions to be translated into their own language.
This also does not seem appropriate in situations where a recovery might be possible. The code might need information about what went wrong in a more structured way in order to attempt a recovery.
For example, the Http package has:
type Error
= BadUrl String
| Timeout
| NetworkError
| BadStatus Int
| BadBody String
If we got a Timeout
error, we could recover by trying again automatically. That would be trickier to figure out from a String
error message.
I can see a parallel with runtime and checked exceptions in Java here. A runtime exception represents a bug in Java (like division by zero), and is something the developers want to find out about in order to fix the bug. A checked exception is either something that might have a recovery, or some appliciation logic level thing representing an error the application user should be told about. But Elm does not have runtime exceptions, I hear you say.
Recently I was using an API, that I had generated a stub from its API specification. The return type was something like this:
{ accessToken : Maybe String,
idToken : Maybe String,
authenticated : Bool,
expiry : Maybe Int
}
I know that when the flag is True
all the Maybe
s will be Just
, but the return type does not give me that. Nor could the stub generator infer it, because its information that was simply missing from the spec used as a source for the stub. In my code I have to assume they will all be Just
but the compiler forces me to deal with the alternatives that ‘should-never-happen’. I feel confident it won’t happen and if it does the end user is not interested in being told - it is for the technical team to fix investigate and fix that bug. So that one seems to operate a lot like a runtime error, even though it is not in Elm.
As a custom type, instead of Result.
Seems to me that application level ‘error’ conditions, in which the user might very well be interested, might be better expressed with custom types. Suppose you have some web application that monitors some data that you must check every day, but today the data has failed to arrive. The user wants to know when the data is not available, so they can phone the ops team and ask what the hold-up is:
type DailyData =
Data DataModel
| NoData
The user will be told about this by rendering different views from this data model. It seems a bit more direct and less inconvenient than using Result
- I won’t make use of functions like Result.mapError
on these errors, they will drive view logic more directly; so is it worth the extra layer of wrapping in a Result
?
More…
Next time I feel like adding to this, where can errors come from in Elm programs?