17

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?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Tomer
  • 1,159
  • 7
  • 15
  • 1
    You are correct — `pure f <*> x` is exactly the same as `fmap f x`. I am sure that there is some reason why `pure` was included in `Applicative`, but I’m not entirely sure why. – bradrn Feb 18 '20 at 06:34
  • 4
    I don't have time for an answer, and not convinced this would make a good or complete one anyway, but one observation: `pure` allows one to use, well, "pure" values in an Applicative computation. While, as you correctly observe, `pure f <*> x` is the same as `f <$> x`, there is no such equivalent for, say, `f <*> x <*> pure y <*> z`. (At least I don't think so.) – Robin Zigmond Feb 18 '20 at 06:58
  • 3
    As another, more theoretical, justification - there is an [alternative formulation](https://wiki.haskell.org/Typeclassopedia#Alternative_formulation) which relates it closely to the important `Monoid` class - in which `pure` corresponds to `Monoid`'s identity element. (This suggests that `Applicative` without `pure` could be interesting, since `Semigroup` - which is a `Monoid` without necessarily having an identity - still is used. Actually, now I think about it, I seem to recall PureScript has exactly such an "Applicative without `pure`" class, although I don't know what it's used for.) – Robin Zigmond Feb 18 '20 at 07:05
  • 2
    @RobinZigmond `fmap (\f' x' z' -> f' x' y z') f <*> x <*> z`, I think. The idea is in the `Applicative` documentation as the law of "interchange". – HTNW Feb 18 '20 at 07:05
  • 3
    @RobinZigmond `Applicative` without `pure` exists as [`Apply` from *semigroupoids*](https://hackage.haskell.org/package/semigroupoids-5.3.4/docs/Data-Functor-Apply.html#g:2). – duplode Feb 18 '20 at 10:44

2 Answers2

21

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.

HTNW
  • 27,182
  • 1
  • 32
  • 60
8

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736