Контрольные заявления в Haskell?

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

Ответ 1

Есть ли общепринятый/более быстрый/более эффективный способ, чем остальные?

Гвардейцы (довольно сложный) синтаксический сахар для if-then-else после сопоставления с образцом. If-then-else - синтаксический сахар для case над Bool. Таким образом, эти вещи в основном одинаково эффективны.

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

length xs == 0

который стоит пропорционально длине xs, где

case xs of { [] -> True; _:_ -> False }

стоит постоянное время.

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

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

Ответ 2

Ну, я не знаю, является ли мышление с точки зрения "контрольных заявлений" лучшим способом обойти это в Haskell. Тем не менее, в основном все сводится к совпадению шаблонов в конце; булевские условные выражения, такие как if ... then ... else, могут быть определены в терминах сопоставления шаблонов для конструкторов для Bool, например.

Наиболее "примитивной" формой, вероятно, является оператор case - соответствие шаблона для определений функций - это просто синтаксический сахар над определением одной функции, содержащим одно большое выражение case.

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

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

Ответ 3

Ни один из трех вариантов не делает то же самое или может использоваться во всех ситуациях.

Шаблоны соответствия проверяют, какой конструктор использовался для создания заданного значения, и привязывают переменные. Ни то, ни другое охранники не делают этого. Вы могли использовать их только вместо совпадений шаблонов, если вы сопоставляете с конструктором с нулевым значением (или литералом числа) типа, реализующего Eq.

Пример:

foo (Just x) = x+1 -- Can not do this without a pattern match (except by using
                   -- functions like fromJust that themselves use pattern matches)
foo Nothing = 0 -- You could do this using a pattern guards like
                -- foo x | x==Nothing = 0, but that is less readable and less
                -- concise than using a plain pattern match

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

maybeSqrt (Just x) | x >= 0 = sqrt x
maybeSqrt _ = Nothing

Использование, если это будет выглядеть так (обратите внимание на повторение Nothing):

maybeSqrt (Just x) = if x >= 0 then sqrt x
                     else Nothing
maybeSqrt _ = Nothing

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

Ответ 4

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