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

casedeclaraciones 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",| otherwisecomo "de lo contrario" y=como "es" o "ser"), probablemente estés haciendo algo. Derecha.if..then..elsees para cuando tiene una condición binaria o una sola decisión que debe tomar. Lasif..then..elseexpresiones anidadas son muy poco comunes en Haskell, y casi siempre se deben usar guardias en su lugar.Cada
if..then..elseexpresió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..ofes 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 conTrueyFalse.Los guardias complementan las
case..ofexpresiones, 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
TrueyFalse" ¿hay alguna ocasión en la que harías eso? Después de todo, este tipo de decisión siempre se puede tomar con unify también con guardias.case (foo, bar, baz) of (True, False, False) -> ...guardfunció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