Tip: How to do andThen2

You know how many packages have map, map2, map3, etc functions? For example Maybe, Result and Json.Decode.

Such packages usually also have an andThen function.

Have you ever needed andThen2? I did the other day, and learned how to do it.

Here’s the trick:

  1. Use map2 as if it was andThen2. (Pretend it is.)
  2. Pipe to andThen identity.

So how does that work? map2 will give a nested structure. andThen identity flattens it.

On to an example. Array.get returns a Maybe. Since I do that inside a map (map2), we end up with a Maybe inside a Maybe:

x : Maybe (Maybe Int)
x =
    Maybe.map2
        (\one two -> Array.get (one + two) (Array.fromList [ 1, 2, 3, 4 ]))
        (String.toInt "1")
        (String.toInt "2")

Maybe.andThen identity flattens it:

x : Maybe Int
x =
    Maybe.map2
        (\one two -> Array.get (one + two) (Array.fromList [ 1, 2, 3, 4 ]))
        (String.toInt "1")
        (String.toInt "2")
        |> Maybe.andThen identity

Why? Because the function you give to Maybe.andThen takes something, and must return a new Maybe. But unlike map, andThen does not produce a nested structure. It flattens. (It’s sometimes called flatMap in other languages.)

In the above example, andThen’s function will receive a Maybe Int as input. Since I used identity as that function, it will return the same Maybe Int. And that’s a Maybe, just like Maybe.andThen requires us to return. And now andThen will flatten for us, like it always does.

This trick works for any container kind of type that has map2 and andThen. It also works for map3 and up :slight_smile:

I thought this was a really neat trick for the times you end up needing andThen2, and I learned a lot from it. Hope this can be useful to somebody else too!

15 Likes

Neat trick!
You can also find Maybe.andMap which is part of maybe-extra package to be useful:

func = \a b c -> Array.get (a + b + c) (Array.fromList [ 1, 2, 3, 4 ])
otherStuff = 
    Just func 
        |> andMap (String.toInt "1" ) 
        |> andMap (String.toInt "1") 
        |> andMap (String.toInt "1") 
        |> andThen identity
        

With andMap you can have an any arity function inside of a container and use the pattern above to apply it.

1 Like

Interesting. You can define it too, for reference. :slight_smile:

andThen2 : (a -> b -> Maybe c) -> Maybe a -> Maybe b -> Maybe c
andThen2 f a b =
    Maybe.andThen identity (Maybe.map2 f a b) 
1 Like

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