Hello guys,
I’ve recently started learning Elm and have a bit fear on using monads.
Could some of you please be able to share simple yet practical examples of using monads in Elm?
–g
Hello guys,
I’ve recently started learning Elm and have a bit fear on using monads.
Could some of you please be able to share simple yet practical examples of using monads in Elm?
–g
We don’t use that word here. My guess is that a lot of the people in the Elm community are using monads day to day without knowing or at least actively thinking that they are using monads.
Maybe a
is a good example of a monad. You can think about it as a “box” that has something inside the box or maybe the box is empty.
This type is good for operations that can fail where you don’t care about the failure. A good example is getting the first element of a List a
. Because the list can be empty, this is an operation that can fail so, the return type is a box that might contain the first element OR it might be empty… so, you get a Maybe a
.
Now, if you imagine that you have multiple operations that can fail, you need a way to execute all those operations in sequence, this means that you need to chain the operations together.
Imagine that you have a list of strings and you need the first character of the first element in that list.
You could write this as:
firstChar : String -> Maybe Char
firstChar str =
str |> String.toList |> List.head
firstCharOfFirstElement : List String -> Maybe Char
firstCharOfFirstElement list =
list
|> List.head -- first operation that might fail
|> Maybe.andThen firstChar -- second operation that might fail
Maybe this talk can provide some more clarity. It uses F# but that’s not important, what is important is the way of thinking, the way of approaching the problems. The actual code can be easily transcribed to Elm since it uses concepts that are present in Elm too.
@pdamoc, thanks a ton for your time to shed some light.
I’m sorry for my statement in the question but I tried to be honest.
Well, Your explanation with an example is a good tap on my shoulder
–g
That’s perfectly fine. It is a frequently encountered fear. A lot of people are discouraged to go into functional programming because they have seen highly academic language with a lot of specialized terms. You don’t need to learn all the theory in order to solve your problems. It helps but it is not mandatory.
Oversimplifying a bit, every time you chain functions with andThen
, you’re using monads . “Monad” is a generic way of referring to this pattern of chaining with andThen
functions.
Knowledge of “monads” as an abstract pattern is not required to build complex Elm programs.
As you learn Elm and try to build bigger programs, you’ll naturally run into situations that need to be solved with an andThen
function. Eventually, you’ll build an intuition for how to use andThen
and how the various andThen
functions are similar. That’s basically it
In day-to-day Elm, I most commonly use andThen
in the following scenarios:
Maybe
values getting returned along the way in a chain of functionsupdate
@pdamoc already gave an example of chaining Maybes with Maybe.andThen
. It’s a great way to avoid having to do a bunch of extra presence-checks. It’s very common to use Maybe.andThen
in combination with List.head
.
Here’s another, somewhat more complex example. In a social networking application, you want to find where a user’s most popular friend lived the longest. The problem is that everything there could be not-present: The user passed into the function is optional, even if they are present they may not have any friends, and even if they have a most popular friend the friend may not have any residences in the system.
It might sound like all this uncertainty would leave you drowning in nested case
statements but Maybe.andThen
can save you here. Example from this article on problem solving with Maybe.
maybeMostPopularFriendLongestResidence : Maybe User -> Maybe Address
maybeMostPopularFriendLongestResidence maybeUser =
maybeUser
|> Maybe.andThen mostPopularFriend
|> Maybe.andThen longestResidence
You have this user type:
type User = Admin String | RegularUser String
you get user data from the server that you want to decode into one of either Admin
or RegularUser
based on the the value of the “role” field in the JSON. So first you decode the role field, then based on what value was there you choose which decoder to use. Example from this article on 5 common JSON decoding situations.
userDecoder : Decoder User
userDecoder =
Json.Decode.field "role" Json.Decode.string
|> Json.Decode.andThen (\role ->
case string of
"regular" -> regularUserDecoder
"admin" -> adminDecoder
_ -> JD.fail ("Invalid user role: " ++ role)
)
First role a random gender, then based on that result, run either the appropriate gendered name generator. This example comes from my talk Rolling Random Romans where I try to build a generator for historically accurate ancient Roman names
nameGenerator : Generator String
nameGenerator =
genderGenerator
|> Random.andThen (\gender ->
case gender of
Female -> femaleNameGenerator
Male -> maleNameGenerator
)
First make an HTTP request to get the current user’s server ID, then use that ID to make another HTTP request to get the user’s details.
getUser : Task Http.Error User
getUser =
Http.toTask getCurrentUserId
|> Task.andThen (\userId -> Http.toTask (getUserDetails userId))
For some fancier chaining of HTTP requests, check out the solution this discourse thread which uses recursion + Task.andThen
to clean up code to fetch data over a paginated API.
Also, in addition to *.andThen
, there is List.concatMap
which is also monad. If you look at the types, you’ll see it follows the same pattern:
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
List.concatMap : (a -> List b) -> List a -> List b
Maybe.andThen
runs the function on the a
inside the Maybe
, ends up with Maybe (Maybe b)
and unwraps one level to get to Maybe b
.List.concatMap
runs the function on every a
in the List
, ends up with List (List b)
and concatenates all the inner lists to get to List b
.This isn’t needed for day-to-day use, but might help intuition:
What I described above is a possible way to implement a monad:
map : (a -> b) -> MyType a -> MyType b
unwrap : MyType (MyType a) -> MyType a
Combining those gives you
andThen : (a -> MyType a) -> MyType a -> MyType b
andThen fn myType =
myType -- `MyType a`
|> map fn -- gives you `MyType (MyType b)`
|> unwrap -- gives you `MyType b`
You still need something to create MyType
out of an ordinary value:
wrap : a -> MyType a
After you have both wrap
and andThen
, you have a monad!
As you can recall, Maybe
is defined this way:
type Maybe a
= Nothing
| Just a
For defining wrap
we have two options:
wrap1 : a -> Maybe a
wrap1 _ =
Nothing
wrap2 : a -> Maybe a
wrap2 value =
Just value
Which one to use? Well, there are “Monad laws” that say how the functions wrap
and andThen
have to work together. I won’t dive into this, but if you did, you would find wrap2
is the correct one
We can define andThen
ourselves directly:
andThen : (a -> Maybe b) -> Maybe a -> Maybe b
andThen fn maybeValue =
case maybeValue of
Nothing ->
Nothing
Just value ->
fn value
Or, we can go the map + unwrap
way:
andThen : (a -> Maybe b) -> Maybe a -> Maybe b
andThen fn maybeValue =
maybeValue
|> map fn
|> unwrap
with map
and unwrap
defined below:
map : (a -> b) -> Maybe a -> Maybe b
map fn maybeValue =
case maybeValue of
Nothing ->
Nothing
Just value ->
Just (fn value)
Try to find the (two) differences from andThen
!
unwrap : Maybe (Maybe a) -> Maybe a
unwrap nestedMaybe =
case nestedMaybe of
Nothing ->
Nothing
Just maybe ->
maybe
So, these are the building blocks of monads. wrap
and andThen
. Monad is just a type and a set of functions on it that have a certain type and behave a certain way.
Note: In academic literature the names are sometimes a bit different (but I like the Elm ones more )
map
is called fmap
unwrap
is called join
andThen
is called bind
or >>=
wrap
is called return
or pure
This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.