Зачем мне использовать падежные выражения, если я могу использовать "уравнения"?

Я изучаю Хаскелл из книги "Реальный мир Хаскелла". На страницах 66 и 67 они показывают выражения с этим примером:

fromMaybe defval wrapped =
    case wrapped of 
        Nothing    -> defval
        Just value -> value

Я помню похожую вещь в F#, но (как показано ранее в книге) Haskell может определять функции как серии уравнений; в то время как AFAIK, F Sharp не может. Поэтому я попытался определить это таким образом:

fromMaybe2 defval Nothing = defval
fromMaybe2 defval (Just value) = value

Я загрузил его в GHCi и после нескольких результатов я убедил себя, что это то же самое. это заставляет меня задаться вопросом, почему должны быть выражения падежа, когда уравнения:

  • более понятны (это математика; зачем использовать case something of, кто так говорит?);
  • менее многословны (2 против 4 строки);
  • требуют гораздо меньше структурирования и синтетического сахара (-> может быть оператором, посмотрите, что они сделали!);
  • Используйте переменные только при необходимости (в основных случаях, таких как этот wrapped просто занимает место).

Что хорошего в падежных выражениях? Они существуют только потому, что они есть в похожих языках на базе FP (таких как F#)? Я что-то пропустил?

Редактировать:

Из ответа @freyrs я вижу, что компилятор делает это точно так же. Таким образом, уравнения всегда можно превратить в выражения падежа (как и ожидалось). Мой следующий вопрос - обратное; можно пойти противоположным путем компилятора и использовать уравнения с let/where выражения, чтобы выразить любое выражение выражения?

6 ответов

Решение

Это происходит из-за наличия небольших "ядро" языков, ориентированных на выражения. Хаскель вырос из корней Лиспа (то есть лямбда-исчисления и комбинаторной логики); это в основном Lisp плюс синтаксис плюс явные определения типов данных плюс сопоставление с образцом минус мутация плюс ленивая оценка (сама ленивая оценка была впервые описана в Lisp AFAIK; т.е. в 70-х годах).

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

Уравнения не являются выражениями. Несколько уравнений могут быть каким-то образом объединены в одно выражение; вам придется ввести некоторый синтаксис для этого; case это тот синтаксис.

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


На ваш новый вопрос, да, вы можете, введя вспомогательные функции, как показывает Луис Касильяс в своем ответе, или с использованием шаблонных охранников, поэтому его примером становится:

foo x y | (Meh o p) <- z = baz y p o
        | (Gah t q) <- z = quux x t q
    where 
       z = bar x

Эти две функции компилируются в один и тот же внутренний код в Haskell (называемый Core), который вы можете выгружать, передавая флаги -ddump-simpl -dsuppress-all в GHC.

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

fromMaybe2
fromMaybe2 =
  \ @ t_aBC defval_aB6 ds_dCK ->
    case ds_dCK of _ {
      Nothing -> (defval_aB6) defval_aB6;
      Just value_aB8 -> (value_aB8) value_aB8
    }

fromMaybe
fromMaybe =
  \ @ t_aBJ defval_aB3 wrapped_aB4 ->
    case wrapped_aB4 of _ {
      Nothing -> (defval_aB3) defval_aB3;
      Just value_aB5 -> (value_aB5) value_aB5
    }

В статье "История Хаскелла: быть ленивым с классом" (PDF) дается некоторая полезная точка зрения на этот вопрос. Раздел 4.4 ("Стиль объявления против стиля выражения", стр.13) посвящен этой теме. Цитата денег:

[Мы] участвовали в яростных дебатах о том, какой стиль "лучше". Основное предположение заключалось в том, что, если возможно, должен существовать "только один способ что-то сделать", так что, например, let а также where было бы излишним и запутанным. [...] В конце концов, мы отказались от базового предположения и предоставили полную синтаксическую поддержку для обоих стилей.

По сути, они не могли договориться об одном, поэтому они добавили оба. (Обратите внимание, что цитата явно о let а также where, но они относятся как к этому выбору, так и к case Выбор между уравнениями как двумя проявлениями одного и того же базового выбора - то, что они называют "стиль объявления" против "стиля выражения".)

В современной практике стиль объявления (ваша "серия уравнений") стал более распространенным. case часто встречается в этой ситуации, когда вам нужно сопоставить значение, вычисленное по одному из аргументов:

foo x y = case bar x of
            Meh o p -> baz y p o
            Gah t q -> quux x t q

Вы всегда можете переписать это, чтобы использовать вспомогательную функцию:

foo x y = go (bar x)
    where go (Meh o p) = baz y p o
          go (Gah t q) = quux x t q

Это имеет очень незначительный недостаток: вам нужно назвать свою вспомогательную функцию, но go как правило, идеально подходит имя в этой ситуации.

Выражение падежа можно использовать везде, где ожидается выражение, а уравнения - нет. Пример:

1 + (case even 9 of True -> 2; _ -> 3)

Вы даже можете вкладывать выражения case, и я видел код, который это делает. Однако я стараюсь держаться подальше от падежных выражений и пытаюсь решить проблему с помощью уравнений, даже если мне нужно ввести локальную функцию, используя where/let,

Ответ на ваш добавленный вопрос - да, но это довольно уродливо.

case exp of
  pat1 -> e1
  pat2 -> e2
  etc.

может, я полагаю, подражать

let
  f pat1 = e1
  f pat2 = e2
  etc.
in f exp

пока f не свободен в exp, e1, e2 и т. д. Но вы не должны этого делать, потому что это ужасно.

Каждое определение, использующее уравнения, эквивалентно одному случаю использования. Например

negate True = False
negate False = True

обозначает

negate x = case x of
             True -> False
             False -> True

То есть, это два способа выразить одно и то же, и первый переводится на последний GHC.

Из кода на Haskell, который я прочитал, кажется каноничным использовать первый стиль, где это возможно.

См. Раздел 4.4.3.1 отчета Haskell '98.

Другие вопросы по тегам