Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 482
Vladimir Avatar asked Dec 10 '25 08:12

Vladimir


2 Answers

Besides nesting lifts and fmaps, another option to compose applicative functors is the Data.Functor.Compose newtype:

newtype Compose f g a = Compose { getCompose :: f (g a) }

for example:

ghci> let Compose result = (*) <$> Compose (Just [1,2,3]) <*> Compose (Just [4,5,6,7])
ghci> result
Just [4,5,6,7,8,10,12,14,12,15,18,21]

Applicatives are so well-behaved that a single newtype suffices to compose any two types that are instances. And there are other ways to combine them besides nesting, like Product and Day convolution:

data Product f g a = Pair (f a) (g a)

data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)

Monads do not compose as well though, so we need a different newtype for each monad in order to augment some other monad with the first monad's abilities. We call those newtypes monad transformers.

like image 93
danidiaz Avatar answered Dec 13 '25 08:12

danidiaz


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]
like image 29
Jon Purdy Avatar answered Dec 13 '25 07:12

Jon Purdy