我是Haskell的初学者,在理解我得到的警告方面遇到了一些麻烦。我已经实现了二叉树,
data Tree a = Nil | Node a (Tree a) (Tree a) deriving (Eq, Show,
Read)
它工作正常,但我在此代码上发出不完整的模式警告
get :: Ord a => a -> Tree a -> Maybe a
get _ Nil = Nothing
get x (Node v lt rt)
| x == v = Just x
| x < v = get x lt
| x > v = get x rt
它希望我匹配的模式是_ (Node _ _ _ )
。我不确定这种模式是什么意思?
答案 0 :(得分:3)
这里有两个问题。首先,数据类型:
data Tree a = Nil | Node a (Tree left) (Tree right) deriving (Eq, Show, Read)
-- ^ left? ^ right?
在您的数据定义中,您使用 left
和right
,但这些不是在数据定义的头部定义因此,这些不是类型参数。你可能想说:
data Tree a = Nil
| Node { value :: a, left :: Tree a, right :: Tree a}
deriving (Eq, Show, Read)
但现在我们仍然收到错误:
hs.hs:5:1: Warning:
Pattern match(es) are non-exhaustive
In an equation for ‘get’: Patterns not matched: _ (Node _ _ _)
Ok, modules loaded: Main.
这里的问题是 Haskell不知道两个值只能是<
,==
或>
)。
如果你写instance
Ord
,那么你有一个“联系人”,你将定义总排序。换句话说,对于任何两个值x
和y
,它都包含x < y
,x > y
或x == y
。然而问题是Haskell 不知道这一点。对于Haskell,任何函数(<)
,(==)
或(>)
都可以生成True
或False
。因此 - 由于编译器始终保守 - 它会考虑有两个值的情况,即所有x < y
,x == y
和x > y
都会失败(比如你假设会写foo x y
,bar x y
和qux x y
然后这肯定会发生,因为它们是三个 blackbox 函数。您可以通过在最后一种情况下撰写otherwise
来解决此问题:
get :: Ord a => a -> Tree a -> Maybe a
get _ Nil = Nothing
get x (Node v lt rt)
| x == v = Just x
| x < v = get x lt
| otherwise = get x rt
otherwise
是True
的别名,因此不可能不接受该分支。所以现在保守编译器理解,无论x
和y
的值是什么,它总是需要一些分支,因为如果它没有采取前两个,它将当然采取最后一个。
您可能认为这很奇怪,但由于合同通常没有以正式语言指定(仅在文档中,因此是自然语言),编译器 no < / em>意味着要知道:作为程序员,您可以决定不尊重合同(但请注意,这是非常坏主意)。即使你通常作为程序员编写正式合同,你仍然可以决定不尊重它,而且编译器也不能总是对正式合同做必要的逻辑推理。
答案 1 :(得分:2)
Willem Van Onsem已经很好地解释了这个问题。我只想补充一点, 可以在x
和v
之间进行比较,其方式与发布的代码非常相似,但其分支却被详尽无遗的编译器。
而不是
get :: Ord a => a -> Tree a -> Maybe a
get _ Nil = Nothing
get x (Node v lt rt)
| x == v = Just x
| x < v = get x lt
| x > v = get x rt
只需使用
get :: Ord a => a -> Tree a -> Maybe a
get _ Nil = Nothing
get x (Node v lt rt) = case compare x v of
EQ -> Just x
LT -> get x lt
GT -> get x rt
实际上,compare
是一个带有两个参数的函数,并在枚举类型Ordering
中返回一个值,该值只能是EQ
(相等),LT
(更少)比)和GT
(大于)。由于这是一个代数类型,GHC可以看到它的所有构造函数都由case
处理。
此外,根据实际类型a
,使用compare
可能会更有效率。例如,当比较两个可能很长的字符串时,它是次优的,以便遍历它们两次(如果不是原始代码中的三次):compare
只对一个字符串进行一次传递并确定哪个顺序关系成立