В функциональном программировании считается неправильной практикой иметь неполные сопоставления шаблонов
Обычно считается неправильной практикой использовать не исчерпывающие шаблонные манипуляции на функциональных языках, таких как 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 очень хорошо говорит о проблеме...
Ответы
Ответ 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, часто используют бесконечные ленивые списки, где пустой список не может возникнуть, поэтому они (исторически) выбрали краткость по сравнению с исправленной статически проверкой.