For anyone who has trouble accepting that an argument to sequenceA [(+1)]
just magically applies itself to BOTH (+1)
and const []
, this is for you. It was the only sticking point for me after realizing that pure [] = const []
.
sequenceA [(+1)] = (:) <$> (+1) <*> const []
Using lambdas (so we can explicitly show and move things around when we start treating function application like a functor and an applicative):
sequenceA [(+1)] = \b c -> ( (:) b c ) <$> ( \a -> (+1) a ) <*> ( \a -> const [] a )
Both (<$>)
and (<*>)
are infixl 4. Which means we read and evaluate from left to right, i.e. we start with (<$>)
.
Where (<$>) :: Functor f => (a -> b) -> f a -> f b
.
The effect of <$>
is to pluck (+1)
out of it's wrapper ((->) r)
, OR \a ->
from our lambda code, and apply it to \b c -> ( (:) b c )
where it will take the place of b
, then re-apply the wrapper (that's the \a
that appears after the equals sign on the line below):
sequenceA [(+1)] = \a c -> ( (:) ((+1) a) c ) <*> ( \a -> const [] a )
Notice that (:)
is still waiting for an argument c
, and (+1)
is still waiting for an a
. Now, we get to the applicative part.
Remember that: (<*>) :: f (a -> b) -> f a -> f b
. Our f
here is the function application \a ->
.
Both sides now have the same wrapper, namely \a ->
. I'm keeping the a
's in there to remind us where the a
's will be applied later, so it does become a little pseudo-y here. The function application will be connected back up in just a jiffy. Both functions depend on the same a
, precisely because they had the same function application wrapper i.e. an applicative. Without their \a ->
wrappers (thanks to <*>
), it goes like this:
( \c -> ( (:) ((+1) a) c ) ) (const [] a)
= ( (:) ((+1) a) (const [] a) ) -- Ignore those a's, they're placeholders.
Now, the very last thing that <*>
does is to pop this result back into it's wrapper \a ->
:
sequenceA [(+1)] = \a -> ( (:) ((+1) a) (const [] a) )
Sugaring this up a little bit gives:
sequenceA [(+1)] = \a -> (+1) a : const [] a
See! It makes perfect sense that an argument to sequenceA [(+1)]
goes to both (+1)
and const
. Applying a 2, for instance, gives:
sequenceA [(+1)] 2 = (+1) 2 : const [] 2
Remember that const a b :: a -> b -> a
, and therefore just ignores it's input:
sequenceA [(+1)] 2 = 3 : []
OR, more sweetly:
sequenceA [(+1)] 2 = [3]