Подтвердить что ты не робот

В чем разница между сопоставлением образцов и гвардейцами?

Я очень новичок в 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 и Guard являются принципиально одинаковыми. Оба оценивают условие, и если true выполнит выражение, связанное с ним. Правильно ли я в своем понимании?

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

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

4b9b3361

Ответ 1

Собственно, они принципиально разные! По крайней мере, в Хаскелле, во всяком случае.

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

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

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

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

square = (^2)

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

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

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


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

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

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

Например:

Так же, как и при использовании списков, булевы выражения могут быть свободно смешаны между защитными элементами шаблонов. Например:

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


<Б > Обновление

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

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

Ответ 4

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

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

То есть, если шаблон соответствует, он следует сразу после дискриминации if-then-else. Защитник сводит эту дискриминацию напрямую к шаблону:

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

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

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