在Andrew Koenig的An anecdote about ML type inference中,作者使用merge sort的实现作为ML的学习练习,很高兴找到“不正确”的类型推断。
令我惊讶的是,编译器报告了一种
'a list -> int list
换句话说,这个sort函数接受任何类型的列表并返回一个整数列表。
这是不可能的。输出必须是输入的排列;它怎么可能有不同的类型?读者肯定会发现我的第一个冲动:我想知道我是否在编译器中发现了一个错误!
在考虑了一些之后,我意识到还有另一种方法可以忽略它的论点:也许它根本没有返回。确实,当我尝试它时,这正是发生的事情:
sort(nil)
确实返回nil
,但排序任何非空列表将进入无限递归循环。
翻译成Haskell时
split [] = ([], [])
split [x] = ([x], [])
split (x:y:xs) = (x:s1, y:s2)
where (s1,s2) = split xs
merge xs [] = xs
merge [] ys = ys
merge xx@(x:xs) yy@(y:ys)
| x < y = x : merge xs yy
| otherwise = y : merge xx ys
mergesort [] = []
mergesort xs = merge (mergesort p) (mergesort q)
where (p,q) = split xs
GHC推断出类似的类型:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
Damas–Hindley–Milner algorithm如何推断这种类型?
答案 0 :(得分:30)
这确实是一个了不起的例子;基本上,在编译时检测到一个无限循环!在这个例子中,Hindley-Milner推论没有什么特别之处;它只是照常进行。
请注意,ghc正确获取split
和merge
的类型:
*Main> :t split
split :: [a] -> ([a], [a])
*Main> :t merge
merge :: (Ord t) => [t] -> [t] -> [t]
现在谈到mergesort
时,对于某些类型t 1 <,它通常是一个函数t 1 →t 2 / sub>和t 2 。然后它看到第一行:
mergesort [] = []
并且意识到t 1 和t 2 必须是列表类型,比如t 1 = [t 3 ]和t 2 = [t 4 ]。因此mergesort必须是函数[t 3 ]→[t 4 ]。下一行
mergesort xs = merge (mergesort p) (mergesort q)
where (p,q) = split xs
告诉它:
p
和q
的类型也是[t 3 ],因为split
是[a]→([a],[a] )mergesort p
(回想一下,mergesort被认为类型为[t 3 ]→[t 4 ])类型为[t] <子> 4 子>]。mergesort q
的类型为[t 4 ],原因完全相同。merge
的类型为(Ord t) => [t] -> [t] -> [t]
,而表达式merge (mergesort p) (mergesort q)
中的输入均为[t 4 ]类型,因此类型为t 4 必须在Ord
。merge (mergesort p) (mergesort q)
的类型与其输入相同,即[t 4 ]。这符合mergesort
以前已知的类型[t 3 ]→[t 4 ],因此不再需要进行推论和“统一” “Hindley-Milner算法的一部分已经完成。 mergesort
的类型为[t 3 ]→[t 4 ],Ord
中的t 4 。 这就是你得到的原因:
*Main> :t mergesort
mergesort :: (Ord a) => [t] -> [a]
(以上关于逻辑推理的描述与算法的描述相同,但算法遵循的具体步骤顺序就是维基百科页面上给出的步骤。)
答案 1 :(得分:2)
可以推断出该类型,因为它看到您将mergesort
的结果传递给merge
,后者又将列表的头部与<
进行比较,后者是{{1}}的一部分。 Ord类型类。因此类型推断可以推断它必须返回Ord实例的列表。当然,因为它实际上是无限地递归,所以我们无法推断出它实际上没有返回的类型。