Haskell如何进行模式匹配,而不在数据类型上定义Eq?

Haskell如何进行模式匹配,而不在数据类型上定义Eq?,haskell,pattern-matching,Haskell,Pattern Matching,我定义了一个二叉树: 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 + (

我定义了一个二叉树:

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
,我相信:

那么,必须提出的问题是,Haskell如何在不知道传递的参数何时匹配的情况下进行模式匹配,而不诉诸于
Eq

编辑 您的论点似乎围绕着这样一个事实:Haskell实际上并没有比较函数的论点,而是比较签名的“形式”和类型,以了解要匹配的子函数。但是这个怎么样

f :: Int -> Int -> Int
f 1 _ = 666
f 2 _ = 777
f _ 1 = 888
f _ _ = 999
当运行
f2 9
时,它不需要使用
Eq
来知道哪个子功能是正确的吗?它们都是相等的(与我最初使用Tree/Node/Null时的树示例相反)。或者
Int
的实际定义类似于

data Int = -2^32 | -109212 ... | 0 | ... +2^32 

Haskell知道构造特定实例时使用了什么类型的构造函数,这就是它成功匹配模式所需的一切。

您缺少的是,您假设
Null
是某种常量值,如C或Java中的值。它不是-它是树类型的构造函数

模式匹配以相反的方式进行构造。不是向构造函数提供两个值,它会给您一个适当类型的值,而是向构造函数提供一个类型的值,它会给您组成该类型的组成值。该语言知道判别并集的哪个分支用于构造值,因此它可以匹配正确的模式。因此,不需要相等,因为它只是构造函数和变量-没有实际值被比较(甚至不需要计算)

关于您对Ints的编辑:
是的,Int基本上是一种具有大量构造函数的类型,这些构造函数沿着
-2 |-1 | 0 | 1 | 2 | 3 | 4…
。这有点手工操作,但在实践中是有效的。

对于模式匹配,Haskell使用值的结构和使用的构造函数。例如,如果您评估

sumOfValues (Node Null 5 (Node Null 10 Null))
它从上到下检查模式:

  • 第一个,Null,不匹配,因为它有不同的结构

  • 第二个组件
    (Node Null v Null)
    不匹配,因为最后一个组件
    Null
    的结构与
    (Node Null 10 Null)
    不同(模式匹配以递归方式进行)

  • 第三个匹配绑定到5的
    v
    和绑定到
    (Node Null 10 Null)
    t2

Eq
和它定义的==运算符是一个不相关的机制;将
Tree
作为
Eq
的实例不会改变模式匹配的工作方式

我认为您的想法有点落后:模式匹配是Haskell中使用值的最基本方式;除了一些基本类型,如
Int
Eq
是使用模式匹配实现的,而不是相反

模式匹配与数字 事实证明,数字文字是一种特殊情况。根据Haskell报告():

如果v==k,则将数字、字符或字符串文本模式k与值v匹配成功,其中==根据模式的类型重载

在某些情况下,当您不想使用公式时,您需要模式匹配。例如,您可以定义

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类)获得一个相等性测试

因此,如果您重载Fred数据类型,它是Num类,并且有一个构造函数,也称为Fred,那么您可以通过检查给定的整数是否为1来定义与整数的相等性。现在在这个奇怪的场景中,除非我忽略了什么,否则以下应该是可行的:

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,但它们不是模式匹配,可以在模式匹配的基础上实现。

当您对数字进行模式匹配时,您使用的是
Eq
类。数字文本不是构造函数。如果是,则不能使用类型
Num a=>a
。请注意,
Num
需要
Eq
。如果使用funky
Eq
实例定义
Num
实例,则模式匹配将相应地进行(即,如果
(fromInteger 42::MyInt)==fromInteger 23
为真,则模式
23
也将匹配值
42::MyInt
),并非所有32位都可以表示Int。在我的机器上,
maxBound::Int
返回2147483647,2^31-1。是的,我知道。但是对于问题的上下文,我不在乎。顺便说一句,如果你需要
Eq
进行模式匹配,那么首先你自己很难定义
Eq
实例…;-)@魏虎:事实上,他们有。考虑一下:maxBound=2147483647=2^31-1,minBound=-2147483648=2^32,然后加0,得到:2^31+2^31-1+1=
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