2

我按照这个答案中的建议开始学习 Haskell 。所以我只是在实现简单的列表函数,我偶然发现了编译器行为的差异,我无法向自己解释:

-- Impl 1
elementAt :: (Integral b) => [a] -> b -> a
elementAt xs id = xs !! (fromIntegral(id-1))

-- Impl 2
elementAt' :: (Num b) => [a] -> b -> a
elementAt' xs id = xs !! (id-1)

带有以下签名:

fromIntegral :: (Integral a, Num b) => a -> b
(!!) :: [a] -> Int -> a

我只收到第二个实现的错误,elementAt'.

Could not deduce (b ~ Int)
from the context (Num b)

如果我理解正确,这意味着运算符 (!!) 期望Int实例作为其第二个参数(从签名中可以看出),但我们只保证提供的参数符合NumelemenAt'类型类(从签名中推断) ,它比Int更宽。

考虑到这一点,我不明白为什么第一个实现实际上有效,因为我知道它fromIntegral也返回一个仅符合Num类型类的值。

4

1 回答 1

5

fromIntegral返回该类的任何实例Num。即,无论您需要什么实例,它都会知道如何生成它。这就是类型变量的想法,它基本上是一个额外的编译时参数,函数的调用者可以选择它。这就是elemAt工作的原因:编译器知道我们需要Int,所以它告诉fromIntegral,然后知道该做什么。

但是,因此,如果定义一个带有签名的函数Num b => ...,您还需要允许调用者输入他们选择的任何类型b,前提是它在Num类中。在这种情况下,您无需请求特定实例Int,而是需要获取调用者给您的任何内容。这就是区别。

事实上,Num b => [a] -> b -> a不是您可以有效定义此函数的签名。您将如何通过复数,甚至是无限维矩阵或其他什么来索引列表?你能做的是Integral b => [a] -> b -> a

于 2013-10-30T10:17:05.497 回答