Some Haskell source code (see ref):
-- | Sequential application.
--
-- A few functors support an implementation of '<*>' that is more
-- efficient than the default one.
(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id
-- | Lift a binary function to actions.
--
-- Some functors support an implementation of 'liftA2' that is more
-- efficient than the default one. In particular, if 'fmap' is an
-- expensive operation, it is likely better to use 'liftA2' than to
-- 'fmap' over the structure and then use '<*>'.
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)
Three things seem to be quite confusing to me:
1) (<*>)
is defined in terms of liftA2
, where liftA2
is defined in terms of (<*>)
. How does it work? I see no obvious "recursion-break" case...
2) id
is an a -> a
function. Why is it passed into liftA2
as an (a -> b -> c)
function?
3) fmap id x
always equals x
, since functors must preserve appropriate identities. Thus (<*>) (fmap id x)
= (<*>) (x)
where x
= f a
- an a
-typed functor itself (by the way, how can a
-typifying of the functor can be explained from the pure category theory's point of view? functor is just a mapping between categories, it has no further "typification"... seems like it is better to say - "a container of type a
with an (endo)functor defined for each instance of asummed category Hask
of well-defined Haskell types). So (<*>) (f a)
while by definition (<*>)
expects f(a' -> b')
: thus, the only way to make it work is to deliberately bound a
to be an (a' -> b')
. However when I run :t \x -> (<*>) (fmap id x)
in the gchi
, it spits out something mind-blowing: f (a -> b) -> f a -> f b
- which I fail to explain.
Can someone step by step explain how does that work and why it even compiles? P.S. Category theory terms, if needed, are welcome.