I'm learning Haskell's Applicatives. It seems to me (I'm probably wrong) that the pure
function is not really needed, for example:
pure (+) <*> [1,2,3] <*> [3,4,5]
can be written as
(+) <$> [1,2,3] <*> [3,4,5]
Can someone explain the benefit that the pure
function provides over explicit mapping with fmap
?
A function is called pure if it corresponds to a function in the mathematical sense: it associates each possible input value with an output value, and does nothing else.
In Haskell, an applicative is a parametrized type that we think of as being a container for data of that type plus two methods pure and <*> . Consider a parametrized type f a . The pure method for an applicative of type f has type. pure :: a -> f a. and can be thought of as bringing values into the applicative.
Functor in Haskell is a kind of functional representation of different Types which can be mapped over. It is a high level concept of implementing polymorphism. According to Haskell developers, all the Types such as List, Map, Tree, etc. are the instance of the Haskell Functor.
fmap
doesn't always cut it. Specifically, pure
is what lets you introduce f
(where f
is Applicative
) when you don't already have it. A good example is
sequence :: Applicative f => [f a] -> f [a]
It takes a list of "actions" producing values and turns it into an action producing a list of values. What happens when there are no actions in the list? The only sane result is an action that produces no values:
sequence [] = pure [] -- no way to express this with an fmap
-- for completeness
sequence ((:) x xs) = (:) <$> x <*> sequence xs
If you didn't have pure
, you'd be forced to require a nonempty list of actions. You could definitely make it work, but it's like talking about addition without mentioning 0 or multiplication without 1 (as others have said, because Applicative
s are monoidal). You will repeatedly run into edge cases that would be easily solved with pure
but instead have to be solved by weird restrictions on your inputs and other band-aids.
I'm at the edge of my competency here, so don't take this for more than it is, but it was a bit too long for a comment.
There may be practical reasons to include pure
in the type class, but many Haskell abstractions are derived from theoretical foundations, and I believe that that's the case for Applicative
as well. As the documentation says, it's a strong lax monoidal functor (see https://cstheory.stackexchange.com/q/12412/56098 for a elaboration). I suppose that pure
serves as the identity, just like return
does for Monad
(which is a monoid in the category of endofunctors).
Consider pure
and liftA2
:
pure :: a -> f a
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
If you squint a little, you may be able to imagine that liftA2
is a binary operation, which is also what the documentation states:
Lift a binary function to actions.
pure
, then, is the corresponding identity.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With