[英]Why type of pure is a -> f a, not (a -> b) -> f (a -> b) on Applicative?
Pure用于在Applicative
容器中将普通的function转化为function。 有了这个,任何多参数操作都可以在Applicative
上使用。 在这种情况下, pure 不希望成为a -> fa
类型,它只希望成为(a -> b) -> f (a -> b)
类型。 但是pure
的类型是a -> fa
。 为什么要将 normal values 转化为Applicative
? use pure
more 比转换函数有更多的目的吗?
我没有发现应用提升函数(即(<*>)
)的Applicative
接口是一个很好的直觉。 由于各种原因,函数的概念化更为复杂。
我更喜欢将Applicative
视为提升n元 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)
其中liftA0 = pure
和liftA
已经作为根据Applicative
定义的fmap
存在。
问题是 0 元和 1 元提升
liftA0 @f @a :: Applicative f => a -> f a
liftA @f @a @b :: Applicative f => (a -> b) -> (f a -> f b)
如果我们在 function 类型中实例化liftA0 = pure
,都可以采用a -> b
function 类型:
liftA0 @f @(a->b) :: Applicative f => (a -> b) -> f (a->b)
liftA @f @a @b :: Applicative f => (a -> b) -> (f a -> f b)
所以pure @f @(a->b)
已经有那个类型了。
pure
有很多目的,理论上在 Haskell 中被证明是实用的,如果Applicative
被视为自然变换类别中的Monoid
,它是单位,与(计算概念作为 Monoids )和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
我刚刚发布了一个适用于Applicative
同态的库,应用同态是尊重应用结构的多态函数。 它为此类结构定义了类型 class
type Idiom :: k -> (Type -> Type) -> (Type -> Type) -> Constraint
class (Applicative f, Applicative g) => Idiom tag f g where
idiom :: f ~> g
其中pure
是初始应用态射。
-- 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
作为计算单位。 它是 Haskell 成功案例之一的Traversable
的推动力
instance Traversable [] where
traverse :: Applicative f => (a -> f b) -> ([a] -> f [b])
traverse f [] = pure []
traverse f (x:xs) = ...
我们需要pure
是因为我们唯一产生Applicative
-action 的参数是fx
但对于一个空列表,我们没有x:: a
来提供它。 因此,我们需要 0 元提升。
您可以根据lift:: (a -> b) -> f (a -> b)
和<*>
定义pure:: a -> fa
:
pure x = lift (const x) <*> lift (const ())
所以这两种方式都是等价的,而且通常写成pure
更简单。
(这是 Iceland_jack 对设计原因的出色总结的补充,它应该是这样的。)
有时从相反的方向来探讨问题是值得的。 如果pure
的类型是(a -> b) -> f (a -> b)
你会得到什么?
作为调用pure
的人,这完全是降级。 如前所述, (a -> b) -> f (a -> b)
已经是当前pure
类型的实例化。 所以在跟注方,你只会失去选择权。
不过,实现方面是双重的。 类型越具体,实现的选项就越多。 要求参数为 function 意味着实现可以利用它来做特定于功能的事情。 就像...称呼它。 这是您可以使用 Haskell 中的函数做的唯一特殊事情。因此,要调用它,您只需要为其提供某种类型的值a
, pure
的调用者可以选择该类型。 你可以通过.. 呃.. 你不能得到其中之一。 唯一的选择是使用undefined
或其他一些具有通用量化类型的底值。 你会怎样做? pure f = let x = f undefined in...
? 这对实施 pure 有何帮助?
回到最初的问题,那么:如果pure
的类型是(a -> b) -> f (a -> b)
你会得到什么? 作为调用者,它的用处不大。 作为一个实施者,它提供了额外的力量,但这种额外的力量并不能帮助你做任何有用的事情。 更具体的类型的好处在哪里?
是的, pure
函数比转换函数有更多的用途。 do
块以调用pure
结束是很常见的。 通过将它的使用与<|>
结合使用,它也可以方便地提供回退值。
此外,它与基础范畴论非常吻合; 但我真的不认为这是一个激励原因。 相反,它首先有用,然后被发现与先前已知的范畴论概念相吻合。 (实际上,从历史上看,我认为它是“定义一些有用的东西;意识到它是 monad 的概念;发现应用函子的相关概念;意识到它们是有用的。所以它实际上是“有用第一”和“理论第一”的混合体”。但我永远不会为它的存在辩护,因为它在理论上存在——只会为这个理论很有见地而兴奋。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.