The Switch
When we learn how to get Nothing
from Just something
me> Wat… how are you doing?
wat> I’m on the right tracks, baby… 🚋
me> Do I want to ask you what you’re doing on a train?… 🤔
wat> Well, since last time we found a map
and all I thought I’d explore a little bit. 😉
me> Good, because I found another family of functions that looks quite like map
and I was wondering if you could tell me more…
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
Generator.andThen : (a -> Generator b) -> Generator a -> Generator b
Decoder.andThen : (a -> Decoder b) -> Decoder a -> Decoder b
wat> Oh my… looks like you’ve stumbled over the dreaded M-word!
me> What’s the M-word? Is it like Functor is the F-word?
wat> Yes, but wooooorse. The horrible, terrible M-word is so powerful it is rumored that those who understand it are cuuuuursed and unable to explain it to others. 👻
me> You’re kidding me right?
wat> Do I look like the kind of ducky that believes in curses? 😆 Nah, it’s nothing as complicated as programmers’ folk tales have it. In fact, it’s pretty much as simple as understanding map
and Functors. Remember you can replace the word Functor by “something that can be map
-ed over”?
me> Yup.
wat> So now, the word is Monad. And it can be replaced by “something that can be andThen
-ed over”.
me> But… I don’t know what… andThen
… does… and… 🙄 you’re going to make me read signatures and figure it out by myself again, aren’t you?
wat> 😇
me> Ok… Let’s start with Maybe
, at least I know what a Maybe
looks like…
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
me> … so, andThen
for Maybe
:
- takes a function that takes an a
and returns a Maybe b
- then it takes a Maybe a
- and finally, returns a Maybe b
…
…which I guess is produced by unpacking the a
value in the Maybe a
, feeding it to the a -> Maybe b
function?
wat> Exactly. And if the Maybe a
was a Nothing
after all?
me> It just returns Nothing
?
wat> Yes, because Nothing
is a valid Maybe b
value too.
me> So… how is that different from map
? 😅
wat> Let’s compare their signature:
Maybe.map : (a -> b) -> Maybe a -> Maybe b
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
me> Oh… Maybe.map
takes an a -> b
transformation while Maybe.andThen
takes an a -> Maybe b
transformation.
wat> Which means that the operation you give to Maybe.map
can only change what is inside your Maybe
; if your original Maybe
was a Just
, the result will necessarily be another Just
, never a Nothing
. With Maybe.andThen
you can choose whether you’ll return another Just
or, in a surprise twist give back a Nothing
!
me> Hmm… So, instead of letting Maybe.map
unpack and re-pack the value for me, Maybe.andThen
only unpacks it and lets me do the re-packing?
wat> Yep. 📦
me> Why?
wat> Let’s consider a situation in which we receive an input from the user. We receive a String
, and it needs to be casted to Int
…
me> Sounds familiar 😉 we can use String.toInt
and we get a Maybe Int
. Now what?
wat> We also want the Int
value to be a positive value, so Just -1
, Just -23
or Just -777
are as useful to us as Nothing
. Say we write a function that takes the Int
we got (maybe) and returns a Maybe Int
if it’s negative:
onlyPositive : Int -> Maybe Int
onlyPositive value =
if value >= 0 then
Just value
else
Nothing
me> So… that’s where Maybe.andThen
comes in and saves the day, right?
wat> 👏 Exactly:
input
|> String.toInt
|> Maybe.andThen onlyPositive
wat> Now, let’s say I only want integers that are lower than 9000 too…
me> We could change onlyPositive
to integrate that condition?
wat> We’d have to change the name too if we don’t want to surprise whomever comes next to read the code… besides, isn’t it better to have small functions that do one thing and do it well? So let’s make another simple function instead:
itsUnder9000 : Int -> Maybe Int
itsUnder9000 value =
if value < 9000 then
Just value
else
Nothing
me> And now we can add it to the pipeline I guess:
input
|> String.toInt
|> Maybe.andThen onlyPositive
|> Maybe.andThen itsUnder9000
wat> Yup 😄 Since we always get a Maybe
value back (just like with Maybe.map
), we can pile up Maybe.andThen
s and Maybe.map
s in the pipeline and work on the potential value as if we were sure it was there (or let the piepline bypass all those Nothing
values right to the end).
me> So, it’s a bit as if we had two parallel tracks… when we use Maybe.map
, whichever track we’re on we continue along, transforming the value when there is one, or just passing by on the other track when there’s none…
Just 1 |> Maybe.map ((+) 2) -- on the 'Just' track...
> Just 3 -- ...we continue with a 'Just'Nothing |> Maybe.Map ((+) 2) -- but on the 'Nothing' track...
> Nothing -- ...we continue with a 'Nothing'
wat> …whereas when we use Maybe.andThen
, we have the opportunity to go from the Just
track to the Nothing
track.
Just 1 |> Maybe.andThen onlyPositive -- on the 'Just' track..
> Just 1 -- ...we may continue with a 'Just'...Just -1|> Maybe.andThen onlyPositive
> Nothing -- ...but we may also continue with a 'Nothing'Nothing |> Maybe.andThen onlyPositive -- on the 'Nothing' track...
> Nothing -- ...we always continue with a 'Nothing'it to others. 👻
me> Ok… but what’s a Monad in all this? And how does it work with a Generator
or a Decoder
?