Functors, Applicatives, And Monads In Pictures (In Elm)

Elm Version

Lucamug
Lucamug
Jun 30, 2018 · 13 min read
Image for post
Image for post

This is a translation of Functors, Applicatives, And Monads In Pictures from Haskell into Elm. I left the Haskell examples as reference.

I don’t want to take any merit for writing this, I only went through the fun exercise of translating the code snippets in Elm.

If you enjoy this post be sure to say thanks to the author of the original version: Aditya Bhargava, @_egonschiele on Twitter.

This is the list of operators and function mentioned in the article:

(+) Addition
(*) Multiplication
(//) Integer division
(%) Modulo operation
(<|) Backward function application
(|>) Forward function application
(<<) Function composition
(>>) Function composition
Maybe.Maybe
Maybe.map
Maybe.map2
Maybe.andThen
List.map
List.concatMap

With parenthesis operator can be used without the infix notation like2 + 3 == (+) 2 3, so (+)3 return a function with type annotation number -> number.

All Elm code example can run inside elm-repl, if you don’t have Elm installed you can use the online version of elm-repl. To write multiline command be sure to add “/” at the end of the line, if not already present.


Here’s a simple value:

Image for post
Image for post

And we know how to apply a function to this value:

Image for post
Image for post

Simple enough. Lets extend this by saying that any value can be in a context. For now you can think of a context as a box that you can put a value in:

Image for post
Image for post

Now when you apply a function to this value, you’ll get different results depending on the context. This is the idea that Functors, Applicatives, Monads, Arrows etc are all based on. The Maybe data type defines two related contexts:

Image for post
Image for post
In Haskell:data Maybe a = Nothing | Just aIn Elm:type Maybe a = Nothing | Just a

In a second we’ll see how function application is different when something is a Just a versus a Nothing. First let's talk about Functors!

Functors

When a value is wrapped in a context, you can’t apply a normal function to it:

Image for post
Image for post

This is where fmap comes in. fmap is from the street, fmap is hip to contexts. fmap knows how to apply functions to values that are wrapped in a context. For example, suppose you want to apply (+3) to Just 2. Use fmap:

In Haskell:> fmap (+3) (Just 2)
Just 5
In Elm:> Maybe.map ((+)3) (Just 2)
Just 5
---Elm doesn't have typeclasses (info here and here) so let's use directly Maybe.map. The type signature of Maybe.map isMaybe.map : (a -> b) -> Maybe a -> Maybe bthat is a special case offmap :: (a -> b) -> fa -> fbwhere f = Maybe
Image for post
Image for post

Bam! fmap shows us how it's done! But how does fmap know how to apply the function?

Just what is a Functor, really?

Functor is a typeclass. Here's the definition:

Image for post
Image for post

A Functor is any data type that defines how fmap applies to it. Here's how fmap works:

Image for post
Image for post

So we can do this:

In Haskell:> fmap (+3) (Just 2)
Just 5
In Elm:> Maybe.map ((+)3) (Just 2)
Just 5

And fmap magically applies this function, because Maybe is a Functor. It specifies how fmapapplies to Justs and Nothings:

In Haskell:instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing
In Elm:This is how Maybe.map is defined in Elm:map func maybe =
case maybe of
Just val -> Just (func val)
Nothing -> Nothing

Here’s what is happening behind the scenes when we write fmap (+3) (Just 2):

Image for post
Image for post

So then you’re like, alright fmap, please apply (+3) to a Nothing?

Image for post
Image for post
In Haskell:> fmap (+3) Nothing
Nothing
In Elm:> Maybe.map ((+)3) Nothing
Nothing
Image for post
Image for post

Like Morpheus in the Matrix, fmap knows just what to do; you start with Nothing, and you end up with Nothing! fmap is zen. Now it makes sense why the Maybe data type exists. For example, here's how you work with a database record in a language without Maybe:

post = Post.find_by_id(1)
if post
return post.title
else
return nil
end

But in Haskell:

In Haskell:fmap (getPostTitle) (findPost 1)In Elm:Maybe.map getPostTitle (findPost 1)

If findPost returns a post, we will get the title with getPostTitle. If it returns Nothing, we will return Nothing! Pretty neat, huh? <$> is the infix version of fmap, so you will often see this instead:

In Haskell:getPostTitle <$> (findPost 1)In Elm:Maybe.map getPostTitle (findPost 1)---infix operators will not be supported in new releases of Elm so let's keep using Maybe.map

Here’s another example: what happens when you apply a function to a list?

Image for post
Image for post

Lists are functors too! Here’s the definition:

In Haskell:instance Functor [] where
fmap = map
In Elm:List.map

Okay, okay, one last example: what happens when you apply a function to another function?

In Haskell:fmap (+3) (+1)In Elm:(+)3 << (+)1---In Elm "<<" and ">>" are the infix operators for function composition

Here’s a function:

Image for post
Image for post

Here’s a function applied to another function:

Image for post
Image for post

The result is just another function!

In Haskell:> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15
In Elm:> foo = (+)3 << (+)2
> foo 10
15

So functions are Functors too!

In Haskell:instance Functor ((->) r) where
fmap f g = f . g
In Elm:(>>) g f x = f (g x)
(<<) f g x = f (g x)

When you use fmap on a function, you’re just doing function composition!

Applicatives

Applicatives take it to the next level. With an applicative, our values are wrapped in a context, just like Functors:

Image for post
Image for post

But our functions are wrapped in a context too!

Image for post
Image for post

Yeah. Let that sink in. Applicatives don’t kid around. Control.Applicative defines <*>, which knows how to apply a function wrapped in a context to a value wrapped in a context:

Image for post
Image for post

i.e:

In Haskell:Just (+3) <*> Just 2 == Just 5In Elm:Just ((+)3) |> applicative (Just 2) == Just 5---Elm doesn't have <*>. We can achieve the same result defining "applicative" as> applicative maybeValue maybeCallback = \
case maybeCallback of \
Just callback -> case maybeValue of \
Just value -> Just (callback value) \
Nothing -> Nothing \
Nothing -> Nothing
or shortly as> applicative = Maybe.map2 (|>)type signature isapplicative : Maybe (a -> b) -> Maybe a -> Maybe bwhile in Haskell(<*>) :: f(a -> b) -> fa -> fb

Using <*> can lead to some interesting situations. For example:

In Haskell:> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]
In Elm:> [(*)2, (+)3] |> applicativeList [1, 2, 3]
[2, 4, 6, 4, 5, 6]
---In this case we need to build a new applicative for the list> applicativeList l fl = \
List.concatMap (\f -> List.map (\i -> f i) l) fl
Image for post
Image for post

