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

В функциональном программировании считается неправильной практикой иметь неполные сопоставления шаблонов

Обычно считается неправильной практикой использовать не исчерпывающие шаблонные манипуляции на функциональных языках, таких как Haskell или F #, что означает, что указанные случаи не охватывают все возможные случаи ввода?

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

Пример:

let head (x::xs) = x

или

let head list = 
    match list with
    | x::xs -> x
    |    _  -> failwith "Applying head to an empty list"

F # (в отличие от Haskell) дает предупреждение для первого кода, так как [] -case не покрывается, но могу ли я игнорировать его, не нарушая функциональных условностей стиля ради лаконичности? MatchFailure очень хорошо говорит о проблеме...

4b9b3361

Ответ 1

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

Мои коллеги и я, работая над большим проектом OCaml (200 000 + строк), вынуждаем себя избегать частичных предупреждений о совпадении шаблонов (даже если это означает время записи | ... -> assert false) и избегать так называемых " хрупкие сопоставления шаблонов" (сопоставления с образцами, написанные таким образом, что добавление конструктора также не может быть обнаружено). Мы считаем, что выгоды от ремонтопригодности.

Ответ 2

Явный лучше, чем неявный (заимствованный из Zen Python;))

Это точно так же, как в C-переключателе через enum... Лучше писать все случаи (с провалом), а не просто ставить default, потому что компилятор скажет вам, если вы добавьте новые элементы в перечисление, и вы забыли обработать их.

Ответ 3

Я думаю, что это зависит от контекста. Вы пытаетесь написать надежный, легко отлаживаемый код или пытаетесь написать что-то простое и сжатое?

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

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

Я думаю, что это также немного зависит от типов, которые вы сопоставляете. Реально, никакие дополнительные случаи объединения не будут добавлены к типу списка, поэтому вам не нужно беспокоиться о хрупком сопоставлении. С другой стороны, в коде, который вы контролируете и активно работаете, могут быть типы, которые находятся в потоке, и добавлены дополнительные союзные случаи, что означает, что защита от хрупкого соответствия может стоить того.

Ответ 4

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

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

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

head [] = error "head: empty list"

В этом случае ответ ДА!

Ответ 5

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

Ответ 6

Этот вопрос имеет два аспекта.

  • Для пользователя API, сбой... просто выдает исключение System.Exception, которое является неспецифическим (и поэтому иногда считается само по себе плохой практикой). С другой стороны, неявное брошенное MatchFailureException может быть специально захвачено с использованием тестового шаблона типа и, следовательно, предпочтительнее.
  • Для рецензента кода реализации, сбой... четко документирует, что разработчик хотя бы немного подумал о возможных случаях и поэтому предпочтителен.

Поскольку оба аспекта противоречат друг другу, правильный ответ зависит от обстоятельств (см. также ответ kvb). Решение, которое на 100% "правильно" с любой точки зрения, должно было бы

  • обрабатывать каждый случай явно,
  • при необходимости укажите конкретное исключение и
  • четко документировать исключение

Пример:

/// <summary>Gets the first element of the list.</summary>
/// <exception cref="ArgumentException">The list is empty.</exception>
let head list = 
    match list with
    | [] -> invalidArg "list" "The list is empty." 
    | x::xs -> x

Ответ 7

Неисчерпывающие рисунки паттерна являются идиоматическими в Haskell, но очень плохой стиль в F # (и OCaml, Standard ML и т.д.).

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