了解 Monad '>>=' 函数中的 forall?

Understanding forall in Monad '>>=' function?

根据 this 的回答,我在我的程序中实现了一个通用的提升函数:

liftTupe :: (x -> c x) -> (a, b) -> (c a, c b) --This will error
liftTuple :: (forall x. x -> c x) -> (a, b) -> (c a, c b)

我明白,在这种情况下,forall 使 x 可以是任何类型([]Maybe 等)。

我现在正在研究 Monads 中 >>= 的定义:

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

我无法理解这个 forall 在函数定义中的作用?因为,与 liftTuple 不同,它没有绑定到特定函数 (x -> c x)?

基本上,当你不使用forall时,所有类型在函数定义中都是全局的,这意味着它们都是在调用函数时推导出来的。使用 forall 您可以放弃使用 x 的函数,直到它被调用。

所以在第一个函数中你有一个接受 x 并给出 c x 的函数,然后你有一个包含 ab 的元组并且你期望一个c ac b 的元组。既然你已经说过第一个函数接受 x,你可以使 x 变得与 a 相同,但它不会是 b 因为 x 为整个声明定义一次。所以你不能让函数同时接受 ab.

然而,在第二种情况下,x 范围仅限于采用 x 的函数。我们基本上是说有一个函数接受一些东西并用那个东西做 c,它可以是任何类型。这使我们能够先向它提供 a,然后再向其提供 b,它就会工作。 x 现在外面不必是奇异的东西了。

您在 Monad 定义中看到的是 "ExplicitForAll" 语言扩展。 Haskell Prime 上有关于此扩展的描述

ExplicitForAll enables the use of the keyword 'forall' to explicitly state that a type is polymorphic in its free type variables. It does not allow any types to be written that cannot already be written; it just allows the programmer to explicitly state the (currently implicit) quantification.

这个语言扩展是纯视觉的,允许您明确地写出以前不能写出的变量。您可以简单地从 Monad 声明中省略 forall a b.,程序将在功能上保持完全相同。

说,有了这个扩展,您可以将 liftTupe 重写为 forall a b x. (x -> c x) -> (a, b) -> (c a, c b)。定义是一样的,作用也是一样的,但是读者现在会清楚的看到类型变量都定义在最顶层了。

Haskell 类型系统中的所有类型变量都由 forall 量化。但是,GHC 可以在许多情况下推断出量化,因此您无需在源代码中编写它们。

例如 liftTuple 的类型 forall 显式是

liftTuple :: forall c a b. (forall x. x -> c x) -> (a, b) -> (c a, c b)

>>= 情况相同。

您编写的每个函数都在其类型变量上隐式普遍量化:

id :: a -> a           -- this is actually universally quantified over a
id :: forall a. a -> a
id x = x

您实际上可以使用 ExplicitForall 语言编译指示打开此行为。

这个 属性 非常有用,因为它限制了您编写仅适用于某些类型的代码。想一想 id 函数可以做什么:它可以 return 它的参数或永远循环。这是它唯一可以做的两件事,你可以根据它的类型签名来计算。

强制所有多态函数实例以相同的方式运行,而不考虑类型参数称为 parametricity,Philip Wadler 在 this blog post by Bartosz Milewski. The TL;DR is: Using parametricity, we can guarantee that some reorderings in out program's structure do not affect it's behaviour. For a mathematically more rigorous treatment of this, see Theorems for free! 中对此进行了解释。

monad定义中的forall只是为了让全称量化更加明确。如果你有一个没有进一步限制的类型变量,它默认是通用的,即可以是任何东西。

那么让我们看看 forall 的两种用法之间的区别以及 haskell 可能如何看待它们:

隐式:

foo ::  (x -> f x) -> a -> b -> (f a, f b)
-- same as
foo :: forall f x a b . (x -> f x) -> a -> b -> (f a, f b)
-- our function is applied to a, so x is equal to a
foo :: forall f x a b . (x ~ a) => (x -> f x) -> a -> b -> (f a, f b)
-- our function is also applied to b, so x is equal to b
foo :: forall f x a b . (x ~ a, x ~ b) => (x -> f x) -> a -> b -> (f a, f b)

呃哦,(x ~ a, x~ b) 需要(a ~ b)。这将在没有注释的情况下推断出来,但由于我们明确使用了不同的类型变量,所以一切都崩溃了。为了解决这个问题,我们需要 f 在我们的函数中保持多态。

标准 haskell 无法表达这一点,因此我们将需要 rank2types 或 rankntypes。有了它我们可以写:

foo ::  (forall x . x -> f x) -> a -> b -> (f a, f b)

请注意,forall 是函数类型的一部分。这样它在我们的函数中保持多态,我们可以将它应用于不同的类型而不会爆炸!

请注意,我们也可以这样做:

foo :: Monad m => a -> b -> (m a, m b)
foo a b = (return a, return b)