繁体   English   中英

如果没有我们在数据类型上定义Eq,Haskell如何进行模式匹配?

[英]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类型的构造函数。

模式匹配反向构建。 您不是向构造函数提供两个值,而是为您提供适当类型的值,而是为构造函数提供类型的值,并为您提供构成该类型的组成值。 语言知道区分联合的哪个分支用于构造值,因此它可以匹配正确的模式。 因此,不需要相等,因为它只是构造函数和变量 - 没有实际值进行比较(或者甚至必须进行评估)。

关于你对Ints的编辑:

是的,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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM