One of the great advantages of Elm is its strong runtime guarantees, and one important technique it uses to achieve this is using data types such as Maybe
and Result
to handle errors, instead of having values like null
or undefined
in the language. These structures are very nice to work with, but if you don't know the tools at your disposal, they can be tricky.
Suppose you have some data such as:
data : List String
data = ["1", "17", "a87", "34", "3b7"]
This was supposed to be a list of integers, but for whatever reason whoever provided you with this data also left in a bunch of garbage like "a87"
. You're only interested in the strings that are valid integers, and you also want to work with them as integers, not strings. Let's start writing our convert
function.
We can convert strings to integers with the function
String.toInt : String -> Maybe Int
and we can use our old friend List.map
to apply the conversion to all the elements in the list:
List.map String.toInt data
-- [Just 1, Just 17, Nothing, Just 34, Nothing]
Now we have our integers, wrapped in Just
, so all we have to do now is to remove the values that are Nothing
.
"I know this one! That's List.filter
, right?", you might have said prior to learning the lesson that I am getting to in this post. Let's try.
List.filter isJust (List.map String.toInt data)
There are two problems with this solution:
First of all, isJust
is not provided by the core library. We could get it from elm-community/maybe-extra
, or we could save ourselves a dependency and just define it ourselves:
isJust maybe =
case maybe of
Just _ -> True
Nothing -> False
Now we can see if it works:
List.filter isJust (List.map String.toInt data)
-- [Just 1, Just 17, Just 34]
We got rid of the bad entries, but we have another problem. All our values are still wrapped up in Just
! Each time you would want to use these values, you would have to handle the case of it being Nothing
, even though we just made sure all the Nothing
-values are gone. We have now introduced an impossible state 💀. What we really want is a function with the type
convert : List String -> List Int
as opposed to what we had, which was:
convert : List String -> List (Maybe Int)
Fortunately the solution is very simple! The core library provides the function which does exactly this:
List.filterMap : (a -> Maybe b) -> List a -> List b
List.filterMap
takes a function that produces a Maybe
, and applies that functions to all the elements of a list while unpacking the Just
-values, and removing the Nothing
-values.
Our final convert function can then be defined simply as:
convert = List.filterMap String.toInt
convert data
-- [1, 17, 34]
Conclusion
Error handling in Elm is easy as long as you you have the right functions in your tool belt. With List.filterMap
and a few more you will be able to handle anything 🎉