我想知道Haskell中的模式匹配是如何工作的,我知道这个帖子,但是不太明白其中的答案。
How does Haskell do pattern matching without us defining an Eq on our data types?
(==)
更通用,Eq
是通过使用模式匹配来实现的。所以你能告诉我为什么match
和match3
正在工作,即使我在下面的代码片段中省略了deriving (Eq)
,(很明显为什么match2
失败了)
data MyType = TypeA | TypeB
deriving (Eq)
match :: MyType -> String
match TypeA = "this is type A"
match TypeB = "this is type B"
match2 :: MyType -> String
match2 a | a == TypeA = "this is type A matched by equality"
| a == TypeB = "this is type B matched by equality"
| otherwise = "this is neither type A nor type B"
match3 :: MyType -> String
match3 a = case a of TypeA -> "this is type A matched by case expression"
TypeB -> "this is type B matched by case expression"
main :: IO ()
main = do
(print . match) TypeA
(print . match) TypeB
(print . match2) TypeA
(print . match2) TypeB
(print . match3) TypeA
(print . match3) TypeB
答案 0 :(得分:12)
首先,match
和match3
实际上完全相同(忽略不同的字符串):函数中的模式匹配被置于case语句中。
接下来,模式匹配适用于构造函数,而不是任意值。模式匹配内置于语言中,不依赖于布尔表达式 - 它只是直接作用于构造函数。对于包含一些匹配术语的更复杂的匹配,这一点最为明显:
f :: MyType -> Int
f (A a) = a + 1
f (B a b) = a + b
您如何将这些模式重写为布尔表达式?你不能(不知道关于MyType
的任何其他内容)。
相反,模式匹配仅仅是构造函数。每个模式必须由构造函数引导 - 您不能在Haskell中编写类似f (a b c)
的模式。然后,当函数获取值时,它可以查看值的构造函数并立即跳转到相应的情况。这是它必须适用于更复杂的模式(如A a
)的方式,也适用于您使用的简单模式。
由于模式匹配只是通过转到相应的构造函数来实现,因此它完全不依赖于Eq
。您不仅不需要Eq
实例来模式匹配,而且还有一个也不会改变模式匹配的行为方式。例如,试试这个:
data MyType = TypeA | TypeB | TypeC
instance Eq MyType where
TypeA == TypeA = True
TypeB == TypeC = True
TypeC == TypeB = True
_ == _ = False
match :: MyType → String
match TypeA = "this is type A"
match TypeB = "this is type B"
match TypeC = "this is type C"
match2 :: MyType → String
match2 a | a == TypeA = "this is type A matched by equality"
| a == TypeC = "this is type B matched by equality"
| a == TypeB = "this is type C matched by equality"
| otherwise = "this is neither type A nor type B"
现在您已经定义了相等性,TypeB
等于TypeC
但不等于它自己。 (在现实生活中,你应该确保相等的行为合理并遵循反身属性,但这是一个玩具示例。)现在,如果你使用模式匹配,TypeB
仍匹配TypeB
和{{1匹配TypeC
。但是,如果您使用保护表达式,TypeC
实际上匹配TypeB
和TypeC
匹配TypeC
。 TypeB
在两者之间没有变化。
此外,请注意TypeA
实例是如何使用模式匹配定义的。当您使用Eq
子句时,它将以与编译时生成的代码类似的方式定义。因此,模式匹配比deriving
更基础 - 它是Eq
只是标准库的一部分的语言的一部分。
总结:模式匹配是内置于语言中的,并通过比较构造函数然后以递归方式匹配其余值来工作。平等通常是在模式匹配方面实现的,并且比较整个值而不仅仅是构造函数。
答案 1 :(得分:12)
我只是想指出数据类型和模式匹配(对于第一个近似)仅仅是有用但冗余的语言特征,可以纯粹使用lambda演算来实现。如果您了解如何在lambda演算中实现它们,您就可以理解为什么他们不需要Eq
来实现模式匹配。
在lambda演算中实现数据类型被称为" Church-encoding"他们(在Alonzo教堂之后,他们演示了如何做到这一点)。教会编码的函数也被称为"延续传递风格"。
它被称为"延续传递风格"因为您不是提供值,而是提供将处理该值的函数。例如,我可以改为给他们一个以下类型的值,而不是给用户Int
,而不是给他们一个值:
type IndirectInt = forall x . (Int -> x) -> x
以上类型是"同构"到Int
。 "同构"只是一种说法,我们可以将任何IndirectInt
转换为Int
:
fw :: IndirectInt -> Int
fw indirect = indirect id
...我们可以将任何Int
转换为IndirectInt
:
bw :: Int -> IndirectInt
bw int = \f -> f int
......这样:
fw . bw = id -- Exercise: Prove this
bw . fw = id -- Exercise: Prove this
使用延续传递样式,我们可以将任何数据类型转换为lambda-calculus术语。让我们从一个简单的类型开始:
data Either a b = Left a | Right b
在延续传递方式中,这将成为:
type IndirectEither a b = forall x . (Either a b -> x) -> x
但是Alonzo Church很聪明,并注意到对于任何具有多个构造函数的类型,我们可以为每个构造函数提供单独的函数。因此,在上述类型的情况下,我们可以改为提供两个单独的函数,一个用于(Either a b-> x)
,一个用于a
,而不是提供类型为b
的函数,那会很好:
type IndirectEither a b = forall x . (a -> x) -> (b -> x) -> x
-- Exercise: Prove that this definition is isomorphic to the previous one
类似Bool
的类型,构造函数没有参数?好吧,Bool
与Either () ()
同构(练习:证明这一点),所以我们可以把它编码为:
type IndirectBool = forall x . (() -> x) -> (() -> x) -> x
并且() -> x
与x
同构(练习:证明这一点),因此我们可以进一步将其重写为:
type IndirectBool = forall x . x -> x -> x
只有两个功能可以具有上述类型:
true :: a -> a -> a
true a _ = a
false :: a -> a -> a
false _ a = a
由于同构,我们可以保证所有教会编码将具有与原始数据类型的可能值一样多的实现,因此恰好有两个函数居住{{1}并非巧合。就像IndirectBool
只有两个构造函数一样。
但我们如何在Bool
上模式匹配?例如,对于普通的IndirectBool
,我们可以写:
Bool
好吧,我们的expression1 :: a
expression2 :: a
case someBool of
True -> expression1
False -> expression2
已经附带了解构自己的工具。我们只想写:
IndirectBool
请注意,如果myIndirectBool expression1 expression2
为myIndirectBool
,它将选择第一个表达式,如果它是true
,它将选择第二个表达式,就好像我们以某种方式模式匹配它的价值。
让我们尝试用false
做同样的事情。使用普通的IndirectEither
,我们写下:
Either
使用f :: a -> c
g :: b -> c
case someEither of
Left a -> f a
Right b -> g b
,我们只需写下:
IndirectEither
简而言之,当我们以连续传递样式编写类型时,continuation就像case构造的case语句一样,所以我们所做的只是将每个不同的case语句作为参数传递给函数。
这就是您不需要someIndirectEither f g
对类型进行模式匹配的任何意义的原因。在lambda演算中,类型决定它是什么"相等"简单地通过定义从提供给它的那个参数中选择哪个参数。所以如果我是Eq
,我证明我是平等的#34;通过总是选择我的第一个参数来true
。如果我是true
,我证明我是平等的#34;通过总是选择我的第二个参数来false
。简而言之,构造者"平等"归结为"位置相等",它总是在lambda演算中定义,如果我们可以将一个参数区分为"第一个"另一个是"第二个",我们需要有能力比较"构造
答案 2 :(得分:4)
你缺少的是Haskell中的构造函数可以有参数。类型标签“本身”可以通过相等(至少在内部,幕后)进行比较,但是如果组成参数也具有可比性,那么完整值只是可比较的。
所以,如果您有类似
的类型data Maybe a = Nothing | Just a
然后,即使您可以测试类型标签是“Nothing”还是“Just”(即.;可能值上的模式匹配),通常您无法比较完整的可能,除非类型“a”的值Just所持有的也恰好具有可比性。
--note that your first and third examples are
--just syntactic sugar for each other...
matchMaybe mb = case mb of
Nothing -> "Got a Nothing"
Just _ -> "Got a Just but ignored its value"
现在还应该清楚为什么不能为Maybes编写match2的变体。在Just案例中,你将用什么来测试相等性?
matchMaybe_2 mb | mb == Nothing = "Got a Nothing"
| mb == Just {- ??? -} = "This case is impossible to write like this"
答案 3 :(得分:1)
我认为,模式匹配基本上是按位相等的。它基于类型,而不是一些抽象的价值概念。
但请记住,您应该将Int
视为
data Int = ... | -2 :: Int | -1 :: Int | 0 :: Int | 1 :: Int | 2 :: Int | ...
所以在某种程度上,每个整数都有不同的类型。
这就是为什么你可以匹配Int
说2
。
Eq
更进一步,它允许你设置相同的东西,可能不是按位相同的东西。
例如,您可能有一个存储已排序元素的二叉树。说以下内容:
A A
/ \ / \
B C B D
\ \
D C
Eq
可能被视为相等,因为它们包含相同的元素,但您无法使用模式匹配检查此处的相等性。
但是在数字的情况下,按位相等基本上与逻辑相等相同(除了正浮点和负浮点0.0
之外),所以这里Eq
和模式匹配几乎相同。
对于C ++的类比,将Eq
视为operator==
,将模式匹配视为memcmp
。您可以简单地使用memcmp
来比较许多类型的相等性,但是如果它们可以对“相等”值具有不同的表示,则可以对其进行比较。