在 ghci 终端中,允许以下变量赋值:
a = [2:x | x <- [1, 1, 1]]
但是,当打印
a
时,出现以下错误:
- No instance for (Num [Integer]) arising from a use of 'it'
- In the first argument of 'print', namely 'it'
- In a stmt of an interactive GHCi command: print it
这是为什么?
您能够在 GHCi 中这样定义它的原因是您没有指定它应该是什么类型。好的做法是始终将类型签名添加到顶级定义中——尽管公平地说,在 GHCi 中不这样做是很常见的。
看,您可以写出的一些表达式具有虚假类型,尽管它们会进行类型检查。这是一个例子,它的类型是
ghci> :t [2:x | x <- [1, 1, 1]]
(Num a, Num [a]) => [[a]]
这是什么意思?好吧,在解释之前,让我解释一下一些更简单的表达式的类型,这实际上确实有意义:
ghci> :t 'x'
Char
这个应该是不言自明的。
ghci> :t 37
Num a => a
这个也许不是。难道不应该是像
Int
这样简单的东西吗?
嗯,诀窍是,Haskell 的数字文字更加灵活。它们根本不必具有任何特定类型,而是采用上下文所需的任何类型。原因是它允许您编写类似
sqrt 2
的内容,并让结果为 Double
类型。在这种情况下,文字 2
也具有类型 Double
。 (其他语言通过从 Int
到 Double
自动转换来实现这一点,但这有很多缺点,我不会在这里讨论。)
编译器强加的唯一条件是上下文所需的类型必须具有
Num
类的实例。对于 Double
,该实例如下所示 (https://hackage.haskell.org/package/base-4.19.0.0/docs/src/GHC.Float.html#line-545),带有一堆你不需要担心的可怕的低级细节:
instance Num Double where
(+) x y = plusDouble x y
(-) x y = minusDouble x y
negate x = negateDouble x
(*) x y = timesDouble x y
abs x = fabsDouble x
signum x | x > 0 = 1
| x < 0 = negateDouble 1
| otherwise = x -- handles 0.0, (-0.0), and NaN
{-# INLINE fromInteger #-}
fromInteger i = D# (integerToDouble# i)
通常,每当您尝试在没有
Num
实例的类型上下文中使用数字文字时,编译器都会给出错误。在您的示例中,上下文需要一个列表。具有相同问题的更简单的示例是
reverse 5 :: [Integer]
嗯,常识告诉我们列表不是数字,因此与
Num
不兼容。但编译器并不知道这一点,实际上理论上您可以定义这样一个实例! (别这样做,我只是在这里唱反调)
instance Num [Integer] where
fromInteger i = [523,i,916]
然后你的代码会突然工作......我的意思是,不是“工作”意义上的做正确的事情,而是在没有错误消息的情况下进行评估:
ghci> reverse 5 :: [Integer]
[916,5,523]
因为实际上并没有
instance Num [Integer]
,所以你可能会认为这样写通常会出错。然而,Haskell 的类型类机制的一个怪癖是,在将实例缩小到特定类型之前,您永远无法确定实例是否不存在。但是当你简单地定义时
a = reverse 5
ghci> :t a
a :: Num [a] => [a]
实际的解决方案当然是
不使用数字作为列表。也许你真正想做的是将数字用作列表elementa
,例如你可以这样:
ghci> [[2,x] | x <- [1, 1, 1]]
[[2,1],[2,1],[2,1]]