Haskell:理解ghci中的“没有实例”错误消息

时间:2012-04-29 20:06:01

标签: haskell ghc ghci winghci

问题1

嗨,如果在WinGHCi中,我故意做了以下错误的代码:

3 4

然后我得到的错误信息是

<interactive>:1:1:
    No instance for (Num (a0 -> t0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the expression: 3
    In the expression: 3 4
    In an equation for `it': it = 3 4

No instance for (Num (a0 -> t0))究竟是什么意思?

问题2

为什么以下代码:

(+) 2 3 4
<interactive>:1:7:
    No instance for (Num (a0 -> t0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the second argument of `(+)', namely `3'
    In the expression: (+) 2 3 4
    In an equation for `it': it = (+) 2 3 4

产生与第二段代码略有不同的错误:

2+3 4
<interactive>:1:3:
    No instance for (Num (a1 -> a0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a1 -> a0))
    In the expression: 3
    In the second argument of `(+)', namely `3 4'
    In the expression: 2 + 3 4

即在第一段代码中,我们有No instance for (Num (a0 -> t0)),而在第二段代码中我们有No instance for (Num (a1 -> a0))


[回应ehird]

(问题来自答案评论):

1)我欣赏后两种表达方式不同,但是你是说我不应该试着理解为什么口译员为前者选择(Num (a0 -> t0))而后者为(Num(a1 -> a0))选择,除了事实他们是不同的?

2)嗨,当你说“但是没有功能的Num实例”时,和前者一样,你的意思是什么?对不起,我不清楚实例的概念是什么。此外,出于好奇,您是否可以使用实例Num (a -> b)方法以某种方式告诉口译员将3 4解释为4 modulo 3

2 个答案:

答案 0 :(得分:16)

我的意图是用更多的解释补充ehird的答案。当你写表达式

3 4

然后Haskell解释器认为您正在尝试将函数3应用于4。为了让Haskell将3解释为函数,它需要调用函数

fromInteger :: Integer -> (a -> b)

从整数a -> b获取函数(即3类型的东西)。现在,在fromInteger类型类中定义了Num以获得签名

instance Num x where
    fromInteger :: Integer -> x

即。当您将类型x作为Num类的实例时,您将提供fromInteger的实现,该实现告诉Haskell如何将整数文字转换为x。在您的情况下,x是函数类型a -> b。那就让我们做吧!


首先,一些样板。要使x成为Num Haskell的实例,我们还要将其设为ShowEq的实例:

instance Show (a -> b) where show f = "<function>"
instance Eq (a -> b) where f == g = False

现在假设我们想将3 4解释为“4 modulo 3”。然后我们需要告诉Haskell如何将任何整数解释为调用mod的函数。此外,由于mod只接受整数类型(它具有签名mod :: Integral a => a -> a -> a),因此我们需要将ab的类型限制为整数:

instance (Integral a, Integral b) => Num (a -> b) where

make an instance of Num我们需要提供(+)(-)(*)fromIntegral的实现(实际上我们也应该定义其他几个函数,但现在让我们不要担心。

有一种相当自然的方法可以定义加法,减法和乘法(这里的所有代码都构成Num实例的一部分,并且应该相对于实例声明进行缩进)

    f + g = \x -> f x + g x
    f - g = \x -> f x - g x
    f * g = \x -> f x * g x

即。当您添加两个函数fg时,您将获得一个新函数,它将fg应用于其参数,然后将它们相加。由于我们要求应用fg的结果是整数类型,因此我们知道将它们的输出相加是有意义的。

要将整数解释为函数,我们可以编写

    fromInteger n = \m -> fromIntegral m `mod` fromIntegral n

即。当我们有一个整数n时,我们返回一个参数m的函数,当被调用时,它确保两个参数的类型相同(通过在它们上面调用fromIntegral)然后将它们用作函数mod的参数。

最后,更多的样板来阻止Haskell抱怨:

    abs f = undefined
    signum f = undefined

我们可以测试一下。我的代码名为numfun.hs。我启动Haskell解释器并加载我的文件:

Prelude> :l numfun.hs
[1 of 1] Compiling Main             ( numfun.hs, interpreted )
Ok, modules loaded: Main.

现在我可以定义一些功能:

*Main> let f = (+ 1)
*Main> let g = (* 2)

我可以添加或减去它们:

*Main> (f + g) 3   -- (3+1) + (3*2)
10
*Main> (f - g) 3   -- (3+1) - (3*2)
-2

我可以将号码称为功能:

*Main> 3 4         -- 4 `mod` 3
1

答案 1 :(得分:14)

发生第一个错误是因为像4这样的整数文字可以是带有Num实例的任何类型。也就是说,4的类型为(Num a) => a,因此它可以用作IntegerDoubleRational等。因为您已应用{{ 1}}到一个参数(3),它知道,在上下文中,4必须是一个函数类型(即某些3a0 -> t0 a0 })。但是函数没有t0个实例,因此将Num用作函数是无效的。如果您添加了3,它会起作用,但您可能不想这样做。

对于后者,这两个错误消息是等价的; GHC生成的名称没有特别的意义。这些字母通常来自您正在使用的函数类型中的类型变量,并且附加数字以保持事物的明确性。在这种情况下,第二个表达式相当于instance Num (a -> b)(因为函数应用程序比任何中缀运算符都更紧密),这与您的第一段代码不完全相同。