Maybe: it’s Just / Nothing
When failure is an option and runtime errors are not
me> Hey.
wat> 🎼 Hey, I just met you 🎶 and I’m your ducky 🦆 so what’s the matter? 🎵 Just tell me maybe…
me> 👏 soooo… I need to convert a String
into an Int
and what I found is this: String.toInt : String -> Maybe Int
; what’s a Maybe
?
wat> It’s a type. Here’s its definition:
Maybe something
= Just something
| Nothing
me> Ok, let me see if I get it: Maybe something
is a type with one type parameter (something
) ; its values are either Just
(which holds a value of type something
inside) or Nothing
(and there’s nothing inside Nothing
).
wat> Yep. So you can have a Maybe Int
that can take values like Just 1
, Just 747
, Just -23
or Nothing
for example.
me> So that means String.toInt
will try and convert the String
into an Int
, and give me back either a Just
with the Int
inside when it succeeds, or Nothing
when it failed?
wat> Looks like it does.
me> Cool… But when I tried to add a number to my Maybe Int
I had a compilation error:
> (String.toInt "213") + 456
-- TYPE MISMATCH --------------------------------------- elmAddition does not work with this value:4| (String.toInt "213") + 456
^^^^^^^^^^^^^^^^^^
This `toInt` call produces:Maybe IntBut (+) only works with Int and Float values.Hint: Use Maybe.withDefault to handle possible errors. Longer term, it is usually better to write out the full `case` though!
wat> Aaaah, the elm compiler, always helpful. What it’s telling you here is that you’re trying to add a Maybe Int
with a number
, and that is not possible because you can only add a number
to another number
as the signature for the (+)
function attests: (+) : number -> number -> number
.
me> Fine, I can’t do that. But what can I do?
wat> You can do what the compiler also suggests: using Maybe.withDefault
to convert your Maybe Int
into an Int
; here’s the signature for Maybe.withDefault : something -> Maybe something -> something
me> So… Maybe.withDefault
takes a value of type something
, another value of type Maybe something
and returns another value of type something
?
wat> Exactly. From the signature, I’ll hasard a guess as to how it works:
Maybe.withDefault : something -> Maybe something -> something
Maybe.withDefault default maybe =
case maybe of
Just value ->
value Nothing ->
default
me> It takes the maybe
and checks whether it’s a Just
and return the value inside the Just
, or it finds a Nothing
in which case it returns the default
?
wat> Yep. So now we can do what you wanted to do, and it works:
> (Maybe.withDefault 0 (String.toInt "123")) + 456
579 : Int
me> Cool… and if the String
was not convertible into a number, Maybe.withDefault 0 (String.toInt "not convertible into a number")
would be 0
and the result of the addition would be 456
?
wat> You’re getting good at this. The point of a Maybe
is to give you the possibility of failing without causing a runtime error (forcing the programmer to deal with the failure, ultimately). And that’s an interesting property because sometimes you don’t want to deal with the error just now.
me> You mean, change what is in the Just
(if it’s a Just
and not a Nothing
) but keep it under the Maybe
wrapping?
wat> Yes. It would be cool if we had a function that takes a maybe and a transformation on its content, and returns a new maybe with the transformation applied to its content…
me> We can write one with a case:
transformInsideMaybe : Maybe a -> (a -> a) -> Maybe a
transformInsideMaybe maybe transformation =
case maybe of
Just value ->
Just (transformation value) Nothing ->
Nothing
wat> What you’ve just written looks just like another function I know…
Maybe.map : (a -> b) -> Maybe a -> Maybe b
me> What does it do?
wat> It does just what your transformInsideMaybe
function does, but the transformation argument goes first, and the transformation can change the type of what may be inside the Maybe
value.
me> Changing the type is cool… but why put the transformation first?
wat> That’s because it allows you to “package” a transformation with the Maybe.map
, which is useful when you want to use the pipeline style:
someString
|> String.toInt
|> Maybe.map (\n -> n + 456)
|> Maybe.withDefault 0
me> 😮 looks like someone will have to tell me about this “piping” style…
wat> 😉 looks like someone needs a break…
me> 😅 looks like someone’s guilty as charged.