Охранники против «если-то-еще» против дел в Хаскеле - PullRequest
97 голосов
/ 19 февраля 2012

У меня есть три функции, которые находят 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)

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

Ответы [ 3 ]

114 голосов
/ 19 февраля 2012

С технической точки зрения все три версии эквивалентны.

При этом мое эмпирическое правило заключается в том, что если вы можете читать его, как если бы он был английским (читайте | как "когда", | otherwise как "иначе" и = как "как") или "быть"), вы, вероятно, делаете что-то правильно.

if..then..else - это когда у вас одно двоичное условие или одно решение, которое вам нужно принять. Вложенные if..then..else -выражения очень необычны в Haskell, и вместо них почти всегда следует использовать охрану.

let absOfN =
  if n < 0 -- Single binary expression
  then -n
  else  n

Каждое выражение if..then..else может быть заменено защитным символом, если оно находится на верхнем уровне функции, и это, как правило, предпочтительнее, так как вы можете добавить больше случаев проще, чем:

abs n
  | n < 0     = -n
  | otherwise =  n

case..of предназначен для случаев, когда у вас есть несколько путей кода , и каждый путь кода определяется структура значения, т. Е. Через сопоставление с образцом. Вы очень редко подходите к True и False.

case mapping of
  Constant v -> const v
  Function f -> map f

Guards дополняет case..of выражений, что означает, что если вам нужно принимать сложные решения в зависимости от значения, first принимает решения в зависимости от структуры вашего ввода, а затем make решения о значениях в структуре.

handle  ExitSuccess = return ()
handle (ExitFailure code)
  | code < 0  = putStrLn . ("internal error " ++) . show . abs $ code
  | otherwise = putStrLn . ("user error " ++)     . show       $ code

Кстати. В качестве подсказки стиля всегда делайте новую строку после = или перед |, если материал после = / | слишком длинный для одной строки, или использует больше строк по какой-то другой причине:

-- NO!
nthElement (x:xs) a | a <= 0 = Nothing
                    | a == 1 = Just x
                    | a > 1 = nthElement xs (a-1)

-- Much more compact! Look at those spaces we didn't waste!
nthElement (x:xs) a
  | a <= 0    = Nothing
  | a == 1    = Just x
  | otherwise = nthElement xs (a-1)
22 голосов
/ 19 февраля 2012

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

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)
1 голос
/ 23 мая 2014

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

nthElement :: [a] -> Int -> Maybe a 
nthElement [] a = Nothing
nthElement (x:xs) a = if a  < 1 then Nothing else
                      if a == 1 then Just x
                      else nthElement xs (a-1)

Последнее не нужно, и, если нет других возможностей, функции также должны иметь «крайний случай», если вы что-то пропустили.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...