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 l'utilisation de gardes, des déclarations if-then-else et des cas?
haskell
if-statement
case
marée nucléaire
la source
la source
case
case compare a 0 of LT -> ... | EQ -> ... | GT -> ...
case compare a 1 of ...
Réponses:
D'un point de vue technique, les trois versions sont équivalentes.
Cela étant dit, ma règle de base pour les styles est que si vous pouvez le lire comme s'il était anglais (lire
|
comme "quand",| otherwise
comme "autrement" et=
comme "est" ou "être"), vous faites probablement quelque chose droite.if..then..else
est pour quand vous avez une condition binaire , ou une seule décision que vous devez prendre. Lesif..then..else
expressions imbriquées sont très rares dans Haskell, et les gardes devraient presque toujours être utilisées à la place.Chaque
if..then..else
expression peut être remplacée par une garde si elle est au niveau supérieur d'une fonction, et cela devrait généralement être préféré, car vous pouvez ajouter plus de cas plus facilement alors:case..of
est pour lorsque vous avez plusieurs chemins de code , et chaque chemin de code est guidé par la structure d'une valeur, c'est-à-dire via la correspondance de modèles. Vous correspondez très rarement surTrue
etFalse
.Les gardes complètent les
case..of
expressions, ce qui signifie que si vous devez prendre des décisions compliquées en fonction d'une valeur, prenez d' abord des décisions en fonction de la structure de votre entrée, puis prenez des décisions sur les valeurs de la structure.BTW. Comme astuce de style, faites toujours une nouvelle ligne après a
=
ou avant a|
si le truc après le=
/|
est trop long pour une ligne, ou utilise plus de lignes pour une autre raison:la source
True
etFalse
" y a-t-il une occasion où vous feriez ça? Après tout, ce genre de décision peut toujours être fait avec unif
, et avec des gardes aussi.case (foo, bar, baz) of (True, False, False) -> ...
guard
fonction nécessiteMonadPlus
, mais ce dont nous parlons ici, ce sont des gardes comme dans des| test =
clauses, qui ne sont pas liées.Je sais que c'est une question de style pour les fonctions explicitement récursives, mais je suggérerais que le meilleur style est de trouver un moyen de réutiliser les fonctions récursives existantes à la place.
la source
C'est juste une question de commande mais je pense que c'est très lisible et a la même structure que les gardes.
Le dernier autre n'a pas besoin et s'il n'y a pas d'autres possibilités, les fonctions devraient également avoir un "cas de dernier recours" au cas où vous auriez manqué quelque chose.
la source