Haskell 模式匹配 - 它是啥?
Posted
技术标签:
【中文标题】Haskell 模式匹配 - 它是啥?【英文标题】:Haskell pattern matching - what is it?Haskell 模式匹配 - 它是什么? 【发布时间】:2011-01-14 14:37:35 【问题描述】:什么是 Haskell 中的模式匹配,它与受保护的方程有什么关系?
我试图寻找一个简单的解释,但我没有找到。
编辑: 有人标记为作业。我不再上学了,我只是在学习 Haskell,我正在尝试理解这个概念。纯粹出于兴趣。
【问题讨论】:
或许也应该包含 F# 中的模式匹配概念…… 大量语言都有模式匹配,不仅仅是 Haskell 和 F#。 这是纯函数和约束语言的共同特征。例如,Prolog、Erlang 和 SML。 @Tony:***的文章看起来相当不错。模式匹配是 Haskell、Scala 等语言的基础。我认为了解语言对于理解模式匹配至关重要。我会继续学习 Haskell,而不用担心过早地找到模式匹配的定义。理解终将到来! @Tony,我的意思是,任何对 Haskell 的介绍都会让您以“舒适”的方式熟悉模式匹配。如果不了解类型、绑定等,您将无法真正理解模式匹配,无论如何您都会一路学习。一旦您编写了一些对单个值进行操作的函数,然后您将“获得”元组和列表。它与 C# 非常不同。 FWIW,Scala 可能会引起您的兴趣,它是基于 OO 和 Java 的函数式编程混合体(让我们面对它与 .NET 非常相似!)。 【参考方案1】:简而言之,模式就像在数学中定义分段函数。您可以使用模式为不同的参数指定不同的函数体。当您调用函数时,通过将实际参数与各种参数模式进行比较来选择适当的主体。阅读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。 This F# doc 也很酷。 是否可以合并 case 0 和 1?像 fib (0 | 1) = 1 这样的东西?【参考方案2】:在函数式语言中,模式匹配涉及根据不同形式检查参数。一个简单的示例涉及对列表进行递归定义的操作。我将使用 OCaml 来解释模式匹配,因为它是我选择的函数式语言,但在 F# 和 Haskell、AFAIK 中的概念是相同的。
这是计算列表长度的函数的定义lst
。在 OCaml 中,`a listis defined recursively as the empty list
[], or the structure
h::t, where
his an element of type
a(
abeing any type we want, such as an integer or even another list),
tis a list (hence the recursive definition), and
::` 是 cons 运算符,它创建一个一个元素和一个列表的新列表。
所以函数看起来像这样:
let rec len lst =
match lst with
[] -> 0
| h :: t -> 1 + len t
rec
是一个修饰符,它告诉 OCaml 函数将递归调用自身。不要担心那部分。 match
声明是我们关注的重点。 OCaml 将根据两种模式检查lst
- 空列表或h :: t
- 并基于此返回不同的值。由于我们知道每个列表都会匹配其中一种模式,因此我们可以放心,我们的函数将安全返回。
请注意,尽管这两种模式可以处理所有列表,但您并不局限于它们。 h1 :: h2 :: t
之类的模式(匹配所有长度为 2 或以上的列表)也是有效的。
当然,模式的使用不限于递归定义的数据结构或递归函数。这是一个(人为的)函数来告诉你一个数字是 1 还是 2:
let is_one_or_two num =
match num with
1 -> true
| 2 -> true
| _ -> false
在这种情况下,我们的模式的形式就是数字本身。 _
是一个特殊的包罗万象,用作默认情况,以防上述模式都不匹配。
【讨论】:
【参考方案3】:至少在 Haskell 中,模式匹配与 algebraic data types 的概念紧密相关。当您声明这样的数据类型时:
data SomeData = Foo Int Int
| Bar String
| Baz
...它将Foo
、Bar
和Baz
定义为构造函数——不要与OOP 中的“构造函数”混淆——构造一个SomeData
值其他值。
模式匹配只不过是反向进行——模式会将SomeData
值“解构”成它的组成部分(事实上,我相信模式匹配是唯一在 Haskell 中提取值的方法)。
当一个类型有多个构造函数时,您为每个模式编写函数的多个版本,并根据使用的构造函数选择正确的版本(假设您编写了模式来匹配所有可能的构造——这通常是一种很好的做法)。
【讨论】:
“函数的多个版本”实际上只是 case 语句的语法糖:learnhaskell.blogspot.com/2007/09/lesson-3-case-3.html【参考方案4】:模式匹配是一种痛苦的操作,如果您具有过程编程背景,则很难理解。我觉得很难进入,因为用于创建数据结构的相同语法可用于匹配。
在 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
【讨论】:
"... 用于创建数据结构的相同语法可用于匹配" - 这就是重点;您使用模式匹配来找出用于生成值的构造函数 - 请参阅 Norman Ramsey 或 camccann 的答案。它还有助于认识到 cons 不仅仅是一个作用于列表的函数(如长度或连接),而是一个列表构造函数。当然,正如斐波那契示例所示,您可以对特定值(例如 0 或 1)以及构造函数(例如 cons 或Just
(Haskell 中的常见))进行模式匹配。【参考方案5】:
还有其他很好的答案,所以我会给你一个非常技术性的答案。模式匹配是代数数据类型的消除构造:
“消除构造”的意思是“如何消费或使用价值”
“代数数据类型”,除了一流的函数,是 Clean、F#、Haskell 或 ML 等静态类型函数语言中的重要思想
代数数据类型的概念是定义一种事物的类型,然后说出可以制作该事物的所有方法。举个例子,让我们将“Sequence of String”定义为代数数据类型,可以通过三种方式实现:
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
表达式上加一个警卫来专门化一个方程或替代方案。对于序列示例,我想不出一个合理的保护措施,其他答案中有很多示例,所以我将其留在那里。
【讨论】:
以上是关于Haskell 模式匹配 - 它是啥?的主要内容,如果未能解决你的问题,请参考以下文章