Гвардії проти, якщо тоді, то проти справ у Хаскеллі


104

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

На мою думку, перша функція - це найкраща реалізація, оскільки вона є найбільш стислою. Але чи є щось щодо двох інших реалізацій, які б зробили їх кращими? І як розширення, як би ви вибрали між використанням охоронців, тверджень, тоді ще й випадків?


5
ви можете згортати свої вкладені caseвисловлювання, якщо ви використовувалиcase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
чемпіон

5
@rampion: ти маєш на увазіcase compare a 1 of ...
newacct

Відповіді:


121

З технічної точки зору всі три версії є рівнозначними.

Це означає, що якщо ви можете прочитати його так, як якщо б це було англійською мовою (читайте |як "коли", | 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

Охоронці доповнюють case..ofвирази, тобто якщо вам потрібно приймати складні рішення залежно від значення, спочатку приймайте рішення залежно від структури вашого вкладу, а потім приймайте рішення щодо значень у структурі.

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

До речі. Як підказка стилю, завжди робіть новий рядок після =або перед a, |якщо матеріал після =/ |занадто довгий для одного рядка або використовує більше рядків з іншої причини:

-- 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)

1
"Ви дуже рідко зустрічаєтесь Trueі False" чи є взагалі якийсь привід, де ви це зробили б? Зрештою, подібне рішення завжди можна приймати ifі з охоронцями.
Проблизька

2
Напр.case (foo, bar, baz) of (True, False, False) -> ...
dflemstr

@dflemstr Чи не існують більш тонкі відмінності, наприклад, охоронці, які вимагають MonadPlus і повертають екземпляр монади, тоді як якщо ні, то ще ні? Але я не впевнений.
J Fritsch

2
@JFritsch: guardфункція вимагає MonadPlus, але те, про що ми тут говоримо, є охоронцями, як у | test =пунктах, які не пов'язані між собою.
Бен Мілвуд

Дякую за підказку щодо стилю, тепер це підтверджено сумнівом.
truthadjustr

22

Я знаю, що це питання стилю для явно рекурсивних функцій, але я б припустив, що найкращим стилем є пошук способу повторного використання існуючих рекурсивних функцій.

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)

2

Це лише питання впорядкування, але я вважаю, що його дуже легко читати і має таку ж структуру, як і охоронці.

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)

Останнє ще не потрібно, і якщо немає інших можливостей, також функції повинні мати "крайній випадок", якщо ви щось пропустили.


4
Вкладені if-заяви є антидіапазоном, коли ви можете використовувати захисні пристрої.
користувач76284
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.