traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
Hi,
There are a lot of functions that I can't understand signature. Of course I understan that traverse get two arguments, that first is function. However,
what does mean (a -> f b)
? I can understand (a -> b)
.
Similary, t a
, f (t b)
Could you explain it me ?
traverse
is a type class-ed function so sadly the behaviour of this function depends on what exactly we choose t
to be. This is not dis-similar to >>=
or fmap
. However there are rules for it's behaviour, just like in those cases. The rules are supposed to capture the idea that traverse
takes a function a -> f b
, which is an effectful transformation from a
to b
and lifts it to work on a whole "container" of a
s, collecting the effects of each of the local transformations.
For example, if we have Maybe a
the implementation of traverse
would be
traverse f (Just a) = Just <$> f a
traverse f Nothing = pure Nothing
For lists
traverse f [a1, a2, ...] = (:) <$> f a1 <*> ((:) <$> f a2 <*> ...))
Notice how we're taking advantage of the fact that the "effect" f
is not only a functor, but applicative so we can take two f-ful computations, f a
and f b
and smash them together to get f (a, b)
. Now we want to come up with a few laws explaining that all traverse can do is apply f
to the elements and build the original t a
back up while collecting the effects on the outside. We say that
traverse Identity = Identity -- We don't lose elements
t . traverse f = traverse (t . f) -- For nicely composing t
traverse (Compose . fmap g . f) = Compose . fmap (traverse g) . traverse f
Now this looks quite complicated but all it's doing is clarifying the meaning of "Basically walks around and applies the local transformation". All this boils down to is that while you cannot just read the signature to understand what traverse
does, an OK intuition for the signature is
f :: a -> f b
a
sb
gotten by repeatedly applying f
, ala fmap
f
are accumulated so we get f (t b)
, not just t b
.Remember though, traverse
can get used in some weird ways. For example, the lens package is chock-full of using traverse
with very strange functors to great effect.
As a quick test, can you figure out how to use a legal traverse
to implement fmap
for t
? That is
fmapOverkill :: Traversable f => (a -> b) -> (f a -> f b)
Or headMay
headMay :: Traversable t => t a -> Maybe a
Both of these are results of the fact that traversable instances also satisfy Functor
and Foldable
!