我按照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 类型类的值 .
1 回答
fromIntegral
返回Num
类的任何实例 . 即,无论你需要什么实例,它都是类型变量的想法,'s basically an extra compile-time argument which the caller of the function gets to choose. That'为什么elemAt
工作原理:编译器知道我们需要Int
,所以它告诉fromIntegral
,然后知道该怎么做 .但是,因此,如果您使用签名
Num b => ...
定义一个函数,您还需要允许调用者为b
选择任何类型的选项,前提是它位于Num
类中 . 在这种情况下,您无需询问特定实例Int
,但需要接受调用者给您的任何内容 . 这就是区别 .实际上,
Num b => [a] -> b -> a
不是可以有效定义此函数的签名 . 你如何用复数,甚至是无限维矩阵或其他什么来索引列表?你能做的是Integral b => [a] -> b -> a
.