Совместимость шаблонов Haskell - что это?

Что такое сопоставление шаблонов в Haskell и как оно связано с защищенными уравнениями?

Я пробовал искать простое объяснение, но я его не нашел.

EDIT: Кто-то помечен как домашнее задание. Я больше не хожу в школу, я просто изучаю Хаскелл, и я пытаюсь понять эту концепцию. Чистый из интереса.

Ответ 1

В двух словах шаблоны похожи на определение кусочных функций в математике. Вы можете указать разные тела функций для разных аргументов, используя шаблоны. Когда вы вызываете функцию, соответствующий орган выбирается путем сравнения фактических аргументов с различными шаблонами аргументов. Подробнее читайте Нежное введение в 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)

Ответ 2

Есть и другие хорошие ответы, поэтому я дам вам очень технический ответ. Согласование шаблонов - это алгоритм исключения для алгебраических типов данных:

  • "Конструкция исключения" означает "как потреблять или использовать значение"

  • "Алгебраический тип данных", в дополнение к первоклассным функциям, является большой идеей в статически типизированном функциональном языке, таком как 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, похлопывая охрану с ним. Я не могу придумать правдоподобного стража для примера последовательности, и в других ответах есть много примеров, поэтому я оставлю его там.

Ответ 3

Согласование шаблонов, по крайней мере, в Haskell, глубоко связано с концепцией алгебраических типов данных. Когда вы объявляете тип данных следующим образом:

data SomeData = Foo Int Int
              | Bar String
              | Baz

... он определяет Foo, Bar и Baz как конструкторы - не следует путать с "конструкторами" в ООП - которые строят значение SomeData из других значений.

Согласование шаблонов - это не что иное, как делать это в обратном порядке - шаблон "деконструирует" значение SomeData в его составные части (фактически, я считаю, что сопоставление шаблонов - единственный способ извлечь значения в Haskell).

Если для типа есть несколько конструкторов, вы пишете несколько версий функции для каждого шаблона, причем правильный выбирается в зависимости от того, какой конструктор использовался (при условии, что вы написали шаблоны, соответствующие всем возможным конструкциям, - которые это вообще хорошая практика).

Ответ 4

В функциональном языке сопоставление шаблонов включает проверку аргумента в отношении разных форм. Простой пример включает рекурсивно определенные операции над списками. Я буду использовать 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 a ( 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

В этом случае формами нашего шаблона являются сами числа. _ - это специальный метод catch-all, используемый как случай по умолчанию, если ни один из указанных выше шаблонов не соответствует.

Ответ 5

Совпадение шаблонов - одна из тех болезненных операций, которые трудно получить одной головой, если вы исходите из фона процедурного программирования. Мне трудно попасть, потому что для сопоставления может использоваться тот же синтаксис, который используется для создания структуры данных.

В 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