Чем отличаются Pattern Matching и Guards? - PullRequest
58 голосов
/ 11 ноября 2010

Я очень новичок в Haskell и в функциональном программировании в целом.Мой вопрос довольно простой.В чем разница между сопоставлением с образцом и защитой?

Функция с использованием сопоставления с образцом

check :: [a] -> String
check [] = "Empty"
check (x:xs) = "Contains Elements"

Функция с использованием защит

check_ :: [a] -> String
check_ lst
    | length lst < 1 = "Empty"
    | otherwise = "Contains elements"

Мне кажется, что Pattern Matching и Guards в основном одинаковы.Оба вычисляют условие и, если true, выполнят выражение, подключенное к нему.Правильно ли я понимаю?

В этом примере я могу использовать либо сопоставление с образцом, либо охрану для достижения того же результата.Но что-то говорит мне, что я упускаю что-то важное здесь.Можем ли мы всегда заменить одно на другое?

Может ли кто-нибудь привести примеры, когда сопоставление с образцом предпочтительнее, чем у охранников, и наоборот?

Ответы [ 4 ]

57 голосов
/ 11 ноября 2010

На самом деле, они принципиально разные! По крайней мере, в Haskell, во всяком случае.

Охранники являются и более простыми, и более гибкими: по сути, это просто специальный синтаксис, который преобразуется в серию выражений if / then. Вы можете помещать произвольные логические выражения в охранники, но они не делают ничего, что вы не могли бы сделать с обычным if.

Сопоставление с образцом делает несколько дополнительных вещей: это единственный способ деконструировать данные , и они связывают идентификаторы в своей области действия . В том же смысле, что охранники эквивалентны if выражениям, сопоставление с образцом эквивалентно case выражениям. Объявления (либо на верхнем уровне, либо в чем-то подобном выражению let) также являются формой сопоставления с шаблоном, причем "нормальные" определения совпадают с тривиальным шаблоном, одним идентификатором.

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

Кстати, вы можете сделать сопоставление с образцом в объявлениях верхнего уровня:

square = (^2)

(one:four:nine:_) = map square [1..]

Это иногда полезно для группы связанных определений.

GHC также предоставляет расширение ViewPatterns , которое объединяет оба; Вы можете использовать произвольные функции в контексте привязки, а затем сопоставить шаблон с результатом. Конечно, это обычный синтаксический сахар для обычных вещей.


Что касается повседневного вопроса о том, где и где пользоваться, вот несколько приблизительных указаний:

  • Определенно используйте сопоставление с образцом для всего, что может быть сопоставлено непосредственно с одним или двумя конструкторами, когда вас не интересуют составные данные в целом, но вам важна большая часть структуры. Синтаксис @ позволяет вам привязать общую структуру к переменной, а также сопоставить ее с шаблоном, но выполнение слишком большого количества этого в одном шаблоне может быстро стать уродливым и нечитабельным.

  • Определенно используйте охранники, когда вам нужно сделать выбор на основе некоторого свойства, которое не соответствует аккуратно шаблону, например, сравнивая два значения Int, чтобы увидеть, какое из них больше.

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

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

  • Определенно, не используйте охрану для вещей, которые могут быть тривиально проверены с помощью шаблона. Классическим примером является проверка пустых списков, для этого используйте сопоставление с шаблоном.

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

10 голосов
/ 11 ноября 2010

Мне кажется, что Pattern Matching и Guards в основном одинаковы. Оба вычисляют условие и, если true, выполнят выражение, подключенное к нему. Правильно ли я понимаю?

Не совсем. Первое сопоставление с образцом не может оценить произвольные условия. Он может только проверить, было ли создано значение с использованием данного конструктора.

Второе сопоставление с образцом может связывать переменные. Таким образом, хотя шаблон [] может быть эквивалентен охраннику null lst (без использования длины, потому что это не будет эквивалентно - об этом позже), шаблон x:xs наверняка не эквивалентен охраннику not (null lst) потому что шаблон связывает переменные x и xs, чего нет у охранника.

Примечание по использованию length: использование length для проверки того, пуст ли список, является очень плохой практикой, потому что для вычисления длины необходимо пройти весь список, что займет O(n) время, в то время как простая проверка, является ли список пустым, занимает O(1) время с null или сопоставлением с образцом. Дальнейшее использование `length´ просто не работает в бесконечных списках.

9 голосов
/ 11 ноября 2010

Например, вы можете поместить логические выражения в охрану.

Например :

Как и в случае со списками, логические выражения можно свободно смешивать с охранниками шаблонов. Например:

f x | [y] <- x
    , y > 3
    , Just z <- h y
    = ...

<час /> Обновление

Есть хорошая цитата из Learn You a Haskell о разнице:

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

5 голосов
/ 11 ноября 2010

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

f y = ...
f x =
  if p(x) then A else B

То есть, если шаблон соответствует, сразу за ним следует различение «если-то-еще».Охранник сворачивает это различение в сопоставление с образцом напрямую:

f y = ...
f x | p(x) = A
    | otherwise = B

(otherwise определено как True в стандартной библиотеке).Это более удобно, чем цепочка if-then-else, и иногда это также делает код намного проще для варианта, поэтому его легче писать, чем конструкцию if-then-else.

Другими словами,это сахар поверх другой конструкции, которая во многих случаях значительно упрощает ваш код.Вы обнаружите, что это устраняет множество цепочек if-then-else и делает ваш код более читабельным.

...