Riccardo 是正确的,GHC 并没有推断出你的警卫不可能都是假的。所以请接受他的回答。
我要跑题了,谈谈编码风格。
您不使用的动机otherwise
可能是它看起来难看:
insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right)
| x == a = Node a left right
| x < a = Node a (insert x left) right
| otherwise = Node a left (insert x right)
看着这段代码,人类读者必须向自己确认最终的守卫恰好接受了那些x > a
.
我们可以改为这样写:
insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right) = case x `compare` a of
EQ -> Node a left right
LT -> Node a (insert x left) right
GT -> Node a left (insert x right)
返回的Ordering
类型compare
只有、 和三个值EQ
,因此 GHC 可以确认您已经涵盖了所有可能性,并且人类读者可以很容易地看到您已经正确地涵盖了它们。LT
GT
这也是更高效的代码:我们调用compare
一次,而不是调用==
然后可能也调用<
。
现在我要离题一些,谈谈懒惰。
您可能还编写了一个类似的函数:
contains :: (Ord a) => a -> Tree a -> Bool
contains _ EmptyTree = False
contains x (Node a left right) = case x `compare` a of
EQ -> True
...
当 时x == a
,您需要知道树使用Node
构造函数,并且它的第一个参数等于x
。您不需要知道任何一个子树是什么。
但现在回头看看我对insert
上面的定义。当它给出的树是 aNode
时,它总是返回 a ,它Node
的第一个参数是 always a
。但它并没有预先说明:而是评估x `compare` a
.
我们可以重写insert
以尽可能晚地执行比较:
insert :: (Ord a) => a -> Tree a -> Tree a
insert x EmptyTree = Node x EmptyTree EmptyTree
insert x (Node a left right) = Node a newLeft newRight
where comparison = x `compare` a
newLeft = if comparison == LT then insert x left else left
newRight = if comparison == GT then insert x right else right
现在我们Node a
尽快返回该位 --- 即使比较抛出错误!--- 我们仍然最多执行一次比较。