Search code examples
listhaskelloption-typemonads

Accumulate a Function Over a List of Monads (Maybe)


I'm learning Haskell as a way of getting to grips with a new programming paradigm. I'm getting hung up on a specific, probably basic, issue. While I'm finding lots of documentation and other stack overflow questions, I feel like I am getting lost in the noise of other people's examples when trying to figure out how to get this working.

Critically, I'm not Just (aha) looking to get this working, I am also looking to understand the abstraction, which hitherto I'm definitely failing to do.

Let us say we have a list of Ints

x = [1, 2, 5]

To get the product of these Ints, I know that I can do:

result = foldr (*) 1 x

In the case where this list instead is of Maybe Ints, I'm trying to work out how to do something like

x = [Just 1, Just 2, Just 5, Nothing]

res_from_x = <magic_fold> (*) 1 x
-- res from x is Nothing

y = [Just 2, Just 3]
res_from_y = <magic_fold> (*) 1 y
-- res from y is 6

I'm sure there is a straightforward and idiomatic abstraction to this, but I've not managed to figure out how to manipulate fold or use foldM to meet this end.


Solution

  • You don't need any magic fold, you just need a suitable operator to fold with. Note that your problem doesn't really have anything to do with lists, you could just as well ask for

    x₀ = Just 5
    x₁ = Nothing
    res_from_x = <magic_combo> (*) x₀ x₁
    -- result is `Nothing`
    
    y₀ = Just 2
    y₁ = Just 3
    res_from_y = <magic_combo> (*) y₀ y₁
    -- result is `Just 6`
    

    So you need some combinator that takes an operation Int -> Int -> Int and lifts it to an operation Maybe Int -> Maybe Int -> Maybe Int. Notice further that this combinator shouldn't actually require the operands to have type Int or any other particular type, because only the operation should be dealing with the actual values. So the type we're looking for seems to be

        (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
    

    Let's ask Hoogle about that, and it'll come back with

    liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

    ...using the fact that Maybe is an instance of the Applicative class.

    (Of course, the fact that liftA2 has the correct type doesn't prove that it actually does the right thing; you should always read the documentation and run some simple tests before using something that Hoogle suggests! But, in this case the first hit on Hoogle is indeed exactly what you should use.)