Here’s something you can do with Applicatives that you can’t do with Functors. How do you apply a function that takes two arguments to two wrapped values?

In Haskell:> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST
In Elm:> Maybe.map (+) (Just 5)
Just ((+)5)
> Maybe.map (Just ((+)5)) (Just 4)
-- TYPE MISMATCH -----------------------------The 1st argument to function `map` is causing a mismatch.7| Maybe.map (Just ((+)5)) (Just 4)
^^^^^^^^^^
Function `map` is expecting the 1st argument to be:
a -> Maybe (number -> number)But it is:Maybe (number -> number)Hint: It looks like a function needs 1 more argument.

Applicatives:

In Haskell:> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8
In Elm:> Maybe.map (+) (Just 5)
Just ((+)5)
> Just ((+)5) |> applicative (Just 3)
Just 8

Applicative pushes Functor aside. "Big boys can use functions with any number of arguments," it says. "Armed <$> and <*>, I can take any function that expects any number of unwrapped values. Then I pass it all wrapped values, and I get a wrapped value out! AHAHAHAHAH!"

In Haskell:> (*) <$> Just 5 <*> Just 3
Just 15
In Elm:> Maybe.map (*) (Just 5) |> applicative (Just 3)
Just 15

And hey! There’s a function called liftA2 that does the same thing:

In Haskell:> liftA2 (*) (Just 5) (Just 3)
Just 15
In Elm:> Maybe.map2 (*) (Just 5) (Just 3)
Just 15

Monads

How to learn about Monads:

  1. Get a PhD in computer science.
  2. Throw it away because you don’t need it for this section!

Monads add a new twist.

Functors apply a function to a wrapped value:

Image for post
Image for post

Applicatives apply a wrapped function to a wrapped value:

Image for post
Image for post

Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.

Let’s see an example. Good ol’ Maybe is a monad:

Image for post
Image for post

Suppose half is a function that only works on even numbers:

In Haskell:half x = if even x
then Just (x `div` 2)
else Nothing

