5

I'm trying to understand some Haskell source code, and I encountered this structure some times:

x <*> y <$> z

e.g.

(+) <*> (+1) <$> a

Can somebody explain this structure to me? I get that it translates to fmap a (+ a + 1), but I can't make the connection

hgiesel
  • 5,430
  • 2
  • 29
  • 56

2 Answers2

4

Let's start with:

x <*> y <$> z

Adding parentheses, it becomes:

(x <*> y) <$> z

Given that (<$>) :: Functor f => (a -> b) -> f a -> f b, we have:

x <*> y :: a -> b
z :: Functor f => f a

Given that (<*>) :: Applicative g => g (c -> d) -> g c -> g d, we have:

x :: Applicative g => g (c -> d)
y :: Applicative g => g c
x <*> y :: Applicative g => g d

Combining the last few results, we get:

g d ~ a -> b
g ~ (->) a
d ~ b

x :: a -> c -> b
y :: a -> c
x <*> y :: a -> b

Therefore:

(\x y z -> x <*> y <$> z) :: Functor f => (a -> c -> b) -> (a -> c) -> f a -> f b

Now knowing that (<*>) from the function instance is being used, we can also substitute its definition:

x <*> y <$> z
(\r -> x r (y r)) <$> z

In your example, x = (+), y = (+1) and z = a, so we get...

(\r -> r + (r + 1)) <$> a

... which adds each value in a to itself plus one:

GHCi> (+) <*> (+1) <$> [0..3]
[1,3,5,7]
GHCi> ((+) <*> (+1) <$> (*5)) 2
21
duplode
  • 33,731
  • 7
  • 79
  • 150
  • 1
    Needless to say, but don't actually do this. It is *really* bad style because it looks like one thing, but is actually another. Rule of thumb is to never implicitly use a Reader instance. – Lazersmoke Apr 15 '17 at 17:41
  • @Lazersmoke Agreed. Using `<*>` as an S combinator, exploiting its `(->) a` instance, can be quite confusing. – chi Apr 15 '17 at 18:14
  • 2
    @Lazersmoke I do think there are a few legitimate uses for `Applicative ((->) r)` (in particular, `op <$> f <*> g` doesn't bother me if the fact that `f` and `g` are functions is either very obvious or irrelevant in context). In any case, the example we are discussing is not one of them. (I agree with chi in that using a standalone `(<*>)` as the S combinator tends to be confusing.) – duplode Apr 15 '17 at 18:31
3

So in x <*> y <$> z, i.e. fmap (x<*>y) z, you apply the function x<*>y over the functor-value z. The <*> actually knows nothing about the fmapping – the two operators work on completely separate functors! That the first important thing to realise here.

The next is that, if the result of x<*>y is a function, then the Applicative instance of the <*> is actually the function functor. I wish people would stop using that so much, because it's really one of the more confusing instances and generally not the nicest abstraction to choose.

Concretely, f<*>g is just a clever way of composing the functions f and g while also passing the initial input directly to f. It works thus:

(<*>) :: (f ~ (x->))
     => f (a -> b) -> f a -> f b

i.e.

(<*>) :: (x ->(a -> b)) -> (x -> a) -> (x -> b)
       ≡ (x -> a -> b)  -> (x -> a) ->  x -> b
(f <*> g) x = f x $ g x

In terms of data flow, it's this operation:

────┬─────▶ f ──▶
    │       │
    └─▶ g ──┘

I would rather express this with arrow combinators:

     ┌───id──┐
────&&&     uncurry f ──▶
     └─▶ g ──┘

so f<*>g ≡ id &&& g >>> uncurry f. Granted, that is nowhere as compact, in fact more verbose than the explicit lambda version \x -> f x $ g x, which frankly would probably be the best. However, the arrow version is the most general version of the three and arguably expresses best what's going on. The main reason it's so verbose is that currying does not work in out favour here; we might define an operator

(≻>>) :: (x->(a,b)) -> (a->b->c) -> x -> c
g≻>>h = uncurry h . g

and then

         x <*> y <$> z
≡ fmap (id &&& y ≻>> x) z
≡ fmap (\ξ -> x ξ $ y ξ) z

For the example, we have

   (+) <*> (+1) <$> a
≡ fmap (id &&& (+1) ≻>> (+)) z
≡ fmap (\x -> 1 + x+1) z
≡ fmap (+2) z

I first misread your question. The pattern <$> <*> much more common than your <*> <$>, the following adresses that... perhaps useful for other people.

f <$> y <*> z can also be written liftA2 f y z, and liftA2 is IMO rather easier to understand than the equivalent <*>.

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

What it does is, it takes a combiner-function on values and produces from it a combiner-function on containers. It's a bit similar to zipWith except for the list instance, it not only combines each element in the a list with the corresponding element in the b list, but combines each element in the a list once with all elements in the b list, and concatenates the results.

Prelude> Control.Applicative.liftA2 (+) [0,10..30] [0..3]
[0,1,2,3,10,11,12,13,20,21,22,23,30,31,32,33]
Community
  • 1
  • 1
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • 4
    Note that the OP's expression isn't `x <$> y <*> z` (which is equivalent to `liftA2 x y z`), but rather `x <*> y <$> z`, which is far more confusing. – duplode Apr 15 '17 at 16:32
  • 1
    Even though the answer is wrong, I still find it very helpful. Given an edit that acknowledges it, I would still up vote it – hgiesel Apr 15 '17 at 17:11
  • You'll probably be happy to hear that `liftA2` will become an `Applicative` method in the upcoming base library release, and `Traversable` deriving will use it as appropriate. Unfortunately, `ApplicativeDo` desugaring is a whole other level of tricky, so I haven't tried incorporating it there. – dfeuer Apr 16 '17 at 16:58