user5536315
user5536315

Reputation:

How to combine two composed applicative functors?

I have two composed applicative functors Maybe [Integer] and want to combine them with <$>/<*> but I am stuck with applying the applicative operation. The following does not typecheck:

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

Expected result:

Just [5,6,7,6,7,8,7,8,9]

The functor part works, i.e. the intermediate value passed to <*> as the first argument is Just [Integer -> Integer]. I am used to S-expressions so I have a hard time with the Haskell syntax. I know of Compose but I am interested in the mere composition wihtout abstraction.

Upvotes: 1

Views: 770

Answers (4)

David Young
David Young

Reputation: 10793

The composition of two Applicatives is always an Applicative (unlike the case for Monad).

We can use this to our advantage here with the Compose newtype from Data.Functor.Compose:

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

It requires a bit of wrapping, but this kind of solution could be useful under the right circumstances:

example :: Maybe [Int]
example =
  getCompose ((+) <$> Compose (Just [1,2,3]) <*> Compose (Just [4,5,6]))

Upvotes: 2

Redu
Redu

Reputation: 26191

One other way could be to use the ListT transformer. While it works just fine in this case, for some reason it's a depreciated transformer, marked in red with "Deprecated: This transformer is invalid on most monads".

import Control.Monad.Trans.List

doit :: (Int-> Int -> Int) -> Maybe [Int] -> Maybe [Int] -> Maybe [Int]
doit f mt1 mt2 = runListT $ f <$> (ListT mt1) <*> (ListT mt2)

λ> doit (+) (Just [1,2,3]) (Just [4,5,6])
Just [5,6,7,6,7,8,7,8,9]

Upvotes: -1

Hjulle
Hjulle

Reputation: 2615

As Li-yao Xia said, using liftA2 makes it a lot less confusing.

But if you still what to see what it becomes in terms of the underlaying operations, we can expand the definition of liftA2:

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x y = f <$> x <*> y

so the solution becomes

(liftA2 . liftA2) (+) (Just [1,2,3]) (Just [4,5,6])
= liftA2 (liftA2 (+)) (Just [1,2,3]) (Just [4,5,6])
= (\f x y -> f <$> x <*> y) ((\f x y -> f <$> x <*> y) (+)) (Just [1,2,3]) (Just [4,5,6])
= ((\f x y -> f <$> x <*> y) (+)) <$> Just [1,2,3] <*> Just [4,5,6]
= (\x y ->  (+) <$> x <*> y) <$> Just [1,2,3] <*> Just [4,5,6]

Now, this is not in point free style like your example above, and I really don't think it's helpful to convert it into point free, but here's the output from http://pointfree.io:

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

we can see that this is the same by eta-expanding:

(<*>) . ((+) <$>)
= \x y -> ((<*>) . ((+) <$>)) x y
= \x y -> ((<*>) $ ((+) <$>) x) y
= \x y -> ((<*>) ((+) <$> x)) y
= \x y -> (<*>) ((+) <$> x) y
= \x y -> ((+) <$> x) <*> y
= \x y -> (+) <$> x <*> y

Upvotes: 3

Li-yao Xia
Li-yao Xia

Reputation: 33519

liftA2 might be less confusing for this than (<*>).

(+) :: Int -> Int -> Int
liftA2 (+) :: [Int] -> [Int] -> [Int]
liftA2 (liftA2 (+)) :: Maybe [Int] -> Maybe [Int] -> Maybe [Int]

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

Upvotes: 3

Related Questions