Tenho três funções que encontram o enésimo elemento 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á algo sobre as outras duas implementações que as tornaria preferíveis? E, por extensão, como você escolheria entre usar guardas, declarações if-then-else e casos?
haskell
if-statement
case
nucleartídeo
fonte
fonte
case
instruções aninhadas se você usoucase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
case compare a 1 of ...
Respostas:
Do ponto de vista técnico, todas as três versões são equivalentes.
Dito isso, minha regra para estilos é que se você pode ler como se fosse inglês (leia
|
como "quando",| otherwise
como "caso contrário" e=
como "é" ou "seja"), provavelmente você está fazendo algo certo.if..then..else
é para quando você tem uma condição binária ou uma única decisão que precisa tomar.if..then..else
Expressões aninhadas são muito incomuns em Haskell, e guardas quase sempre devem ser usados em seu lugar.Cada
if..then..else
expressão pode ser substituída por um guarda se estiver no nível superior de uma função, e isso geralmente deve ser preferido, já que você pode adicionar mais casos mais facilmente do que:case..of
é para quando você tem vários caminhos de código e cada caminho de código é guiado pela estrutura de um valor, ou seja, por meio de correspondência de padrões. Você raramente combina emTrue
eFalse
.Os guardas complementam as
case..of
expressões, o que significa que se você precisar tomar decisões complicadas dependendo de um valor, primeiro tome decisões dependendo da estrutura de sua entrada e, em seguida, tome decisões sobre os valores na estrutura.BTW. Como dica de estilo, sempre faça uma nova linha depois de a
=
ou antes de a|
se o material depois de=
/|
for muito longo para uma linha ou usar mais linhas por algum outro motivo:fonte
True
eFalse
" há alguma ocasião em que você faria isso? Afinal, esse tipo de decisão sempre pode ser feito com umif
, e com os guardas também.case (foo, bar, baz) of (True, False, False) -> ...
guard
função requerMonadPlus
, mas o que estamos falando aqui são guardas como em| test =
cláusulas, que não estão relacionadas.Eu sei que esta é uma questão sobre o estilo para funções recursivas explicitamente, mas eu sugeriria que o melhor estilo é encontrar uma maneira de reutilizar funções recursivas existentes.
fonte
É só uma questão de ordem, mas acho muito legível e tem a mesma estrutura dos guardas.
O último else não precisa e se não houver outras possibilidades, as funções também devem ter "último recurso" caso você tenha perdido alguma coisa.
fonte