У меня есть три функции, которые находят n-й элемент списка:
nthElement :: [a] -> Int -> Maybe a
nthElement [] a = Nothing
nthElement (x:xs) a | a <= 0 = Nothing
| a == 1 = Just x
| a > 1 = nthElement xs (a-1)
nthElementIf :: [a] -> Int -> Maybe a
nthElementIf [] a = Nothing
nthElementIf (x:xs) a = if a <= 1
then if a <= 0
then Nothing
else Just x -- a == 1
else nthElementIf xs (a-1)
nthElementCases :: [a] -> Int -> Maybe a
nthElementCases [] a = Nothing
nthElementCases (x:xs) a = case a <= 0 of
True -> Nothing
False -> case a == 1 of
True -> Just x
False -> nthElementCases xs (a-1)
На мой взгляд, первая функция - лучшая реализация, потому что она наиболее лаконична. Но есть ли что-нибудь в двух других реализациях, что могло бы сделать их предпочтительнее? И, в более широком смысле, как бы вы выбрали между использованием охранников, операторов if-then-else и случаев?
haskell
if-statement
case
ядерный прилив
источник
источник
case
операторы, если использовалиcase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
case compare a 1 of ...
Ответы:
С технической точки зрения все три версии равнозначны.
При этом мое практическое правило для стилей заключается в том, что если вы можете читать его так, как если бы он был английским (читается
|
как «когда»,| otherwise
как «иначе» и=
как «есть» или «быть»), вы, вероятно, что-то делаете. право.if..then..else
когда у вас есть одно двоичное условие или одно единственное решение, которое вам нужно принять. Вложенныеif..then..else
-выражения очень редко встречаются в Haskell, и вместо них почти всегда следует использовать охранники.Каждое
if..then..else
выражение может быть заменено защитным элементом, если оно находится на верхнем уровне функции, и это, как правило, предпочтительнее, так как тогда вы можете легко добавить больше вариантов:case..of
предназначен для случаев, когда у вас есть несколько путей кода , и каждый путь кода управляется структурой значения, то есть через сопоставление с образцом. Вы очень редко сопоставляетеTrue
иFalse
.Стражи дополняют
case..of
выражения, что означает, что если вам нужно принимать сложные решения в зависимости от значения, сначала принимайте решения в зависимости от структуры вашего ввода, а затем принимайте решения по значениям в структуре.Кстати. В качестве совета по стилю всегда делайте новую строку после
=
или до,|
если материал после=
/|
слишком длинный для одной строки или использует больше строк по какой-либо другой причине:источник
True
иFalse
есть ли вообще случай, когда вы бы это сделали? В конце концов, такое решение всегда можно принять с помощьюif
охранников.case (foo, bar, baz) of (True, False, False) -> ...
guard
функция требуетMonadPlus
, но то, о чем мы говорим здесь, это охранники, как в| test =
пунктах, которые не связаны.Я знаю, что это вопрос стиля для явно рекурсивных функций, но я бы предположил, что лучший стиль - найти способ вместо этого повторно использовать существующие рекурсивные функции.
источник
Это всего лишь вопрос порядка, но я думаю, что он очень удобочитаемый и имеет ту же структуру, что и охранники.
Последнее else не нужно, и если, поскольку нет других возможностей, функции также должны иметь «крайний случай» на случай, если вы что-то пропустили.
источник