Search code examples
haskellfunctormonad-transformersapplicative

Nested applicative functors of different types in Haskell


I'd like to make the nested applicative functors of different types. For example, nested simple functors of different types (in ghci) work fine:

Prelude> ((+2) <$>) <$> (Just [1..4])
Just [3,4,5,6]

But for applicative functors of different types:

Prelude> ((*) <$>)  <$> (Just [1,2,3]) <*> (Just [4,5,6,7])

<interactive>:56:1: error:
    * Couldn't match type `[Integer -> Integer]' with `[Integer] -> b'

isn't working! I want to obtain something like this:

Just [4,5,6,7,8,10,12,14,12,15,18,21]

I know that applicative functors have intermediate position between functors and monads. And I can see this exercise as preliminary before topic about monad transformers.


Solution

  • In this case, you want:

    liftA2 (*) <$> Just [1, 2, 3] <*> Just [4, 5, 6, 7]
    

    Or:

    liftA2 (liftA2 (*)) (Just [1, 2, 3]) (Just [4, 5, 6, 7])
    

    The outer … <$> … <*> … or liftA2 operates on Maybe, while the inner one operates on []. If you didn’t know this, you could figure it out by asking GHCi for the type of what you should put there, for example with a typed hole:

    :t _ <$> (Just [1 :: Int, 2, 3]) <*> (Just [4 :: Int, 5, 6, 7]) :: Maybe [Int]
    

    It gives back:

    _ :: [Int] -> [Int] -> [Int]
    

    And the behaviour you want for combining the lists is \ xs ys -> (*) <$> xs <*> ys, which can be abbreviated liftA2 (*). ((*) <$>) or fmap (*) didn’t work because that’s only half of what you need: it operates on a single list (using Functor), while you want to combine two (using Applicative).

    Of course, liftA2 (liftA2 (*)) works on any two nested applicative functors whose elements are numeric:

    (Applicative f, Applicative g, Num a)
      => f (g a) -> f (g a) -> f (g a)
    

    For example, nested lists:

    liftA2 (liftA2 (*)) [[1], [2], [3]] [[4, 5, 6]]
    == [[4,5,6],[8,10,12],[12,15,18]]
    
    -- (Transposing the inputs transposes the output.)
    liftA2 (liftA2 (*)) [[1, 2, 3]] [[4], [5], [6]]
    == [[4,8,12],[5,10,15],[6,12,18]]
    

    Or lists of Maybe:

    liftA2 (liftA2 (*)) [Just 1, Nothing, Just 3] [Just 4, Nothing, Just 6]
    == [Just 4, Nothing, Just 6,
        Nothing, Nothing, Nothing,
        Just 12, Nothing, Just 18]
    

    Or even something more exotic, like lists of functions:

    ($ (3, 5)) <$> (liftA2 (+) <$> [fst, snd] <*> [snd, fst])
    == [fst (3, 5) + snd (3, 5),
        fst (3, 5) + fst (3, 5),
        snd (3, 5) + snd (3, 5),
        snd (3, 5) + fst (3, 5)]
    == [3+5, 3+3, 5+5, 5+3]
    == [8,6,10,8]