在 Haskell 中,如果转换两个列表的标准点积,例如
dotProduct :: (Num a) => [a] -> [a] -> a
dotProduct x y = sum . zipWith (*) x y
通过 pointfree.io 这样的 pointfree 工具生成
dotProduct = (sum .) . zipWith (*)
如果我通过统一类型来工作,我充分确信生成的类型确实是
[a] -> [a] -> a
以及必需的类型类。相反,我很困惑为什么我的第一次尝试
dotProduct = sum . zipWith (*)
以如此奇怪的方式失败了。我会理解某种类型错误,但它确实进行了类型检查,并且在 ghci 上,其结果类型是
(Foldable ((->) [c]), Num c, Num [c]) => [c] -> [c]
,我什至无法开始理解。有没有这种类型的示例居民可以传递到点积中?
当我尝试手动运行类型统一算法时,似乎 sum
[a]
的第一个参数应该与 [c] -> [c]
的结尾部分 zipWith (*)
统一。我认为这应该会给出类型错误,但它以某种方式统一了,这就是导致奇怪函数的原因。
当我尝试手动运行类型统一算法时,似乎 sum
的第一个参数应该与[a]
的结尾部分[c] -> [c]
统一。zipWith (*)
正确。
我认为这应该会给出类型错误,但相反它以某种方式统一,这就是导致奇怪函数的原因。
不,为什么?
sum
可以接受任何 t a
,只要 t
是可折叠的,并且 a
是数字类型。在这种情况下我们有
t a = [c] -> [c]
-- i.e., rewriting the function type in prefix notation
t a = (->) [c] [c]
-- ergo
t = (->) [c]
a = [c]
因此这就产生了要求
Foldable ((->) [c])
和 Num [c]
。
当然,游戏中的标准实例无法满足此类约束。尽管如此,Haskell 仍然允许人们添加自己的实例。这样做可能会涉及编写奇怪的、人为的实例,但理论上这是可能的,GHC 此时拒绝这一点是错误的。
作为一个人为的示例,以
c = Void
为例,它是没有(非底部)值的空类型。如果我们忽略底部,那么 [c]
就是 [Void]
,它只有一个值(空列表 []
),所以 [c]
与单位类型 ()
同构。
达到同构,约束
Foldable ((->) [c])
和Num [c]
变为Foldable Identity
和Num ()
。前一个实例已经存在。后者可以被定义为使得 ()
对应于零。可以说,一种零位整数。