Looking for simple yet practical examples of using monads in Elm

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

1 Like

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.

6 Likes

@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 :slight_smile:

–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.

3 Likes

Oversimplifying a bit, every time you chain functions with andThen, you’re using monads :tada:. “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 :slightly_smiling_face:

6 Likes

Practical Examples

In day-to-day Elm, I most commonly use andThen in the following scenarios:

  • Cleaning up logic involving Maybe values getting returned along the way in a chain of functions
  • Decoding JSON differently based on the value of a given field (e.g. “role”)
  • Chaining multiple HTTP requests such that only the final result come through my update
  • Less frequently, generating dependent rolls in a random generator

Chaining functions that return Maybe

@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

Conditionally decoding based on a field

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)
    )

Chaining dependent random rolls

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 :smile:

nameGenerator : Generator String
nameGenerator =
  genderGenerator
    |> Random.andThen (\gender ->
      case gender of
        Female -> femaleNameGenerator
        Male -> maleNameGenerator
    )

Chaining HTTP requests

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.

8 Likes

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.
2 Likes

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!

Implementing this for Maybe

As you can recall, Maybe is defined this way:

type Maybe a
    = Nothing
    | Just a

wrap

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 :slight_smile:

andThen

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

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

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 :slight_smile: )

  • map is called fmap
  • unwrap is called join
  • andThen is called bind or >>=
  • wrap is called return or pure
7 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.