繁体   English   中英

为什么在 Applicative 上 pure 的类型是 a -> fa,而不是 (a -> b) -> f (a -> b)?

[英]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 = pureliftA已经作为根据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 中的函数做的唯一特殊事情。因此,要调用它,您只需要为其提供某种类型的值apure的调用者可以选择该类型。 你可以通过.. 呃.. 你不能得到其中之一。 唯一的选择是使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM