Haskell' forall'之间的关系和' =>'

时间:2015-10-18 14:34:26

标签: haskell syntax types polymorphism

我在围绕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在每个角色中扮演的角色是什么?

3 个答案:

答案 0 :(得分:4)

(另一个角度,没有调用"隐式字典传递"类型类的实现):

Haskell中的

forall a.表示"对于每种类型a"。 1 它引入了一个类型变量,并声明了类型表达式的其余部分必须是有效的a做出的选择。

你通常不会在基本的Haskell中看到它(没有在GHC中启用任何扩展),因为它没有必要;你只需在类型签名中使用类型变量,GHC会自动假设forall在表达式的开头引入了这些变量。

例如:

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)对列表,无论如何我们希望选择ab

forall主要用于扩展,因为如果你不明确地写它们,你可以引入范围其他的类型变量而不是GHC假设的默认类型变量。

现在,constraints => type语法可以大致读作"这些约束意味着这种类型"或者#34;如果这些约束成立,你可以使用这种类型"。它一直在使用,即使在没有扩展的vanilla Haskell中也是如此,因此了解它的含义及其工作方式非常重要,而不仅仅是复制,粘贴和希望。

=>箭头允许我们在类型表达式的其余部分中对变量说明一组约束;它让我们限制了引入类型变量的选择。您应首先忽略=>箭头左侧的所有内容,然后单独阅读右侧部分。这为你提供了'#34;形状"这种类型。 =>箭头左侧的内容告诉您可以使用其他类型的类型。

一个例子:

(+) :: Num a => a -> a -> a

这意味着(+)a -> a -> a等更简单的类型完全相同,除了 Num a =>告诉我们我们& #39;不能自由选择任何类型a。当我们知道它是a类型类的成员时,我们只能选择Num的类型(另一种更精确的说法方式" a是其成员Num是"约束Num a包含")。

请注意,GHC仍然认为这里引入了类型变量forall a的隐式a,所以看起来确实如此:

(+) :: forall a. Num a => a -> a -> a

在这种情况下,一旦您知道forall a.Num a =>的含义,您就可以轻松地将其作为英语句子轻松阅读:"对于每种类型a,提供{{ 1}}持有,加上类型为Num a"。

1 如果你完全熟悉形式逻辑,它只是一种ASCII友好的写作方式∀a,一个普遍量化的变量&#34 ;

答案 1 :(得分:2)

forall问题似乎已解决时,我会尝试解释一下=>=>左侧的内容是参数,非常类似->左侧的参数。但是你不能手动应用这些参数,它们只能有特定的类型。

f :: Num a => a -> a

是一个带有两个参数的函数:

  1. 一个Num a字典。
  2. a
  3. 当您应用f时,您只需提供a即可。 GHC必须提供Num a。如果它应用于特定的具体类型,如Int,GHC知道Num Int并可以在呼叫站点提供它。否则,它会检查某些外部上下文是否提供Num a并使用该上下文。 Haskell的类型类系统的优点是它确保任何两个Num a词典,无论它们是什么,都是相同的。所以字典的来源并不重要 - 它肯定是正确的。

    进一步讨论

    我们谈论的很多这些事情并不完全是Haskell的一部分,因为它们是GHC通过转换为GHC核心,AKA系统FC来解释Haskell的方式的一部分,这是一个非常好的扩展 - 研究了F系统,AKA是Girard-Reynolds演算。系统FC是具有代数数据类型等的显式类型多态lambda演算,但没有类型推断,没有实例解析等。在GHC检查Haskell代码中的类型之后,它通过完全机械的过程将该代码转换为系统FC。它可以自信地做到这一点,因为类型检查器使用desugarer需要检测所有字典的所有信息“装饰”代码。如果你有一个看起来像

    的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线,除了它需要一个类型变量。 @表示将类型应用于需要一个的函数。 +实际上只是一个记录选择器。它从它传递的字典中选择正确的字段。

答案 2 :(得分:1)

如果我们添加隐含的括号,我认为这会有所帮助:

(+) :: ∀ a . ( Num a => (a -> (a -> a)) )
id :: ∀ a . ( a -> a )

始终与.一起使用。它基本上是特殊的语法,意思是“.之间的任何内容都是我要引入以下范围的类型变量”

=>表示Idris称之为隐式函数:Num ainstance Num a字典,无论何时你都需要这样的字典添加数字。但是a是否是以前由某些或固定类型引入的类型变量并不重要。你也可以

(+) :: Num Int => Int -> Int -> Int

这只是多余的,因为编译器知道 IntNum实例,因此会自动(隐式!)选择正确的字典。

实际上,=>之间没有特别的关系,它们恰好经常在一起使用。

实际上这是一个类型级别的lambda 。类型表达式∀ a . b的行为类似于值级别表达式\a -> b