130 votes

Gardes contre si-alors-sinon vs cas en Haskell

J'ai trois fonctions de trouver le n-ième élément d'une liste:

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)

À mon avis, la première fonction est la meilleure mise en œuvre, car il est le plus concis. Mais est-il rien sur les deux autres implémentations qui en ferait-elle préférable? Et par extension, comment voulez-vous choisir entre l'utilisation de gardes, if-then-else, et les cas?

150voto

dflemstr Points 18999

À partir d'un point de vue technique, toutes les trois versions sont équivalentes.

Cela étant dit, ma règle d'or pour les styles, c'est que si vous pouvez le lire comme si c'était de l'anglais (lu | "quand", | otherwise "autrement" et = "est" ou "être"), vous êtes probablement faire quelque chose de bien.

if..then..else est pour quand vous avez un binaire condition, ou d'une seule décision que vous devez faire. Imbriquée if..then..else-les expressions sont très rare en Haskell, et les gardes doivent presque toujours être utilisé à la place.

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

Chaque if..then..else expression peut être remplacée par une protection s'il est au top niveau de la fonction, ce qui devrait généralement être privilégiées, car vous pouvez ajouter plus de cas plus facilement, puis:

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

case..of est pour quand vous avez plusieurs chemins de code, et chaque chemin de code est guidée par l' la structure d'une valeur, c'est à dire par filtrage. Vous avez très rarement le match sur True et False.

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

Les gardes de compléter case..of expressions, ce qui signifie que si vous avez besoin de prendre des décisions compliquées en fonction d'une valeur, d'abord de prendre des décisions en fonction de la structure de votre entrée, et ensuite prendre des décisions sur les valeurs de la structure.

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

BTW. Comme un style de conseil, toujours faire un saut de ligne après un = ou avant un | si les choses après l' =/| est trop long pour une seule ligne, ou utilise plusieurs lignes pour une autre raison:

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

24voto

Daniel Wagner Points 38831

Je sais que c'est question au sujet de style explicite des fonctions récursives, mais je dirais que le meilleur style est de trouver un moyen de réutiliser les fonctions récursives à la place.

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

4voto

Cristian Garcia Points 805

C'est juste une question de la commande mais je pense que c'est très lisible et a la même structure que les gardes.

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)

Le dernier d'autre n'en a pas besoin et si depuis il n'y a pas d'autres possibilités, aussi les fonctions doivent avoir de "dernier recours de cas" dans le cas où vous avez manqué quelque chose.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X