Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The need for pure in Applicatives

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?

like image 953
Tomer Avatar asked Feb 18 '20 06:02

Tomer


People also ask

What does pure do in Haskell?

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.

What is an applicative in Haskell?

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.

What is a functor in Haskell?

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.


2 Answers

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 Applicatives 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.

like image 165
HTNW Avatar answered Oct 10 '22 04:10

HTNW


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.

like image 8
Mark Seemann Avatar answered Oct 10 '22 04:10

Mark Seemann