类型为 Num ([Char] -> t) => t 的奇怪 Haskell 表达式

Strange Haskell expression with type Num ([Char] -> t) => t

在 GHCi 中做一些练习时,我输入并得到以下内容>

ghci> (1 "one")

<interactive>:187:1:
  No instance for (Num ([Char] -> a0)) arising from a use of ‘it’
  In a stmt of an interactive GHCi command: print it

这是一个错误,但是如果我向 GHCi 询问表达式的类型,它不会给出任何错误:

ghci> :type (1 "one")
(1 "one") :: Num ([Char] -> t) => t

(1 "one")是什么意思?

为什么这个表达式给出了错误,但 GHCi 告诉它类型正确?

Num ([Char] -> t) => t是什么意思?

谢谢。

Haskell 报告救援! (引用section 6.4.1

An integer literal represents the application of the function fromInteger to the appropriate value of type Integer.

fromInteger 有类型:

Prelude> :t fromInteger
fromInteger :: Num a => Integer -> a

所以1实际上是fromInteger (1 :: Integer)的语法糖。那么你的表情是:

fromInteger 1 "one"

可以写成:

(fromInteger 1) "one"

现在,fromInteger 生成一个数字(即,一个类型的值,它是 Num 的一个实例,正如它的类型告诉我们的那样)。在您的表达式中,此数字应用于 [Char](字符串 "one")。 GHC 正确地结合了这两条信息来推断您的表达式具有类型:

Num ([Char] -> t) => t

也就是说,这将是将也是 Num 的函数应用于 [Char] 的结果(未指定类型 t)。原则上这是一个有效的类型。唯一的问题是 [Char] -> t 没有 Num 的实例(也就是说,接受字符串的函数不是数字,这并不奇怪)。

P.S.: 正如 Sibi 和 Ørjan 指出的那样,在 GHC 7.10 及更高版本中,如果启用了 FlexibleContexts GHC 扩展,您将只会看到问题中提到的错误;否则类型检查器会抱怨 class 约束中有固定的类型和类型构造函数(即 Char[](->))。

TL;DR: 这是一种乱码无意义的类型,不值得担心。

整数文字可以表示实现 Num 类型class 的任何类型的值。所以 1 或任何其他整数文字都可以在任何需要数字的地方使用。

doubleVal :: Double
doubleVal = 1

intVal :: Int
intVal = 1

integerVal :: Integer
integerVal = 1

这使我们能够在任何数字上下文中灵活地使用整数文字。

当你只使用一个没有任何类型上下文的整数文字时,ghci 不知道它是什么类型。

Prelude> :type 1
1 :: Num a => a

ghci 在说 "that '1' is of some type I don't know, but I do know that whatever type it is, that type implements the Num typeclass".

Haskell 源代码中每次出现的整数文字都用隐式 fromInteger 函数包装。所以 (1 "one") 被隐式转换为 ((fromInteger (1::Integer)) "one"),子表达式 (fromInteger (1::Integer)) 有一个未知的类型 Num a => a,同样意味着它是某种未知类型,但我们知道它提供了一个Num 类型 class 的实例。

我们还可以看到它像函数一样应用于 "one",因此我们知道它的类型必须具有 [Char] -> a0 的形式,其中 a0 是另一种未知类型。所以 a[Char] -> a0 必须相同。将其代回我们上面计算出的 Num a => a 类型,我们知道 1 必须具有类型 Num ([Char] -> a0) => [Char] -> a0),并且表达式 (1 "one") 具有类型 Num ([Char] -> a0) => a0。将最后一个类型读作“有一些类型 a0 是将 [Char] 参数应用于函数的结果,并且该函数类型是 Num class.

因此表达式本身具有有效类型 Num ([Char] -> a0) => a0

Haskell 有一个叫做 Monomorphism restriction 的东西。一方面是表达式中的所有类型变量都必须具有特定的已知类型,然后才能对其求值。 GHC 在某些情况下尽可能使用类型默认规则来适应单态性限制。但是,GHC 不知道任何类型 a0 它可以插入上面定义了 Num 实例的类型表达式。所以它没有办法处理它,并给你 "No Instance for Num..." 消息。

Haskell 是一种非常灵活的语言,但从字面意义上讲也是一种非常 合乎逻辑的 语言。通常,在大多数语言中只是语法错误的事情,Haskell 会查看它们并尽最大努力来理解它们,结果确实令人困惑,但实际上只是规则的逻辑结果语言。

例如,如果我们将您的示例输入 Python,它基本上告诉我们 "what you just typed in makes zero sense":

Python 2.7.6 (default, Sep  9 2014, 15:04:36) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> (1 "one")
  File "<stdin>", line 1
    (1 "one")
           ^
SyntaxError: invalid syntax

Ruby 做同样的事情:

irb(main):001:0> (1 "one")
SyntaxError: (irb):1: syntax error, unexpected tSTRING_BEG, expecting ')'
(1 "one")
    ^
    from /usr/bin/irb:12:in `<main>'

但是Haskell不会轻易放弃!它看到 (1 "one"),它的原因是:

  • f x 形式的表达式是 函数应用程序 ,其中 f 的类型类似于 a -> bx 的类型af x 的类型为 b.
  • 所以在表达式1 "one"中,1必须是一个以"one"(一个[Char])为参数的函数。

然后给定 Haskell 对数字文字的处理,它将 1 转换为 fromInteger 1 :: Num b => [Char] -> bfromIntegerNum class 的一种方法,这意味着用户可以为任何类型提供他们自己的实现——如果您愿意,也包括 [Char] -> b

所以错误信息的意思是Haskell,而不是告诉你你输入的是废话,而是告诉你你没有教它如何构造一个类型Num b => [Char] -> b的数字,因为这是真正奇怪的事情,需要为真才能使表达式有意义。