Haskell 的 'forall' 和 '=>' 之间的关系
Relationship between Haskell's 'forall' and '=>'
我无法理解 Haskell 的 forall
和 =>
之间的关系(和互动)(就此而言 .
经常连接他们)。
例如
λ> :t (+)
λ> :t id
给予
(+) :: forall a. Num a => a -> a -> a
id :: forall a. a -> a
虽然我理解这些在这些特定情况下是如何工作的,但我不太愿意将表达式(签名?)forall a. Num a =>
或 forall a.
本身解析为有意义的东西,或者我通常可以在更复杂的上下文中理解。
forall a. Num a =>
和 forall a.
是什么意思?具体来说,forall
、=>
、a
分别扮演什么角色?
我想如果我们添加隐含的括号会有所帮助:
(+) :: ∀ a . ( Num a => (a -> (a -> a)) )
id :: ∀ a . ( a -> a )
∀
总是与 .
一起出现。它基本上是特殊语法,意思是“∀
和 .
之间的任何内容都是我想引入以下范围的类型变量”†
=>
表示 Idris 所说的隐式函数:Num a
是 字典 对于 instance Num a
,这样的字典是隐式的每当您添加数字时都需要。但是这里的 a
是之前某个 ∀
引入的类型变量,还是固定类型,其实并不重要。你也可以
(+) :: Num Int => Int -> Int -> Int
那是多余的,因为编译器 知道 Int
是一个 Num
实例,因此会自动(隐含地!)选择正确的字典。
其实,∀
和[=16=之间并没有特别的关系,只是碰巧经常一起使用而已。
†实际上这是一个类型级别的lambda。类型表达式 ∀ a . b
的行为类似于值级别表达式 \a -> b
.
由于 forall
问题似乎已经解决,我将尝试稍微解释一下 =>
。 =>
左边的东西是参数,很像 ->
左边的东西。但是你不需要手动应用这些参数,它们只能有特定的类型。
f :: Num a => a -> a
是一个有两个参数的函数:
- 一本
Num a
字典。
- 一个
a
.
当您申请 f
时,您只需提供 a
。 GHC 必须提供 Num a
。如果它应用于特定的具体类型,如 Int
,GHC 知道 Num Int
并且可以在调用站点提供它。否则,它会检查 Num a
是否由某个外部上下文提供并使用该上下文。 Haskell 类型类系统的伟大之处在于它确保任何两个 Num a
字典,无论它们是如何被发现的,都是相同的。因此,字典的来源并不重要——它肯定是正确的。
进一步讨论
我们正在谈论的很多事情并不完全是 Haskell 的一部分,因为它们是 GHC 通过翻译成 GHC 核心,AKA 来解释 Haskell 的方式的一部分系统 FC,是经过充分研究的系统 F 的扩展,又名吉拉德-雷诺兹微积分。 System FC 是一个显式类型的多态 lambda 演算,具有代数数据类型等,但没有类型推断,没有实例解析等。在 GHC 检查 Haskell 代码中的类型后,它通过彻底的机械过程。它可以自信地做到这一点,因为类型检查器 "decorates" 代码包含脱糖器需要探测周围所有词典的所有信息。如果你有一个 Haskell 看起来像
的函数
foo :: forall a . Num a => a -> a -> a
foo x y= x + y
然后这将转化为看起来像
的东西
foo :: forall a . Num a -> a -> a -> a
foo = /\ (a :: *) -> \ (d :: Num a) -> \ (x :: a) -> \ (y :: a) -> (+) @a d x y
/\
是一个 lambda 类型——它只是一个普通的 lambda 行,除了它接受一个类型变量。 @
表示将一种类型应用于采用一个的函数。 +
实际上只是一个记录选择器。它从传递给它的字典中选择正确的字段。
(从另一个角度来看,不调用 classes 类型的 "implicit dictionary passing" 实现):
Haskell中的forall a.
的意思是"for every type a
".1是引入了一个类型变量,声明剩下的类型表达式有 选择 a
.
是有效的
你通常不会在 basic Haskell 中看到它(没有在 GHC 中打开任何扩展),因为它不是必需的;您只需在类型签名中使用类型变量,GHC 会自动假定在表达式的开头有 forall
s 引入这些变量。
例如:
zip :: forall a. ( forall b. ( [a] -> [b] -> [(a, b)] ))
zip :: forall a. forall b. [a] -> [b] -> [(a, b)]
zip :: forall a b. [a] -> [b] -> [(a, b)]
zip :: [a] -> [b] -> [(a, b)]
以上都是一样的;他们只是告诉我们 zip
可以是一种压缩 a
列表和 b
列表以制作 (a, b)
对列表的方法,无论我们想为 a
和 b
.
做出什么选择
forall
主要与扩展一起发挥作用,因为这样你就可以引入范围为 other 的类型变量,而不是 GHC 假定的默认范围,如果你不明确的话写他们。
现在,constraints => type
语法可以大致理解为 "these constraints imply this type" 或 "provided these constraints hold, you can use this type"。它一直在使用,即使在没有扩展名的香草 Haskell 中也是如此,因此了解它的含义及其工作原理很重要,而不仅仅是复制粘贴和希望。
=>
箭头允许我们在类型表达式的其余部分中声明一组对变量的约束;它让我们限制可以做出哪些选择来引入类型变量。您应该先阅读它,忽略 =>
箭头左侧的所有内容,然后单独阅读右侧部分。这为您提供了 "shape" 类型。 =>
箭头左侧的内容告诉您可以使用哪些类型来使用其余类型。
一个例子:
(+) :: Num a => a -> a -> a
这意味着 (+)
与 a -> a -> a
、 除了 和 Num a =>
等更简单的类型完全相同告诉我们我们不能自由选择 any 类型 a
。当我们知道它是 Num
类型 class 的成员时,我们只能为 a
选择一个类型(另一种稍微更精确的说法是“a
是一个成员Num
的 "the constraint Num a
holds").
请注意,GHC 仍然假设这里有一个隐式 forall a
来引入类型变量 a
,所以它看起来确实像:
(+) :: forall a. Num a => a -> a -> a
在这种情况下,一旦您知道 forall a.
和 Num a =>
的含义,您就可以将其作为英文句子轻松阅读:"For every type a
, provided Num a
holds, plus has the type a -> a -> a
".
1 如果你完全熟悉形式逻辑,它只是一种 ASCII 友好的写法 ∀a, a "universally quantified variable".
我无法理解 Haskell 的 forall
和 =>
之间的关系(和互动)(就此而言 .
经常连接他们)。
例如
λ> :t (+)
λ> :t id
给予
(+) :: forall a. Num a => a -> a -> a
id :: forall a. a -> a
虽然我理解这些在这些特定情况下是如何工作的,但我不太愿意将表达式(签名?)forall a. Num a =>
或 forall a.
本身解析为有意义的东西,或者我通常可以在更复杂的上下文中理解。
forall a. Num a =>
和 forall a.
是什么意思?具体来说,forall
、=>
、a
分别扮演什么角色?
我想如果我们添加隐含的括号会有所帮助:
(+) :: ∀ a . ( Num a => (a -> (a -> a)) )
id :: ∀ a . ( a -> a )
∀
总是与 .
一起出现。它基本上是特殊语法,意思是“∀
和 .
之间的任何内容都是我想引入以下范围的类型变量”†
=>
表示 Idris 所说的隐式函数:Num a
是 字典 对于 instance Num a
,这样的字典是隐式的每当您添加数字时都需要。但是这里的 a
是之前某个 ∀
引入的类型变量,还是固定类型,其实并不重要。你也可以
(+) :: Num Int => Int -> Int -> Int
那是多余的,因为编译器 知道 Int
是一个 Num
实例,因此会自动(隐含地!)选择正确的字典。
其实,∀
和[=16=之间并没有特别的关系,只是碰巧经常一起使用而已。
†实际上这是一个类型级别的lambda。类型表达式 ∀ a . b
的行为类似于值级别表达式 \a -> b
.
由于 forall
问题似乎已经解决,我将尝试稍微解释一下 =>
。 =>
左边的东西是参数,很像 ->
左边的东西。但是你不需要手动应用这些参数,它们只能有特定的类型。
f :: Num a => a -> a
是一个有两个参数的函数:
- 一本
Num a
字典。 - 一个
a
.
当您申请 f
时,您只需提供 a
。 GHC 必须提供 Num a
。如果它应用于特定的具体类型,如 Int
,GHC 知道 Num Int
并且可以在调用站点提供它。否则,它会检查 Num a
是否由某个外部上下文提供并使用该上下文。 Haskell 类型类系统的伟大之处在于它确保任何两个 Num a
字典,无论它们是如何被发现的,都是相同的。因此,字典的来源并不重要——它肯定是正确的。
进一步讨论
我们正在谈论的很多事情并不完全是 Haskell 的一部分,因为它们是 GHC 通过翻译成 GHC 核心,AKA 来解释 Haskell 的方式的一部分系统 FC,是经过充分研究的系统 F 的扩展,又名吉拉德-雷诺兹微积分。 System FC 是一个显式类型的多态 lambda 演算,具有代数数据类型等,但没有类型推断,没有实例解析等。在 GHC 检查 Haskell 代码中的类型后,它通过彻底的机械过程。它可以自信地做到这一点,因为类型检查器 "decorates" 代码包含脱糖器需要探测周围所有词典的所有信息。如果你有一个 Haskell 看起来像
的函数foo :: forall a . Num a => a -> a -> a
foo x y= x + y
然后这将转化为看起来像
的东西foo :: forall a . Num a -> a -> a -> a
foo = /\ (a :: *) -> \ (d :: Num a) -> \ (x :: a) -> \ (y :: a) -> (+) @a d x y
/\
是一个 lambda 类型——它只是一个普通的 lambda 行,除了它接受一个类型变量。 @
表示将一种类型应用于采用一个的函数。 +
实际上只是一个记录选择器。它从传递给它的字典中选择正确的字段。
(从另一个角度来看,不调用 classes 类型的 "implicit dictionary passing" 实现):
Haskell中的
forall a.
的意思是"for every type a
".1是引入了一个类型变量,声明剩下的类型表达式有 选择 a
.
你通常不会在 basic Haskell 中看到它(没有在 GHC 中打开任何扩展),因为它不是必需的;您只需在类型签名中使用类型变量,GHC 会自动假定在表达式的开头有 forall
s 引入这些变量。
例如:
zip :: forall a. ( forall b. ( [a] -> [b] -> [(a, b)] ))
zip :: forall a. forall b. [a] -> [b] -> [(a, b)]
zip :: forall a b. [a] -> [b] -> [(a, b)]
zip :: [a] -> [b] -> [(a, b)]
以上都是一样的;他们只是告诉我们 zip
可以是一种压缩 a
列表和 b
列表以制作 (a, b)
对列表的方法,无论我们想为 a
和 b
.
forall
主要与扩展一起发挥作用,因为这样你就可以引入范围为 other 的类型变量,而不是 GHC 假定的默认范围,如果你不明确的话写他们。
现在,constraints => type
语法可以大致理解为 "these constraints imply this type" 或 "provided these constraints hold, you can use this type"。它一直在使用,即使在没有扩展名的香草 Haskell 中也是如此,因此了解它的含义及其工作原理很重要,而不仅仅是复制粘贴和希望。
=>
箭头允许我们在类型表达式的其余部分中声明一组对变量的约束;它让我们限制可以做出哪些选择来引入类型变量。您应该先阅读它,忽略 =>
箭头左侧的所有内容,然后单独阅读右侧部分。这为您提供了 "shape" 类型。 =>
箭头左侧的内容告诉您可以使用哪些类型来使用其余类型。
一个例子:
(+) :: Num a => a -> a -> a
这意味着 (+)
与 a -> a -> a
、 除了 和 Num a =>
等更简单的类型完全相同告诉我们我们不能自由选择 any 类型 a
。当我们知道它是 Num
类型 class 的成员时,我们只能为 a
选择一个类型(另一种稍微更精确的说法是“a
是一个成员Num
的 "the constraint Num a
holds").
请注意,GHC 仍然假设这里有一个隐式 forall a
来引入类型变量 a
,所以它看起来确实像:
(+) :: forall a. Num a => a -> a -> a
在这种情况下,一旦您知道 forall a.
和 Num a =>
的含义,您就可以将其作为英文句子轻松阅读:"For every type a
, provided Num a
holds, plus has the type a -> a -> a
".
1 如果你完全熟悉形式逻辑,它只是一种 ASCII 友好的写法 ∀a, a "universally quantified variable".