Pure is used to transform normal function into function in Applicative
container. With this, any multi-parameter operations become can be used on Applicative
. In this context, pure is not desired to be a -> f a
type, it is just desired to be (a -> b) -> f (a -> b)
type. But type of pure
is a -> f a
. Why should normal values can be transformed into Applicative
? Is there more purpose for use pure
more than transforming functions?

- 429
- 3
- 7
-
8`(a -> b) -> f (a -> b)` **is** a possible instantiation of `pure`'s type. And no, `pure` is not generally “used to transform normal function into function in `Applicative` container”, that sounds rather like you're describing `fmap :: (a -> b) -> f a -> f b`. I'd rather say `pure` is used to transform normal _values_ into values in an `Applicative` context. It so happens that functions are values as well, but that's a special case. – leftaroundabout Apr 17 '22 at 14:18
-
What you are probably thinking about is the `pure f` in `fmap f fx = pure f <*> fx`, but the `fx`, where would it come from? `pure x`, that's where, for one. – Will Ness Apr 18 '22 at 17:52
4 Answers
I don't find the Applicative
interface of applying lifted functions (namely, (<*>)
) a good intuition. Functions are more complicated to conceptualize for various reasons.
I prefer thinking of Applicative
as lifting an n-ary function
liftA0 :: Applicative f => (a) -> (f a)
liftA :: Functor f => (a -> b) -> (f a -> f b)
liftA2 :: Applicative f => (a -> b -> c) -> (f a -> f b -> f c)
liftA3 :: Applicative f => (a -> b -> c -> d) -> (f a -> f b -> f c -> f d)
where liftA0 = pure
and liftA
already exists as fmap
defined in terms of Applicative
.
The thing is that 0-ary and 1-ary liftings
liftA0 @f @a :: Applicative f => a -> f a
liftA @f @a @b :: Applicative f => (a -> b) -> (f a -> f b)
can both take an a -> b
function if we instantiate liftA0 = pure
at a function type:
liftA0 @f @(a->b) :: Applicative f => (a -> b) -> f (a->b)
liftA @f @a @b :: Applicative f => (a -> b) -> (f a -> f b)
So pure @f @(a->b)
already has that type.
And pure
has plenty of purposes, theoretical which turn out to be practical in Haskell, it is the unit if Applicative
is viewed as a Monoid
in the category of natural transformations, with (Notions of Computation as Monoids) with Day
type Mempty :: Type -> Type
type Mempty = Identity
type Mappend :: (Type -> Type) -> (Type -> Type) -> (Type -> Type)
type Mappend = Day
mempty :: Applicative f => Mempty ~> f
mempty (Identity a) = pure a
mappend :: Mappend f f ~> f
mappend (LiftA2 (·) as bs) = liftA2 (·) as bs
I just released a library that works with Applicative
homomorphisms, that are polymorphic functions that respect the applicative structure. It defines a type class for such structures
type Idiom :: k -> (Type -> Type) -> (Type -> Type) -> Constraint
class (Applicative f, Applicative g) => Idiom tag f g where
idiom :: f ~> g
where pure
is the initial applicative morphism.
-- https://chrisdone.com/posts/haskell-constraint-trick/
instance (Identity ~ id, Applicative f) => Idiom Initial id f where
idiom :: Identity ~> f
idiom (Identity a) = pure a
pure
is then frequently used, as a unit for a computation. It is the driving force in Traversable
one of the success stories of Haskell
instance Traversable [] where
traverse :: Applicative f => (a -> f b) -> ([a] -> f [b])
traverse f [] = pure []
traverse f (x:xs) = ...
we require pure
because our only argument that produces an Applicative
-action is f x
but with an empty list we don't have an x :: a
to feed it. Thus, we need 0-ary lifting.

- 6,848
- 7
- 37
- 46
You can define pure :: a -> f a
in terms of lift :: (a -> b) -> f (a -> b)
and <*>
:
pure x = lift (const x) <*> lift (const ())
So it's equivalent either way, and usually simpler to write pure
.
(This is in addition to Iceland_jack's excellent summary of the design reasons it should be this way.)

- 191,574
- 25
- 345
- 413
Sometimes it's worth approaching questions from the opposite direction. What would you gain if the type of pure
was (a -> b) -> f (a -> b)
?
As someone calling pure
, that's strictly a downgrade. As mentioned, (a -> b) -> f (a -> b)
is an instantiation of the current type of pure
already. So on the calling side, you only lose options.
The implementation side is the dual here, though. The more concrete a type is, the more options an implementation has. Requiring the argument to be a function means the implementation can take advantage of that to do function-specific things. Like... calling it. That's the only special thing you get to do with functions in Haskell. So to call it, you just need to provide it with a value of some type a
that the caller of pure
gets to choose. You can get one of those by.. uh.. you can't get one of those. The only option is using undefined
or some other bottom value that has a universally-quantified type. What are you going to do? pure f = let x = f undefined in ...
? How can that be helpful for implementing pure?
To return to the initial question, then: what would you gain if the type of pure
was (a -> b) -> f (a -> b)
? As a caller, it's strictly less useful. As an implementer, it provides additional power, but that additional power doesn't help you do anything useful. Where are the upsides to a more concrete type?

- 26,500
- 4
- 65
- 86
Yes, there is more purpose for pure
than transforming functions. It is very common for do
blocks to end with a call to pure
. It's also handy for giving fallback values by combining its use with <|>
.
Also, it meshes well with the underlying category theory; but I don't really consider that a motivating reason. Rather it's first useful and then discovered to coincide with a previously-known category theory concept. (Actually, historically I think it went "define something useful; realize it's the concept of monad; discover the related concept of applicative functors; realize they're useful. So it is in fact a mix of "useful first" and "theory first". But I would never defend its existence because it's there in theory -- only get excited that the theory was insightful.)

- 145,880
- 9
- 220
- 380