简体   繁体   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 is used to transform normal function into function in Applicative container. Pure用于在Applicative容器中将普通的function转化为function。 With this, any multi-parameter operations become can be used on Applicative .有了这个,任何多参数操作都可以在Applicative上使用。 In this context, pure is not desired to be a -> fa type, it is just desired to be (a -> b) -> f (a -> b) type.在这种情况下, pure 不希望成为a -> fa类型,它只希望成为(a -> b) -> f (a -> b)类型。 But type of pure is a -> fa .但是pure的类型是a -> fa Why should normal values can be transformed into Applicative ?为什么要将 normal values 转化为Applicative Is there more purpose for use pure more than transforming functions? use pure more 比转换函数有更多的目的吗?

I don't find the Applicative interface of applying lifted functions (namely, (<*>) ) a good intuition.我没有发现应用提升函数(即(<*>) )的Applicative接口是一个很好的直觉。 Functions are more complicated to conceptualize for various reasons.由于各种原因,函数的概念化更为复杂。

I prefer thinking of Applicative as lifting an n -ary function我更喜欢将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)

where liftA0 = pure and liftA already exists as fmap defined in terms of Applicative .其中liftA0 = pureliftA已经作为根据Applicative定义的fmap存在。

The thing is that 0-ary and 1-ary liftings问题是 0 元和 1 元提升

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:如果我们在 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)

So pure @f @(a->b) already has that type.所以pure @f @(a->b)已经有那个类型了。

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 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

I just released a library that works with Applicative homomorphisms, that are polymorphic functions that respect the applicative structure.我刚刚发布了一个适用于Applicative同态的库,应用同态是尊重应用结构的多态函数。 It defines a type class for such structures它为此类结构定义了类型 class

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 .其中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 is then frequently used, as a unit for a computation.然后经常使用pure作为计算单位。 It is the driving force in Traversable one of the success stories of Haskell它是 Haskell 成功案例之一的Traversable的推动力

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 fx but with an empty list we don't have an x:: a to feed it.我们需要pure是因为我们唯一产生Applicative -action 的参数是fx但对于一个空列表,我们没有x:: a来提供它。 Thus, we need 0-ary lifting.因此,我们需要 0 元提升。

You can define pure:: a -> fa in terms of lift:: (a -> b) -> f (a -> b) and <*> :您可以根据lift:: (a -> b) -> f (a -> b)<*>定义pure:: a -> fa

pure x = lift (const x) <*> lift (const ())

So it's equivalent either way, and usually simpler to write pure .所以这两种方式都是等价的,而且通常写成pure更简单。

(This is in addition to Iceland_jack's excellent summary of the design reasons it should be this way.) (这是 Iceland_jack 对设计原因的出色总结的补充,它应该是这样的。)

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) ?如果pure的类型是(a -> b) -> f (a -> b)你会得到什么?

As someone calling pure , that's strictly a downgrade.作为调用pure的人,这完全是降级。 As mentioned, (a -> b) -> f (a -> b) is an instantiation of the current type of pure already.如前所述, (a -> b) -> f (a -> b)已经是当前pure类型的实例化。 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.要求参数为 function 意味着实现可以利用它来做特定于功能的事情。 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.这是您可以使用 Haskell 中的函数做的唯一特殊事情。因此,要调用它,您只需要为其提供某种类型的值apure的调用者可以选择该类型。 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.唯一的选择是使用undefined或其他一些具有通用量化类型的底值。 What are you going to do?你会怎样做? pure f = let x = f undefined in... ? pure f = let x = f undefined in... How can that be helpful for implementing pure?这对实施 pure 有何帮助?

To return to the initial question, then: what would you gain if the type of pure was (a -> b) -> f (a -> b) ?回到最初的问题,那么:如果pure的类型是(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?更具体的类型的好处在哪里?

Yes, there is more purpose for pure than transforming functions.是的, pure函数比转换函数有更多的用途。 It is very common for do blocks to end with a call to pure . do块以调用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.) (实际上,从历史上看,我认为它是“定义一些有用的东西;意识到它是 monad 的概念;发现应用函子的相关概念;意识到它们是有用的。所以它实际上是“有用第一”和“理论第一”的混合体”。但我永远不会为它的存在辩护,因为它在理论上存在——只会为这个理论很有见地而兴奋。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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