Haskell中的模式匹配是什么?它与保护方程有什么关系?
我试过寻找一个简单的解释,但我还没找到。
编辑: 有人标记为家庭作业。我不再去上学了,我只是在学习Haskell,而我正在努力理解这个概念。纯粹出于兴趣。
答案 0 :(得分:59)
简而言之,模式就像在数学中定义分段函数一样。您可以使用模式为不同的参数指定不同的函数体。调用函数时,通过将实际参数与各种参数模式进行比较来选择适当的主体。请阅读A Gentle Introduction to Haskell以获取更多信息。
比较
使用等效的Haskell:
fib 0 = 1
fib 1 = 1
fib n | n >= 2
= fib (n-1) + fib (n-2)
请注意,分段函数中的“ n ≥2”在Haskell版本中成为一个保护,但其他两个条件只是模式。模式是测试值和结构的条件,例如x:xs
,(x, y, z)
或Just x
。在分段定义中,基于=
或∈
关系的条件(基本上,说“某事”的条件是“其他东西”)成为模式。警卫允许更一般的条件。我们可以重写fib
来使用警卫:
fib n | n == 0 = 1
| n == 1 = 1
| n >= 2 = fib (n-1) + fib (n-2)
答案 1 :(得分:25)
还有其他好的答案,所以我会给你一个非常技术性的答案。模式匹配是代数数据类型的消除构造 :
“消除构造”是指“如何消费或使用价值”
除了一流函数外,“代数数据类型”是静态类型函数语言中的一个重要思想,如Clean,F#,Haskell或ML
代数数据类型的概念是你定义一种东西,然后你说出你可以制作那种东西的所有方法。例如,让我们将“字符串序列”定义为代数数据类型,有三种方法可以实现:
data StringSeq = Empty -- the empty sequence
| Cat StringSeq StringSeq -- two sequences in succession
| Single String -- a sequence holding a single element
现在,这个定义存在各种各样的错误,但作为一个例子,它很有趣,因为它提供了任意长度序列的恒定时间连接。 (还有其他方法可以实现这一点。)声明引入了Empty
,Cat
和Single
,它们是制作序列的所有方式。 (这使得每个人都成为介绍构造 - 一种创造事物的方式。)
Cat
的序列,您需要另外两个序列。Single
的序列,您需要一个元素(在本例中为字符串)这里有一个妙语:消除构造,模式匹配,为您提供了一种方法来仔细检查序列,并问问题您使用了什么构造函数?。因为您必须为任何答案做好准备,所以每个构造函数至少提供一个替代方法。这是一个长度函数:
slen :: StringSeq -> Int
slen s = case s of Empty -> 0
Cat s s' -> slen s + slen s'
Single _ -> 1
在语言的核心,所有模式匹配都建立在这个case
构造上。但是,由于代数数据类型和模式匹配对于语言的习语非常重要,因此在函数定义的声明形式中进行模式匹配时会有特殊的“语法糖”:
slen Empty = 0
slen (Cat s s') = slen s + slen s'
slen (Single _) = 1
使用这种句法糖,通过模式匹配计算看起来很像方程式的定义。 (Haskell委员会是故意这样做的。)正如你在其他答案中所看到的那样,可以通过在其上打一个守护来专门化case
表达式中的等式或替代。我不能想到序列示例的合理保护,其他答案中有很多例子,所以我会留在那里。
答案 2 :(得分:12)
至少在Haskell中,模式匹配与algebraic data types的概念密切相关。声明如下数据类型时:
data SomeData = Foo Int Int
| Bar String
| Baz
...它将Foo
,Bar
和Baz
定义为构造函数 - 不要与OOP中的“构造函数”混淆 - 从其他值构造SomeData
值。
模式匹配只不过是反向执行 - 模式会将SomeData
值“解构”为其组成部分(事实上,我相信模式匹配是仅在Haskell中提取值的方法。)
当一个类型有多个构造函数时,为每个模式编写一个函数的多个版本,根据使用的构造函数选择正确的函数(假设您已经编写了模式以匹配所有可能的构造 - 这通常是一种很好的做法。
答案 3 :(得分:3)
在函数式语言中,模式匹配涉及检查不同形式的参数。一个简单的例子涉及对列表进行递归定义的操作。我将使用OCaml来解释模式匹配,因为它是我选择的函数式语言,但F#和Haskell,AFAIK中的概念是相同的。
以下是计算列表lst
长度的函数的定义。在OCaml中,一个列表is defined recursively as the empty list
[] , or the structure
h :: t , where
h is an element of type
一个(
一个being any type we want, such as an integer or even another list),
t {{ 1}} ::`是cons运算符,它从元素和列表中创建一个新列表。
所以函数看起来像这样:
is a list (hence the recursive definition), and
let rec len lst =
match lst with
[] -> 0
| h :: t -> 1 + len t
是一个修饰符,告诉OCaml函数将递归调用自身。不要担心那部分。 rec
声明是我们关注的重点。 OCaml将针对两种模式检查match
- 空列表或lst
- 并根据该值返回不同的值。由于我们知道每个列表都会匹配其中一个模式,因此我们可以放心,我们的功能将安全返回。
请注意,即使这两种模式都会处理所有列表,但您不仅限于此。像h :: t
这样的模式(匹配长度为2或更长的所有列表)也是有效的。
当然,模式的使用不限于递归定义的数据结构或递归函数。这是一个(人为的)函数来告诉你数字是1还是2:
h1 :: h2 :: t
在这种情况下,我们的模式形式就是数字本身。 let is_one_or_two num =
match num with
1 -> true
| 2 -> true
| _ -> false
是一个特殊的catch-all用作默认情况,以防上述模式都不匹配。
答案 4 :(得分:1)
模式匹配是一种痛苦的操作,如果你来自程序编程背景,很难让人感觉到。我发现很难进入,因为用于创建数据结构的相同语法可用于匹配。
在F#中,您可以使用cons运算符::
将元素添加到列表的开头,如下所示:
let a = 1 :: [2;3]
//val a : int list = [1; 2; 3]
类似地,您可以使用相同的运算符来分割列表,如下所示:
let a = [1;2;3];;
match a with
| [a;b] -> printfn "List contains 2 elements" //will match a list with 2 elements
| a::tail -> printfn "Head is %d" a //will match a list with 2 or more elements
| [] -> printfn "List is empty" //will match an empty list