[英]How does Haskell do pattern matching without us defining an Eq on our data types?
我已经定义了一个二叉树:
data Tree = Null | Node Tree Int Tree
并实现了一个函数,它将产生所有节点的值的总和:
sumOfValues :: Tree -> Int
sumOfValues Null = 0
sumOfValues (Node Null v Null) = v
sumOfValues (Node Null v t2) = v + (sumOfValues t2)
sumOfValues (Node t1 v Null) = v + (sumOfValues t1)
sumOfValues (Node t1 v t2) = v + (sumOfValues t1) + (sumOfValues t2)
它按预期工作。 我有想法也尝试使用警卫来实现它:
sumOfValues2 :: Tree -> Int
sumOfValues2 Null = 0
sumOfValues2 (Node t1 v t2)
| t1 == Null && t2 == Null = v
| t1 == Null = v + (sumOfValues2 t2)
| t2 == Null = v + (sumOfValues2 t1)
| otherwise = v + (sumOfValues2 t1) + (sumOfValues2 t2)
但是这个没有用,因为我没有实现Eq
,我相信:
No instance for (Eq Tree) arising from a use of `==' at zzz3.hs:13:3-12 Possible fix: add an instance declaration for (Eq Tree) In the first argument of `(&&)', namely `t1 == Null' In the expression: t1 == Null && t2 == Null In a stmt of a pattern guard for the definition of `sumOfValues2': t1 == Null && t2 == Null
那么,必须要做的问题是Haskell如何在不知道何时传递的参数匹配的情况下进行模式匹配,而不依赖于Eq
?
你的论证似乎围绕着这样一个事实:Haskell确实没有比较函数的参数,而是在“形式”和签名类型上知道要匹配哪个子函数。 但是这个怎么样?
f :: Int -> Int -> Int
f 1 _ = 666
f 2 _ = 777
f _ 1 = 888
f _ _ = 999
运行f 2 9
,是否必须使用Eq
才能知道哪一个子功能是正确的? 所有这些都是相同的(与我的Tree / Node / Null时的初始Tree示例相反)。 或者是Int
的实际定义
data Int = -2^32 | -109212 ... | 0 | ... +2^32
?
对于模式匹配,Haskell使用值的结构和使用的构造函数。 例如,如果您评估
sumOfValues (Node Null 5 (Node Null 10 Null))
它从上到下检查模式:
第一个, Null
,不匹配,因为它有不同的结构
第二个(Node Null v Null)
不匹配,因为最后一个组件Null
具有与(Node Null 10 Null)
不同的结构(模式匹配递归地进行)
第三个匹配v
绑定到5和t2
绑定到(Node Null 10 Null)
。
Eq
和它定义的==运算符是一个不相关的机制; 使Tree
成为Eq
的实例不会改变模式匹配的工作方式。
我认为你的想法有点落后:模式匹配是在Haskell中使用值的最基本方式; 除了像Int
类的一些基本类型, Eq
是使用模式匹配实现的,而不是相反。
事实证明,数字文字是一种特殊情况。 根据Haskell报告( 链接 ):
如果v == k ,则将数字,字符或字符串文字模式k与值v匹配成功,其中==基于模式的类型重载。
Haskell知道使用什么类型的构造函数来构造特定的实例,这就是成功模式匹配所需的全部内容。
你缺少的是你假设Null
是一些像C或Java那样的常量值。 它不是 - 它是Tree类型的构造函数。
模式匹配反向构建。 您不是向构造函数提供两个值,而是为您提供适当类型的值,而是为构造函数提供类型的值,并为您提供构成该类型的组成值。 语言知道区分联合的哪个分支用于构造值,因此它可以匹配正确的模式。 因此,不需要相等,因为它只是构造函数和变量 - 没有实际值进行比较(或者甚至必须进行评估)。
是的,Int基本上是一个类型,在-2 | -1 | 0 | 1 | 2 | 3 | 4 …
的行上有大量的构造函数 -2 | -1 | 0 | 1 | 2 | 3 | 4 …
-2 | -1 | 0 | 1 | 2 | 3 | 4 …
它有点手工,但它在实践中有效。
在某些时候,当您不想使用Eq时,您需要模式匹配。 你可以定义
isEmpty :: Tree -> Bool
isEmpty Null = True
isEmpty _ = False
sumOfValues2 :: Tree -> Int
sumOfValues2 Null = 0
sumOfValues2 (Node t1 v t2)
| isEmpty t1 && isEmpty t2 = v
| isEmpty t1 = v + (sumOfValues2 t2)
| isEmpty t2 = v + (sumOfValues2 t1)
| otherwise = v + (sumOfValues2 t1) + (sumOfValues2 t2)
顺便说一下,你不需要所有这些情况,只需这样:
sumOfValues :: Tree -> Int
sumOfValues Null = 0
sumOfValues (Node t1 v t2) = v + (sumOfValues t1) + (sumOfValues t2)
您可以将数据构造函数视为标记。 要对Tree
的值进行模式匹配,编译后的代码将提取标记,并且只知道要分派到哪个分支。 它并不关心真正的价值,所以你不需要Eq
。
模式匹配取决于语法。 例如,如果你编写一个涉及构造函数的模式,那么构造函数的模式匹配就是你得到的。 如果你编写一个涉及litteral表达式的模式(并且litteral浮点值或整数就是这样),你可以使用你的类型可能提供的(==)运算符的任何重载定义来获得一个相等的测试(来自Eq类)。
因此,如果你重载你的Fred数据类型,它是类Num并且有一个构造函数,也称为Fred你可以通过检查给定的Integer是否来定义与Integers的相等性。现在在那个奇怪的场景中,除非我忽略了某些东西,以下应该工作:
getFred :: Fred -- get a Fred
getFred = Fred -- invoke the Fred constructor
testFred :: Fred -> Bool -- tests if Fred's equality operator considers 1 equal to Fred
testFred 1 = True -- overloaded (==) tests against 1, so should be true
testFred _ = False -- shouldn't happen, then...
isFredOne = testFred $ getFred -- returns True
我通过说模式匹配是Haskell的核心来证明这种类型的东西--Eq类型类不是。
因此虽然存在类似于需要Eq的模式匹配的特征,但它们不是模式匹配并且可以在模式匹配之上实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.