Let’s use our map, andMap, andThen we should find some interesting Parsers.

Monads, what are they good for?

Where we put it all together to discover a new lib.

Michel Belleville
Published in
4 min readMay 2, 2020

--

me> So… what’s all this good for anyways? 😩

wat> What is “all this” you speak of? 🤨

me> Functors, Monads, Applicatives… what’s the point?

wat> Ooooh, someone is moody today. 😅

me> I mean, I know Functors are things you can map on, Monads things you can andThen on and Applicatives things you can andMap on… and it feels like whoop-dee-doo, what do I do with all this now?

wat> Because you were not trying to learn all this just for the pure joy of learning useless facts? 😏

me> Kind of. Yes. In fact, I’d rather do this for a purpose, to be honest.

wat> Well, let’s see whether this knowledge helps you discovering a new library… say… the Parser module.

me> I don’t know… that sounds complicated. 😐

wat> All the better. Let’s open our books at… https://package.elm-lang.org/packages/elm/parser/1.1.0/ :

me> Unsurprisingly, it’s about creating Parser a instances… that unsurprisingly promise to parse an arbitrary string and either produce an a value or fail… sounds familiar…

wat> I’d says it looks an awful lot like a more generic version of the Decoder a type, doesn’t it?

me> Yes… looking into https://package.elm-lang.org/packages/elm/parser/1.1.0/Parser we learn that Parser a is an alias for a Parser Never Problem a… which is confusing. How can Parser a be defined using itself like that? And there’s not even the same number of parameters for each one?

wat> That’s because they’re not the same type. The Parser a type comes from the Parser module while the Parser context problem a type lives in the Parser.Advanced module. So Parser.Parser a is an alias for Parser.Advanced.Parser Never Parser.Problem a.

me> Ok… so, Never being a special elm type that has no value, I guess a Parser a will not provide context, whatever that’s for ; also, I guess the problem type parameter is for a type that represents problems encountered so the Problem type must be the way the Parser module characterizes those problems… so the only type variable that we have to care about is our a type that is promised?

wat> Yes. We also find a lot of pre-built standard Parser like int, float, …

me> Some of those look funny 🤨 Like:

symbol : String -> Parser ()

wat> What’s so funny about it?

me> Well, what is a Parser () for? What’s the point of parsing something if all we get is an empty tuple?

wat> Hmm… remember when we said that Functor, Monads and Applicatives could be seen as a way to wrap other types of values in something like a context?

me> Yes? What’s the context for Parser a then?

wat> It’s a context that promises to either succeed parsing, or fail… but while parsing, this context is also a way to remember what character your parser is currently looking at.

me> Looks like you’ve just spoiled me the ending there: Parser a is also a Functor, a Monad and an Applicative, right?

wat> Let’s not go too far ahead, but yes it is. Now, to answer your question, a Parser () will not extract an interesting value, but it can say either “hey, this here character is totally what I was expecting there” or “hey, there’s a problem here, this character is not at all what I expected”, and then pass to the next character (or characters) for the next parser in the chain to do its job (or not, because there’s no point continuing when the previous parser in the chain failed).

me> A little bit like when we’re using andMap to accumulate multiple Maybe values, when one is Nothing the end result will be Nothing no matter what the other Maybe hold?

wat> Yup. Just like that.

me> Ok. But… if this is an applicative too, where’s the andMap?

wat> Just look at this:

(|=) : Parser (a -> b) -> Parser a -> Parser b

me> Hmm… it takes an a -> b wrapped in a Parser context and a Parser a to make a Parser b… that looks like a proper andMap indeed… but why is it not called andMap?

wat> I guess because designers of the Parser module wanted to adopt a pipeline kind of style:

include Parser exposing (..)clownCarParser : Parser ClownCar
clownCarParser =
succeed ClownCar
|= clownParser
|= clownParser
|= clownParser
|= clownParser
-- in my days, clown cars could hold more than just 4 clowns

me> I see… so, I guess you wrap your empty ClownCar constructor in a successful parsing with Parser.succeed, then you take it for a ride accumulating potential clowns with your |= clownParser calls until it’s full. Just like we did filling functions with Decoder.andMap.

wat> Quite. Now, if you have a Parser a and need a Parser b?…

me> I can either use an a -> b function with Parser.map to pull out the a from a successful parsing and turn it into a b that Parser.map will re-wrap in a successful parser… or maybe go the andThen route when the a value may or may not be enough to guarantee I can have my b. Right?

wat> Right. And that’s how knowing about the Functor, Monad and Applicative stuff is useful. They can help you get to speed with new libraries (provided the authors are adhering to the rules¹).

me> Well, I guess I’m going to have a closer look at all these interesting Parser functions…

wat> Tell me what you discover next time 😃

[1] there are rules a proper Functor, Applicative or Monad must observe that we haven’t mentioned yet ; we’ll probably get into them sooner or later, probably when we talk about how to make your own types a Functor, Applicative or Monad.

--

--