Guardas vs. if-then-else vs. casos em Haskell

Tenho três funções que encontram o elemento n º de uma lista:

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)
Na minha opinião, a primeira função é a melhor implementação, porque é a mais concisa. Mas há alguma coisa sobre as outras duas implementações que os tornaria preferíveis? E por extensão, como você escolheria entre usar guardas, se-então-outras declarações, e casos?

Author: Gilles, 2012-02-19

3 answers

Do ponto de vista técnico, as três versões são equivalentes.

Dito isto, a minha regra de ouro para styles é que se você pode lê-lo como se fosse Inglês (Leia | como "quando", | otherwise como "caso contrário" e = Como "é" ou "ser"), você provavelmente está fazendo algo certo.

if..then..else é para quando você tem uma condição binária, ou uma única decisão que você precisa tomar. As expressões aninhadas if..then..else são muito raras em Haskell, e os guardas devem quase sempre em vez disso, ser usado.

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

Cada expressão if..then..else pode ser substituída por uma guarda se estiver no nível superior de uma função, e esta deve ser geralmente preferida, uma vez que pode adicionar mais casos mais facilmente então:

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

case..of é para quando você tem múltiplos caminhos de código, e cada caminho de código é guiado pelo estrutura de um valor, isto é, através da correspondência de padrões. Raramente se compara a True e False.

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

Guardas complementares case..of expressões, o que significa que se você precisar fazer complicadas decisões dependendo do valor, primeiro tomar decisões, dependendo da estrutura de sua entrada, e então tomar decisões sobre os valores na estrutura.

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

Já agora. como uma dica de estilo, faça sempre uma nova linha após um = ou antes de um | Se o material após o =/| é muito longo para uma linha, ou usa mais linhas por alguma outra razão:

-- 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)
 104
Author: dflemstr, 2012-02-19 01:15:02

Eu sei que isto é uma pergunta sobre estilo para funções explicitamente recursivas, mas eu sugeriria que o melhor estilo é encontrar uma maneira de reutilizar as funções recursivas existentes em vez disso.

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)
 21
Author: Daniel Wagner, 2012-02-19 02:36:07
Isto é apenas uma questão de ordem, mas acho que é muito legível e tem a mesma estrutura que os guardas.
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)
{[[2]} o último não precisa e se já que não há outras possibilidades, as funções também devem ter" último caso" no caso de você ter perdido alguma coisa.
 1
Author: Cristian Garcia, 2014-05-22 21:19:14