Haskell模式匹配 - 它是什么?

时间:2010-02-08 23:51:35

标签: haskell functional-programming pattern-matching

Haskell中的模式匹配是什么?它与保护方程有什么关系?

我试过寻找一个简单的解释,但我还没找到。

编辑: 有人标记为家庭作业。我不再去上学了,我只是在学习Haskell,而我正在努力理解这个概念。纯粹出于兴趣。

5 个答案:

答案 0 :(得分:59)

简而言之,模式就像在数学中定义分段函数一样。您可以使用模式为不同的参数指定不同的函数体。调用函数时,通过将实际参数与各种参数模式进行比较来选择适当的主体。请阅读A Gentle Introduction to Haskell以获取更多信息。

比较

Fibonacci sequence

使用等效的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

现在,这个定义存在各种各样的错误,但作为一个例子,它很有趣,因为它提供了任意长度序列的恒定时间连接。 (还有其他方法可以实现这一点。)声明引入了EmptyCatSingle,它们是制作序列的所有方式。 (这使得每个人都成为介绍构造 - 一种创造事物的方式。)

  • 您可以创建一个没有任何其他值的空序列。
  • 要制作包含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

...它将FooBarBaz定义为构造函数 - 不要与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