'Why type of pure is a -> f a, not (a -> b) -> f (a -> b) on Applicative?
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?
Solution 1:[1]
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.
Solution 2:[2]
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.)
Solution 3:[3]
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?
Solution 4:[4]
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.)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | Daniel Wagner |