In Elm:
half x = if even x \
then Just (x // 2) \
else Nothing
---"even" is not part of the standard library. It can be defined like:even n = n % 2 == 0
Image for post
Image for post

What if we feed it a wrapped value?

Image for post
Image for post

We need to use >>= to shove our wrapped value into the function. Here's a photo of >>=:

Image for post
Image for post

Here’s how it works:

In Haskell:> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
In Elm:> Just 3 |> Maybe.andThen half
Nothing
> Just 4 |> Maybe.andThen half
Just 2
> Nothing |> Maybe.andThen half
Nothing
---The analogue of ">>=" in Elm, for Maybe, is Maybe.andThen that is defined as:andThen : (a -> Maybe b) -> Maybe a -> Maybe b
andThen callback maybeValue =
case maybeValue of
Just value ->
callback value
Nothing ->
Nothing

What’s happening inside? Monad is another typeclass. Here's a partial definition:

In Haskell:class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
In Elm: andThen : (a -> Maybe b) -> Maybe a -> Maybe b

Where >>= is:

Image for post
Image for post

So Maybe is a Monad:

In Haskell:instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
In Elm:andThen func maybe =
case maybe of
Nothing -> Nothing
Just val -> func val

Here it is in action with a Just 3!

Image for post
Image for post

And if you pass in a Nothing it's even simpler:

Image for post
Image for post

You can also chain these calls:

In Haskell:> Just 20 >>= half >>= half >>= half
Nothing
In Elm:> Just 20 \
|> Maybe.andThen half \
|> Maybe.andThen half \
|> Maybe.andThen half
Nothing
---To recap, in Haskell:Functors (<$>) :: (a -> b) -> fa -> fb
Applicatives (<*>) :: f(a -> b) -> fa -> fb
Mondads (>>=) :: (a -> fb) -> fa -> fb
In Elm, using the Maybe monad as example:Maybe.map : (a -> b) -> Maybe a -> Maybe b
applicative : Maybe (a -> b) -> Maybe a -> Maybe b
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
I rearranged the terms of "(>>=) :: m a -> (a -> m b) -> m b" to highlight the similarities with Functors and Applicatives and also to be consistent with the type signature of Maybe.andThen. To be composable the terms need to be in the proper position. This is why in Elm we use "|>" to chain andThen.In Haskell:Just 20 >>= halfIn Elm:Just 20 |> Maybe.andThen halforMaybe.andThen half (Just 20)
Image for post
Image for post
Image for post
Image for post

Cool stuff! So now we know that Maybe is a Functor, an Applicative, and a Monad.

Now let’s mosey on over to another example: the IO monad:

Image for post
Image for post

Specifically three functions. getLine takes no arguments and gets user input:

Image for post
Image for post
In Haskell:getLine :: IO StringIn Elm:getTime : Task.Task x Time.Time---Instead "IO String", let's use "Task err ok" in Elm.We will build a different example:Let suppose that we want to get the present time and use it as parameter in a first http request to get a page, and then use the returned page as parameter for a second http request to get a second page.1. get the present time
2. get a page using the time as parameter
3. get a second page using the first page as parameter
So, getLine (in the Haskell example) will be getTime in Elm:getTime = Time.nowto run these examples in the elm-repl, remember to run these commands first:import Task
import Time
import Http

readFile takes a string (a filename) and returns that file's contents:

Image for post
Image for post
In Haskell:readFile :: FilePath -> IO StringIn Elm:readFile : a -> Task.Task Http.Error String---For example:readFile time = \
Http.getString ("https://example.com?time=" ++ toString time) \
|> Http.toTask

putStrLn takes a string and prints it:

Image for post
Image for post
In Haskell:putStrLn :: String -> IO ()In Elm:readSecondFile : a -> Task.Task Http.Error String---For example:readSecondFile response = \
Http.getString ("https://example.com?response=" ++ toString response) \
|> Http.toTask

All three functions take a regular value (or no value) and return a wrapped value. We can chain all of these using >>=!

Image for post
Image for post
In Haskell:getLine >>= readFile >>= putStrLnIn Elm:getTime \
|> Task.andThen readFile \
|> Task.andThen readSecondFile

Aw yeah! Front row seats to the monad show!

Haskell also provides us with some syntactical sugar for monads, called do notation:

foo = do
filename <- getLine
contents <- readFile filename
putStrLn contents

Conclusion

  1. A functor is a data type that implements the Functor typeclass.
  2. An applicative is a data type that implements the Applicative typeclass.
  3. A monad is a data type that implements the Monad typeclass.
  4. A Maybe implements all three, so it is a functor, an applicative, and a monad.

What is the difference between the three?

Image for post
Image for post
  • functors: you apply a function to a wrapped value using fmap or <$> (for example: Maybe.map in Elm)
  • applicatives: you apply a wrapped function to a wrapped value using <*> or liftA (for example: Maybe.map2 (|>) or Maybe.map2 in Elm)
  • monads: you apply a function that returns a wrapped value, to a wrapped value using >>= or liftM (for example: Maybe.andThen in Elm)

So, dear friend (I think we are friends by this point), I think we both agree that monads are easy and a SMART IDEA(tm). Now that you’ve wet your whistle on this guide, why not pull a Mel Gibson and grab the whole bottle. Check out LYAH’s section on Monads. There’s a lot of things I’ve glossed over because Miran does a great job going in-depth with this stuff.

Examples in Ellie: https://ellie-app.com/NZqfGnCyBpa1

Gist: https://gist.github.com/lucamug/d24b18d99686d194373fec83e0b17cf7

This is a translation of Functors, Applicatives, And Monads In Pictures from Haskell into Elm. I left the Haskell examples as reference.

I don’t want to take any merit for writing this, I only went through the fun exercise of translating the code snippets in Elm.

If you enjoy this post be sure to say thanks to the author of the original version: Aditya Bhargava, @_egonschiele on Twitter.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store