Are you curious about monads? đ¤ Or maybe youâre further down the rabbit đ° hole, looking into applicatives? Does âfunctorâ sound alien đ˝ to you?
No worries! đ
Monad, applicative functor, and functor are just functional programming patterns you can use to deal with effects like lists/arrays, trees, hashes/dictionaries, and even functions.
Functor is the simplest pattern, so it makes sense to start there. As you work your way to monad, youâll see that functor is the basis for applicative functor which is the basis for monad.
â ď¸ If you donât get the animations, donât worry. Just focus on the code samples and explanations. The animations are there for some visual intuition. đ
Whatâs a functor?
A functor is a list.
map(function)(list) = [function(element) | element <- list]add1(input) = input + 1map(add1)([1,2,3])-- [2,3,4]
The list functor takes a function and applies it to every element inside the list. The function doesnât have to consider or worry about the list â map
handles the list.
A functor is a function.
map(function1)(function2)(input) = function1(function2(input))
sub2(input) = input - 2map(add1)(sub2)(1)
-- 0
The function functor composes or glues two functions together returning a new function. This new function routes its input through the two functions returning the output of the first function.
A functor is a promise.
map(function)(promise) = fmap(function)(promise)promise <- async(return 11)wait(map(sub2)(promise))
-- 9
The promise functor applies a function to its fulfilled input and returns a new promise that will contain the output of the function.
A functor is an effect that you lawfully define map
for.
map
:: (input -> output) -- Takes a function.
-> effect(input ) -- Takes input inside an effect.
-> effect( output) -- Returns output inside an effect.
In general, a functor is just map
defined for some effect. The map
function
- takes a function
input -> output
, - takes input for that function stuck inside an effect
effect(input)
, - passes the
input
to the function, - collects the functionâs
output
, - and returns the
output
stuck inside the effecteffect(output)
.
Wait, what do you mean by âlawfully define?â
The map
definition must obey the functor laws.
How many functor laws are there?
There are two laws your map
definition must obey.
Okay, whatâs the first law?
If you pass identity to map
, map
âs output must equal its input.
list = [1,2,3]identity(input) = inputlist == map(identity)(list)
-- True
Hmm, whatâs the second and last law?
If you compose two or more functions and pass that to map
, map
âs output must equal the output of composing multiple calls of map
togetherâ one for each function.
times3(input) = input * 3composition(input) = add1(sub2(times3(input)))composition(1)
-- 2list = [1,2,3]map(composition)(list) == map(add1)(map(sub2)(map(times3)(list)))
-- True
One call to map
using composition
and list
equaled three calls to map
using add1
, sub2
, and times3
and list
.
So functor is an interface?
Yes. If you define a lawful version of map
for some effect, it is an instance of functor or mappable. map
is an overloaded function that is defined differently for each type of effect you define it for.
If youâve used the usual map for lists/arraysâ found in most standard libraries â youâve been using functors all along. đş
What do you mean by effect?
List/Array is an effect. Tuple/Struct/Record is an effect. Future/Promise is an effect. Function is an effect. Tree is an effect. HashMap/Hash/Dictionary is an effect. Maybe/Optional/Nullable is an effect. Either (or the more specialized try) is an effect. There are many effects. These are some of them.
Ask what an effect is and youâll see or hear âcontextâ, âfunctorial contextâ, âcontainerâ, âcontainer and payloadâ, âwrapperâ, âboxâ, âcomputationâ, âambiguous computationâ, âstructuresâ, âdata structuresâ, ânondeterminismâ, ânondeterministicâ, âtype constructorsâ, âside-effectsâ, âa side-effect made explicitâ, âimpureâ, and/or ânot pureâ. Phew.
The definition of effect is âpower to bring about a resultâ. This is true for anything Iâve seen called an effect. They have the power or capability to bring about a result but may not always. For example, the empty list/array or nothing/none/null for maybe/optional/nullable.
I still donât get functor.
A functor lifts or upgrades a function from one that cannot operate on an effect to one that can work on a single effect, leaving the effect intact after the function is done.
In terms of an interface, if you want some effect to be a functor, you have to (lawfully) define map for it.
Can map accept a function with more than one parameter?
If some function takes more than one parameter and you map
it over some functor, it will be partially applied and stuck in the effect.
Take lists for example.
add(left)(right) = left + rightmap(add)([1,2,3])
-- [ add(1),
-- add(2),
-- add(3) ]
add
was partially applied to 1
, 2
, and 3
making for three partial applications of add
stuck inside the list. Partially applied meaning the function is waiting on its other parameters. add(1)
for example is waiting on one more number.
[ add(1),
add(2),
add(3) ]
â ď¸ At this point youâre stuck. You canât feed this result back into map
in order to supply more input to the add
s. map
doesnât take partially applied functions â waiting inside an effect/list â so youâll need applicative functor to work with this result.
What are applicative functors?
An applicative functor is a list.
pure(input) = [input]apply(functions)(list) =
[ element | function <- functions,
element <- map(function)(list)
]apply(pure(add1))([1,2,3])
-- [2,3,4]apply(apply(pure(add))([1,2,3]))([4,5,6])
-- [5,6,7,6,7,8,7,8,9]apply(map(add)([1,2,3]))([4,5,6])
-- [5,6,7,6,7,8,7,8,9]
It returns [5, 6, 7, 6, 7, 8, 7, 8, 9]
because it adds 1
to 4
, 5
, and 6
, adds 2
to 4
, 5
, and 6
, and then adds 3
to 4
, 5
, and 6
.
đĄ Instead of thinking about a list as a container for values, think of it as a nondeterministic value or put another way, a choice.
The list applicative functor computes all of the possible combinations or choices when sequencing or composing lists. You can represent this as a tree.
Anyway, notice how I had to use one apply for every parameter add
takes. One for the first parameter and another one for the second parameter.
The pure
and first apply
call wrapped three partially applied add
s inside a list but you can substitute the pure
and one apply
call with map
as using add
with map
also wraps three partially applied add
s inside a list. We saw this up above with functor.
apply(pure(add))([1,2,3])
-- [ add(1),
-- add(2),
-- add(3) ]map(add)([1,2,3])
-- [ add(1),
-- add(2),
-- add(3) ]
đ¤ So far all of the examples had non-empty lists but what happens if any one list is empty?
[ ] `apply` [1,2,3] `apply` [4,5,6]
-- []
[add] `apply` [ ] `apply` [4,5,6]
-- []
[add] `apply` [1,2,3] `apply` [ ]
-- []
You get an empty list. Tracing the implementation, you can see that if any one list is empty, it propagates out. Doesnât matter what the other lists had, youâll always get an empty list if any one of the lists are empty.
If any of the effects are empty or failed, for the list effect and others (like maybe or either), the first occurrence will spill out in the end. This works out nicely as you donât have to consider the empty case when applying lists or even worse, experience a crash because you ended up with an empty list somewhere.
đĄOne of the big aspects of applicative functor is that the effects play out before the lifted/upgraded function can do its thing. Applicative functor is like the Ronco â set it and forget it â showtime rotisserie đ of functional programming patterns. Once weâve kicked off the process, we can only wait for the end result.
Thereâs no picking and choosing whatâs next based on whatâs already occurred. We donât get to look at an intermediate result and make a decision. Itâs been said that applicative functor is batch versus interactive processing as is the case with monads.
Letâs say we have a function called if_x_then_y_else_z
(example adapted from Applicative Programming with Effects, Conor McBride and Ross Paterson. in Journal of Functional Programming 18:1 (2008), pages 1â13).
if_a_then_b_else_c(a)(b)(c) = if a then b else cpure(if_a_then_b_else_c) `apply` x `apply` y `apply` z
It takes in three effects and depending on x
âs result, it either picks the result from y
or z
. Since weâre talking about lists at this point, letâs use the list applicative functor.
if_x_then_y_else_z([True])(["y result"])(["z result"])
-- ["y result"]if_x_then_y_else_z([False])(["y result"])(["z result"])
-- ["z result"]
So if an item of x
is True
, we go with y
âs item else we go with z
âs.
if_x_then_y_else_z([True])(["y result"])(["z result"])
-- ["y result"]
For the inputs
[True]
["y result"]
- and
["z result"]
it doesnât matter what z
holds as itâll never be used. But notice what happens when z
is empty.
if_x_then_y_else_z([True])(["y result"])([])
-- []
You get an empty list even though the z
list isnât needed since x
holds a single True
. But like I wrote up above, if any of the lists are empty, the result will be empty too.
You could try to get around this by lifting/upgrading a function that returns a list.
if_x_then_y_else_z(x)(y)(z) =
apply(pure(\ a -> if a then y else z))(x)if_x_then_y_else_z([True])(["y result"])([])
-- [["y result"]]
â ď¸ But now youâre stuck. You have nested effects or in this case, nested lists. Later on, youâll see how the list monad deals with this situation.
đĄThe big thing to remember with applicative functor is that the lifted/upgraded function cannot change/influence the shape of the effect at any time. For list
, the shape would be the size. For maybe
the shape would be just
or nothing
. For either
the shape would be left
or right
.
Without running the applicative functor, you can determine the resulting shape just by looking at the shape of the effects given. Given an empty list? Youâre going to get an empty list. Given [1,2]
and [3,4,5]
? Youâre going to get back a list with six items.
With monads itâs different. They can change the shape because they can look at the intermediate results and decide what to do next.
An applicative functor is a function.
pure(function)(ignore) = functionpure(add1)(Nothing)(0)
-- 1apply(wrapped_function)(function)(input) =
map(wrapped_function(input))(function)(input)apply(pure(add1))(add1)(1)
-- 3(pure(add) `apply` add1 `apply` add1)(1)
-- 4
The pure
definition for functions is just the constant (const
) function. The constant function takes two parameters and returns the first one ignoring the second one. So calling pure(add1)(Nothing)
wraps and then unwraps add1
. You can unwrap using any input youâd like as constant will just ignore it.
pure(add1)("Ignore this?")(1)
-- 2pure(add1)(concat ["Ignore"," this?"])(1)
-- 2
Notice how I chained as many calls to apply
as how ever many parameters the function took. I used one apply
for add1
and two calls of apply
for add
.
add_x_y_z(x)(y)(z) = add(add(x)(y))(z)add_x_y_z(2)(2)(2)
-- 6(pure(add_x_y_z)
`apply` add1
`apply` add1
`apply` add1)(1)
-- 6
The three calls to apply
â one for x
, one for y
, and one for z
â returns 6
because
1
gets added to1
makingx
equal2
1
gets added to1
makingy
equal2
1
gets added to1
makingz
equal2
- and finally
x
,y
, andz
get added together for a total of6
.
apply(pure(add_x_y_z))(add1)(1)(2)(3)
-- 7add_x_y_z(add1)(2)(3)
-- 7
You donât have to chain a call to apply
for every argument. In this example, I modified the x
parameter with add1
but passed 2
as y
and 3
as z
directly to add_x_y_z
.
add1(input) = 1 + input
sub2(input) = 2 - input
sub3(input) = 3 - input
(pure(add_x_y_z) `apply` add1 `apply` sub2 `apply` sub3)(4)
-- 2add1(4) + sub2(4) + sub3(4)
-- 2(1 + 4) + (2 - 4) + (3 - 4)
-- 2
If you do modify every parameter with apply
, the resulting function will only accept one input which will go to every modified parameter. In this example, 2
went to every modified parameter of add_x_y_z
.
first = \ (a,b,c) -> a
second = \ (a,b,c) -> b
third = \ (a,b,c) -> c new_add_x_y_z =
pure(add_x_y_z) `apply` first `apply` second `apply` third new_add_x_y_z(1,2,3)
-- 6
If the form of your input data doesnât match the parameter signature of your function, using apply to compose your function with some other functions comes in handy. In this example, the data was in a tuple so I composed add_x_y_z
with first
, second
, and third
to extract and then add together 1
, 2
, and 3
.
extract_then_add_x_y_z(t) =
add_x_y_z(first(t))(second(t))(third(t)) extract_then_add_x_y_z(1,2,3)
-- 6
Granted, I couldâve done it like the example above but applicative functor gives us a nicely generalized interface for modifying the input to some function with other functions.
first = \ (a,b) -> a
second = \ (a,b) -> bnew_add_x_y_z = pure(add_x_y_z) `apply` first `apply` secondnew_add_x_y_z(1,2)(3)
-- 6
If say, part of my data was in a tuple, I only have to reason about apply
and pure
instead of coming up with a specific function to handle this particular scenario.
An applicative functor is a functor that you lawfully define pure and apply for.
pure
:: input -- Takes some input.
-> effect(input) -- Returns it inside an effect.apply
:: effect(input -> output) -- Takes a function in an effect.
-> effect(input ) -- Takes input in an effect.
-> effect( output) -- Returns output in an effect.
In general, an applicative functor is a functor that you define apply
and pure
for. The apply
function
- takes a function inside an effect
effect(input -> output)
, - takes input for that function inside an effect
effect(input)
, - applies the function to the input,
- and returns the functionâs output inside the effect
effect(output)
.
đĄ Compare it to map and youâll see the subtle change â the function is now in the effect too.
map
:: (input -> output) -- Takes a function.
-> effect(input ) -- Takes input inside an effect.
-> effect( output) -- Returns output inside an effect.apply
:: effect(input -> output) -- Takes a function in an effect.
-> effect(input ) -- Takes input in an effect.
-> effect( output) -- Returns output in an effect.
đ Look again but in this condensed form.
map :: (i -> o) -> e(i) -> e(o)
apply :: e(i -> o) -> e(i) -> e(o)
-- ^ The big difference.
Thatâs the main and only difference between apply
and map
.
Lawfully, meaning applicative functor has laws too?
Yes. There are four applicative functor laws.
So applicative functor is an interface too?
Yes. If there are lawful definitions of pure
and apply
for some functor, it is an instance of applicative functor. In other words, applicative functor extends the functor interface with pure
and apply
.
I still donât get applicative functor.
Applicative functor picks up where functor leaves off. Functor lifts/upgrades a function making it capable of operating on a single effect. Applicative functor allows the sequencing of multiple independent effects. Functor deals with one effect while applicative functor can deal with multiple independent effects. In other words, applicative functor generalizes functor.
In terms of an interface, applicative functor adds pure
and apply
to functorâs map
.
What do you mean by independent effects?
Independent meaning some effect does not rely on the outcome of some other effect.
x = [1,2]
y = [3,4]
z = [5,6]pure(add) `apply` (pure(add) `apply` x `apply` y) `apply` z
-- [9,10,10,11,10,11,11,12]
In this example, we sequence three effects or lists. Neither x
, y
, nor z
rely on the outcome of any other. Since theyâre all independent, we could process the final result in parallel or simultaneously. This is one of the advantages to applicative functor.
[ 1+3+5=09,
1+3+6=10,
1+4+5=10,
1+4+6=11,
2+3+5=10,
2+3+6=11,
2+4+5=11,
2+4+6=12 ]
Now say we lift/upgrade the following function for the list instance.
add1_inside_effect(input) = pure(input + 1)
It will return a dependent effect/list dependent on some item input
from some other list.
pure(add1_inside_an_effect) `apply` x
-- [[2],[3]]pure(add) `apply` (pure(add1_inside_an_effect) `apply` x)
-- error!
Applicative functor only deals with independent effects. If you have a dependent effect, youâll need monads as youâll see next.
What is a monad?
A monad is a list.
join(nested_lists) =
[element | list <- nested_lists, element <- list]join([[1], [2, 3], [4, 5, 6]])
-- [1, 2, 3, 4, 5, 6]join(apply(pure(\ x -> [x + 1, 1]), [1,2,3]))
-- [2, 1, 3, 1, 4, 1]join(
apply(
pure(\ x -> [2, x * 3, x * 4]))(
join(
apply(
pure(\ x -> [x + 1, 1]))(
[1,2,3]))))
-- [2,6,8,2,3,4,2,9,12,2,3,4,2,12,16,2,3,4]
The big and only addition for the monad interface is join
. join
flattens or better put, peels off an effect from two nested effects. For the list instance, join
is just concat
.
Join only deals with nested effects that are the same type and nested one level deep. So for example, [[1], [3,4]]
, just(just(1))
, right(right(2))
but not [just(1), just(left(1))]
, [1, [2]]
, [[[1]]]
, etc.
if_x_then_y_else_z =
pure(\ a -> if a then y else z) `apply` xif_x_then_y_else_z([True])(["y result"])([])
-- [["y result"]]
When we previously lifted/upgraded a function that returned a list (to get around z
being empty), we ended up with a nested list. Back then we were stuck but join
allows us to keep on truckinâ đ. Now you can pick either the y
or z
list based on the items in x
and not end up with a nested list.
if_x_then_y_else_z(x)(y)(z) =
join(pure(\ a -> if a then y else z) `apply` x)if_x_then_y_else_z(
[True,False,False,True])(
["y result"])(
["z result"])
-- [ "y result", True so return y.
-- "z result", False so return z.
-- "z result", False so return z.
-- "y result" ] True so return y.
đĄ Therein lies the power of monads, they allow us to pick the next effect based on the result from a previous effect. In other words, monad allows us to sequence dependent effects or better put, sequence functions that return effects.
â ď¸ Note that âpick the next effectâ doesnât mean you can, at some point, change the type of effect you return. For example, you canât suddenly decide to return a maybe if youâre operating in the list monad. A monad is also a functor, so in the end, the effect must remain intact with only its results potentially altered.
A monad is a function.
join(wrapped_function)(input) =
wrapped_function(input)(input)join(apply(pure(add))(add1))(1)
-- 3add(add(1)(1))(1)
-- 3add(2)(1)
-- 3(1 + 1) + 1
-- 3divide(numerator)(denominator) = numerator / denominatorjoin(
apply(
pure(divide))(
join(
apply(
pure(add))(
add1))))(2)
-- 2.5divide(
add(
add(
1)(
2))(
2))(
2)
-- 2.5divide(
add(
3)(
2))(
2)
-- 2.5divide(
5)(
2)
-- 2.5(((1 + 2) + 2) / 2)
-- 2.5
The function monad allows you to sequence or compose functions together. The first function in the chain can only take one parameter and the rest have to take two parameters.
After youâre done composing functions together using join
, apply
, and pure
, the returned function will only take one input. This single input will start a chain reaction.
- The first function takes only one parameter.
- It receives the input and returns some output.
- The second function takes two parameters.
- It receives the output from function one, the input, and returns some output.
- The third function takes two parameters.
- It receives the output from function two, the input, and returns some output.
- The fourth functionâŚand on and on for how ever many functions you sequenced.
A monad is an applicative functor that you lawfully define join for.
join
:: effect(effect(data))
-> effect(data)
In general, a monad is just an applicative functor you define join
for.
So monads are just applicative functors with join added to the interface?
Yes.
How many laws are there for monad?
There are three monad laws.
What about return and bind?
Bind is just a convenience function that composes join
, apply
, and pure
.
bind(effect)(function) = join(apply(pure(function))(effect))[1,2,3] `bind` \ x -> [x + 2]
-- [3,4,5][1,2,3] `bind` \ x -> [x + 2] `bind` \ y -> [y * 3]
-- [9,12,15][1,2,3] `bind` \ x -> pure(x + 2) `bind` \ y -> [y * 3, 0]
-- [9,0,12,0,15,0](add1 `bind` add `bind` divide)(2)
-- 2.5
Or if you prefer, bind
composes join
and map
together.
bind(effect)(function) = join(map(function)(effect))[1,2,3] `bind` \ x -> pure(x + 2) `bind` \ y -> [y * 3, 0]
-- [9,0,12,0,15,0]
return
is just pure
.
return = pure
Can you summarize them for me?
- Functor lifts or upgrades a function, allowing it to operate on a single effect, leaving the effect intact after itâs done. It requires a lawful
map
definition. - Applicative functor builds on or generalizes functor, allowing you to sequence multiple independent effects. It requires a lawful
pure
andapply
definition. - Monad builds on or generalizes applicative functor, allowing you to sequence independent and/or dependent effects. It requires a lawful
join
definition.
map :: (i -> o ) -> e(i) -> e(o) -- Functor
apply :: e(i -> o ) -> e(i) -> e(o) -- Applicative
bind :: (i -> e(o)) -> e(i) -> e(o) -- Monad
^ ^
If youâd like to see what you can do with functors, applicatives, and monads, check out Movie Monad and Gifcurry â two desktop GUI apps created with Haskell, a purely functional programming language.
For more information about functors, applicatives, monads, and other functional programming patterns, take a look at Typeclassopedia by Brent Yorgey.