web-dev-qa-db-fra.com

Gardes contre les cas si-alors-sinon contre Haskell

J'ai trois fonctions qui trouvent le niè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 implémentation, car elle est la plus concise. Mais y a-t-il quelque chose dans les deux autres implémentations qui les rendrait préférables? Et par extension, comment choisiriez-vous entre utiliser des gardes, des déclarations si-alors-sinon, et des cas?

97
Jason Tu

D'un point de vue technique, les trois versions sont équivalentes.

Ceci étant dit, ma règle générale pour les styles est que si vous pouvez le lire comme si c'était en anglais (lisez | comme quand", | otherwise comme "sinon" et = comme "est" ou "sois"), vous faites probablement quelque chose de bien.

if..then..else est pour lorsque vous avez une condition binaire ou une seule décision à prendre. Imbriqué if..then..else _ Les expressions sont très rares en Haskell et les gardes doivent presque toujours être utilisés.

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

Chaque if..then..else expression peut être remplacée par un garde si elle se trouve au plus haut niveau d’une fonction, ce qui devrait généralement être préféré, car vous pouvez ajouter plus de cas plus facilement, puis:

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

case..of est pour lorsque vous avez plusieurs chemins de code , et que chaque chemin de code est guidé par la structure d'une valeur, c'est-à-dire via un filtrage par motif. Vous ne correspondez que très rarement sur True et False.

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

Gardes complément case..of expressions, ce qui signifie que si vous devez prendre des décisions complexes en fonction d’une valeur, commencez par en fonction de la structure de votre entrée et puis décide des valeurs dans la structure.

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

BTW. Comme astuce de style, faites toujours une nouvelle ligne après un = ou avant un | si les choses après le =/| est trop long pour une ligne ou utilise plus de 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)
114
dflemstr

Je sais que c'est une question de style pour les fonctions explicitement récursives, mais je suggérerais que le meilleur style consiste à trouver un moyen de réutiliser des fonctions récursives existantes à la place.

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

C'est juste une question d'ordre 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)

La dernière chose n’a pas besoin et si, comme il n’y avait pas d’autres possibilités, les fonctions devraient également avoir un "cas de dernier recours" au cas où vous auriez manqué quelque chose.

1
Cristian Garcia