1

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.

  • Wow, this is so confusing! Multiple layers of `<*>`/`<$>` is just a recipe for a mess. Please use `liftA2` instead. – Hjulle May 28 '20 at 14:32
  • 1
    Or at least don't try using point free style. This is a lot clearer: `(liftA2 . liftA2) (+) = \a b -> (\a' b' -> (+) <$> a' <*> b') <$> a <*> b` – Hjulle May 28 '20 at 14:38
  • 1
    If you really want it to be point free, here's a version generated by http://pointfree.io `((<*>) . ((+) <$>)) <$> a <*> b` – Hjulle May 28 '20 at 14:41
  • @Hjulle: Both versions the lambda and the point-free one are great to understand the underlying principle. Thanks! –  May 28 '20 at 15:05
  • I've expanded these comments into a more complete answer now – Hjulle May 28 '20 at 15:10
  • I know you've already accepted an answer, but for posterity, could you include the expected result of your attempt in the question? – chepner May 28 '20 at 18:49

4 Answers4

3

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])
Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • Can you still show the underlying composition, because this is what I try to understand in particular. I've only found examples with the `Compose` abstraction so far. –  May 28 '20 at 14:05
  • What do you mean by "the underlying composition"? – Li-yao Xia May 28 '20 at 14:19
  • I meant `(liftA2 . liftA2) (+) = \a b -> (\a' b' -> (+) <$> a' <*> b') <$> a <*> b`. Thank you! –  May 28 '20 at 15:00
3

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
Hjulle
  • 2,471
  • 1
  • 22
  • 34
2

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]))
David Young
  • 10,713
  • 2
  • 33
  • 47
-1

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]
Redu
  • 25,060
  • 6
  • 56
  • 76
  • 1
    I use applicative to escape monad in as much cases as possible but it is still astonishing that it just works. –  May 28 '20 at 16:39
  • @bob I think this is something to keep in mind. Transformers flatten the nesting and allow you to use the `<$>` and `<*>` pattern in it's simplest form. – Redu May 28 '20 at 16:48
  • 2
    A few things worth mentioning: (1) The `ListT` from *transformers* is deprecated because it only works correctly with commutative monads, and is indeed best avoided (even though `Maybe` happens to be commutative). (2) For use cases in which `ListT` is helpful, there are non-broken alternative implementations, such as [this one](http://hackage.haskell.org/package/list-t-1.0.4/docs/ListT.html). (3) In this case, though, I see little reason to use `ListT` when we might just write something very similar with `Compose`: `getCompose $ f <$> Compose mt1 <*> Compose mt2`. – duplode May 28 '20 at 17:19
  • @duplode Thanks... That's great, i didn't know that. – Redu May 28 '20 at 17:21