Haskell中的表观类型变量行为不连贯澄清
披露:我是一名学习 Haskell 的 Scala 程序员。
我想更好地理解类型变量的行为。
与 Scala 相反,我们似乎可以声明泛型类型的变量(即类型变量)。即泛型不限于功能。但是,我无法理解以下弃用情况,并希望对此进行一些解释。
为什么下面的代码对函数起作用:
g :: a -> a -- a can take the concrete type Num a => a or something else like Bool
g x = x
g 10
--g 10 :: Num a => a
g True
--g True :: Bool
然而,以下奇怪的事情发生在变量而不是函数上
e :: a
e = 1
-- No instance for (Num a) arising from the literal ‘1’
-- Possible fix:
-- add (Num a) to the context of
-- the type signature for:
-- e1 :: forall a. a
-- In the expression: 1
-- In an equation for ‘e1’: e1 = 1
当然,如果我在 REPL 中输入以下内容
e :: a; e = 1
Q1)从那看来,我们在变量和函数之间的泛型类型(即类型变量)的行为似乎有所不同?这是为什么 ?或者我还没有看到或得到什么?
现在奇怪的部分
如果在 Repl 但是我做
e :: a; e = undefined
进而
e = 1
e = 1
然后我得到
:t e
-- e1 :: Num p => p
因此,“至少”在 REPL 中使用 undefined ,我得到了函数上泛型类型的行为。
Q2) 这里到底发生了什么?这一切对我来说听起来不太连贯。谁能解释一下?
回答
Haskell 中的类型变量是普遍量化的。这意味着实际的类型g是
g :: ? a . a -> a
量词通常是隐式的,但forall如果您启用某些 GHC 扩展,您实际上可以在 Haskell 源代码中将其拼写出来(如)。
这是什么意思?嗯,从字面上看,g具有a -> a 所有可能值的类型a。也就是说,g是一个Bool->Boll和一个String->String和一个Int->Int函数。它具有所有这些类型。
当您编写 时g x = x,您并没有违反 的类型给出的承诺g,即参数可以是任何类型并且结果必须是相同类型。事实上,结果是x,参数也是x,并且x显然与 具有相同的类型x。
然而,如果你写g x = x + 42,这违背了承诺。x不能是类型Bool或String更多。所以编译器会抱怨。
现在,记号
e :: a
以非常相似的方式应该被解释为
e :: ? a . a
这意味着e具有a 的所有可能值的类型a。也就是说,它具有所有可能的类型:IntandString和BoolandInt->Int以及无限多的其他类型。
您可以通过定义e为来满足类型
e = undefined
(实际上,这基本上是您满足它的唯一方法)。但是,如果你写
你违反了e. 所以你会得到和上面一样的错误。
如果你写e = undefined和 after e = 1,那么在 REPL 中,第二个定义会影响第一个定义,所以你有两个不同的变量命名为e,每个变量都有自己的类型。如果您正在使用 REPL,您可以随时重新定义任何名称,这就是 REPL 的用途!这在普通的 Haskell 模块中当然是不可能的。