I would like to second folkertdev’s answer. Because Elm is all about those cases in which you might hurt yourself accidentally.
With a Result
, no caller can ever forget that the computation might fail. And they will have all the information they need in case it does. This is exactly why Elm can promise no runtime errors. I admit, you have to shift away from the thinking in other, more common languages, but the reward is that your code is very robust and thus easy to refactor and debug.
The full example with Result
, with some extra Health and Safety stuff in the parameters:
-- Assuming Input looks something like this.
type Input a
= Input a
range : { min : comparable, max : comparable } -> Input comparable -> Result { min : comparable, max : comparable } (Input comparable)
range bounds (Input value) =
if (value >= bounds.min) && (value <= bounds.max) then
Ok <| Input value
else
Err bounds
With this, it will be really hard to switch up the min and max.
And for refactoring the call sites, it’s not that bad. Currently, you either use just use the value, ignoring the failure case, or you handle the possible failure right at the call. (I don’t really see what other alternatives there are.)
In case you just use the value, it’s as simple as specifying a default value.
-- So this:
range 0 10 input
-- becomes this:
Result.withDefault 0 <| range {min = 0, max = 10 } input
And for handling the error, you simply pattern match the Result
.
case range { min = 0, max = 10 } input of
ok validinput ->
-- Continue
"ok"
Err { min, max } ->
let
errorMessage =
"Out of range [" ++ String.fromInt min ++ ", " ++ String.fromInt max ++ "]"
in
-- Output error message
errorMessage
I’ve actually checked that Elm infers the correct type for min
and max
. If you do String.fromChar min
it throws a compiler error.
Now how do we know that min
and max
are number
s? Because we just called range
with those as the boundaries. So since we handle the error right were it occurred, we are not really missing the generic Debug.toString
.
Why all this hassle? Well, previously either you handled the error anyway and this is just a different method for doing so, or you ignored that the error might occur and risked runtime errors. I find it much more helpful to have the Elm compiler point out that I didn’t handle the error case and thus have a really stable program.