我开始按照this answer中的建议学习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 实例作为其第二个参数(从签名中看到),但我们只保证提供的参数是符合 Num 类型类(从elemenAt'
签名推断),它比 Int 更宽。
考虑到这一点,我不明白为什么第一个实现确实有效,因为知道fromIntegral
也返回一个只符合 Num 类型类的值。
答案 0 :(得分:5)
fromIntegral
返回 Num
类的任何实例。即,无论你需要什么实例,它都会知道如何制作它。这是一个类型变量的想法,它基本上是一个额外的编译时参数,函数的调用者可以选择它。这就是为什么elemAt
有效:编译器知道我们需要Int
,因此它会告诉fromIntegral
,然后知道该怎么做。
但是,如果你定义了一个带有签名Num b => ...
的函数,你还需要允许调用者为b
添加任何类型的选择,前提是它是在Num
课程中。在这种情况下,你不要求提供特定的实例Int
,但需要接受调用者给你的任何内容。这就是区别。
实际上,Num b => [a] -> b -> a
不是您可以有效定义此功能的签名。你如何用复数,甚至是无限维矩阵或其他什么来索引列表?你可以做的是Integral b => [a] -> b -> a
。