Tengo tres funciones que encuentran el enésimo elemento de una 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)
En mi opinión, la primera función es la mejor implementación porque es la más concisa. Pero, ¿hay algo en las otras dos implementaciones que las haga preferibles? Y, por extensión, ¿cómo elegiría entre usar guardias, declaraciones if-then-else y casos?
haskell
if-statement
case
nucleartida
fuente
fuente
case
declaraciones anidadas si utilizócase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
case compare a 1 of ...
Respuestas:
Desde un punto de vista técnico, las tres versiones son equivalentes.
Dicho esto, mi regla general para los estilos es que si puedes leerlo como si fuera en inglés (léelo
|
como "cuando",| otherwise
como "de lo contrario" y=
como "es" o "ser"), probablemente estés haciendo algo. Derecha.if..then..else
es para cuando tiene una condición binaria o una sola decisión que debe tomar. Lasif..then..else
expresiones anidadas son muy poco comunes en Haskell, y casi siempre se deben usar guardias en su lugar.Cada
if..then..else
expresión puede ser reemplazada por una guardia si está en el nivel superior de una función, y esto generalmente debería ser preferido, ya que puede agregar más casos más fácilmente que:case..of
es para cuando tiene múltiples rutas de código y cada ruta de código está guiada por la estructura de un valor, es decir, a través de la coincidencia de patrones. Rara vez coincide conTrue
yFalse
.Los guardias complementan las
case..of
expresiones, lo que significa que si necesita tomar decisiones complicadas en función de un valor, primero tome decisiones según la estructura de su entrada y luego tome decisiones sobre los valores en la estructura.Por cierto. Como sugerencia de estilo, siempre haga una nueva línea después de a
=
o antes de a|
si el contenido después de=
/|
es demasiado largo para una línea, o usa más líneas por alguna otra razón:fuente
True
yFalse
" ¿hay alguna ocasión en la que harías eso? Después de todo, este tipo de decisión siempre se puede tomar con unif
y también con guardias.case (foo, bar, baz) of (True, False, False) -> ...
guard
función requiereMonadPlus
, pero de lo que estamos hablando aquí es de guardias como en| test =
cláusulas, que no están relacionadas.Sé que esta es una pregunta sobre el estilo para funciones explícitamente recursivas, pero sugeriría que el mejor estilo es encontrar una manera de reutilizar las funciones recursivas existentes.
fuente
Esto es solo una cuestión de pedido, pero creo que es muy legible y tiene la misma estructura que los guardias.
El último más no es necesario y si no hay otras posibilidades, también las funciones deben tener "caso de último recurso" en caso de que se haya perdido algo.
fuente