簡體   English   中英

Haskell 模式匹配 - 它是什么?

[英]Haskell pattern matching - what is it?

Haskell 中的模式匹配是什么,它與受保護的方程有什么關系?

我試圖尋找一個簡單的解釋,但我還沒有找到。

編輯:有人標記為家庭作業。 我不再上學了,我只是在學習 Haskell,我正在努力理解這個概念。 純粹出於興趣。

簡而言之,模式就像在數學中定義分段函數。 您可以使用模式為不同的參數指定不同的函數體。 當您調用函數時,通過將實際參數與各種參數模式進行比較來選擇適當的主體。 閱讀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)

還有其他很好的答案,所以我會給你一個非常技術性的答案。 模式匹配是代數數據類型消除構造

  • “消除構造”的意思是“如何消費或使用一個值”

  • 除了一流的函數之外,“代數數據類型”是靜態類型函數語言(如 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表達式中的替代項。 我想不出序列示例的合理保護措施,其他答案中有很多示例,因此我將其留在那里。

模式匹配,至少在 Haskell 中,與代數數據類型的概念密切相關。 當您聲明這樣的數據類型時:

data SomeData = Foo Int Int
              | Bar String
              | Baz

...它將FooBarBaz定義為構造函數——不要與 OOP 中的“構造函數”混淆——它們從其他值構造一個SomeData值。

模式匹配無非是反向執行此操作——模式會將SomeData值“解構”為其組成部分(事實上,我相信模式匹配是在 Haskell 中提取值的唯一方法)。

當一個類型有多個構造函數時,你為每個模式編寫多個版本的函數,根據使用的構造函數選擇正確的一個(假設你已經編寫了匹配所有可能的構造的模式——這通常是好的練習做)。

在函數式語言中,模式匹配涉及針對不同形式檢查參數。 一個簡單的例子涉及對列表遞歸定義的操作。 我將使用 OCaml 來解釋模式匹配,因為它是我選擇的函數式語言,但 F# 和 Haskell 中的概念是相同的,AFAIK。

這是計算列表lst長度的函數的定義。 在 OCaml 中,``a 列表is defined recursively as the empty list [] , or the structure h::t , where h is an element of type ( a being any type we want, such as an integer or even another list), t is 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

在這種情況下,我們模式的形式就是數字本身。 _是一個特殊的包羅萬象,用作默認情況,以防上述模式都不匹配。

模式匹配是那些痛苦的操作之一,如果您具有過程編程背景,則很難理解這些操作。 我發現很難進入,因為用於創建數據結構的相同語法可用於匹配。

在 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM