Reputation: 4609
I looked up the implementation and it's even more mysterious:
-- | Sequence actions, discarding the value of the first argument.
(*>) :: f a -> f b -> f b
a1 *> a2 = (id <$ a1) <*> a2
-- This is essentially the same as liftA2 (flip const), but if the
-- Functor instance has an optimized (<$), it may be better to use
-- that instead. Before liftA2 became a method, this definition
-- was strictly better, but now it depends on the functor. For a
-- functor supporting a sharing-enhancing (<$), this definition
-- may reduce allocation by preventing a1 from ever being fully
-- realized. In an implementation with a boring (<$) but an optimizing
-- liftA2, it would likely be better to define (*>) using liftA2.
-- | Sequence actions, discarding the value of the second argument.
(<*) :: f a -> f b -> f a
(<*) = liftA2 const
I don't even understand why does <$
deserve a place in a typeclass. It looks like there is some sharing-enhancig effect which fmap . const
might not have and that a1
might not be "fully realized". How is that related to the meaning of Applicative
sequencing operators?
Upvotes: 3
Views: 312
Reputation: 15605
These operators sequence two applicative actions and provide the result of the action that the arrow points to. For example,
> Just 1 *> Just 2
Just 2
> Just 1 <* Just 2
Just 1
Another example in writing parser combinators is
brackets p = char '(' *> p <* char ')'
which will be a parser that matches p
contained in brackets and gives the result of parsing p
.
In fact, (*>)
is the same as (>>)
but only requires an Applicative
constraint instead of a Monad
constraint.
I don't even understand why does
<$
deserve a place in a typeclass.
The answer is given by the Functor documentation: (<$)
can sometimes have more efficient implementations than its default, which is fmap . const
.
How is that related to the meaning of Applicative sequencing operators?
In cases where (<$)
is more efficient, you want to maintain that efficiency in the definition of (*>)
.
Upvotes: 7