24 votos

¿Por qué si las expresiones mal visto en Haskell?

Esta ha sido una pregunta que me he estado preguntando por un tiempo. si las declaraciones son básicos en la mayoría de los lenguajes de programación (al menos, el que yo he trabajado), pero en Haskell parece que está muy mal visto. Entiendo que para situaciones complejas, Haskell la coincidencia de patrón es mucho más limpio que un montón de ifs, pero ¿hay alguna diferencia real?

Para un ejemplo simple, tomar una versión casera de la suma (sí, sé que podría ser foldr (+) 0):

sum :: [Int] -> Int
-- separate all the cases out
sum [] = 0
sum (x:xs) = x + sum xs

-- guards
sum xs
    | null xs = 0
    | otherwise = (head xs) + sum (tail xs)

-- case
sum xs = case xs of
    [] -> 0
    _ -> (head xs) + sum (tail xs)

-- if statement
sum xs = if null xs then 0 else (head xs) + sum (tail xs)

Como segunda cuestión, en la que una de estas opciones se considera la "mejor práctica" y por qué? Mi profesor de camino de regreso cuando siempre se utiliza el primer método siempre que sea posible, y me pregunto si esa es su preferencia personal o si había algo detrás de él.

45voto

hammar Puntos 89293

El problema con tus ejemplos no es la if expresiones, es el uso de funciones parciales como head y tail. Si intenta llamar a uno de estos con una lista vacía, se lanza una excepción.

> head []
*** Exception: Prelude.head: empty list
> tail []
*** Exception: Prelude.tail: empty list

Si usted comete un error al escribir código con estas funciones, el error no se detecta hasta el tiempo de ejecución. Si usted comete un error con la coincidencia de patrones, el programa no compilará.

Por ejemplo, digamos que usted accidentalmente de conmutación de la then y else partes de su función.

-- Compiles, throws error at run time.
sum xs = if null xs then (head xs) + sum (tail xs) else 0

-- Doesn't compile. Also stands out more visually.
sum [] = x + sum xs
sum (x:xs) = 0

Tenga en cuenta que el ejemplo y con guardias tiene el mismo problema.

20voto

ertes Puntos 3012

Creo que el Booleano Ceguera artículo responde a esta pregunta muy bien. El problema es que los valores booleanos han perdido todo su significado semántico tan pronto como se construyen. Que hace una gran fuente de errores y también hace que el código sea más difícil de entender.

16voto

Ingo Puntos 21438

Su primera versión, el preferido por su profe, tiene las siguientes ventajas con respecto a los demás:

  1. no hay mención de null
  2. lista de componentes se denominan en el patrón, así que no hay mención de head y tail.

Yo creo que esta es considerada la "mejor práctica".

¿Cuál es el problema? ¿Por qué queremos evitar, especialmente, head y tail? Bueno, todo el mundo sabe que esas funciones no son totales, por lo que uno intenta automáticamente para asegurarse de que se cubren todos los casos. Una coincidencia de patrón en [] no sólo destaca más que null xs, una serie de coincidencias de patrón puede ser comprobado por el compilador para la integridad. Por lo tanto, la idiomáticas versión completa coincidencia de patrón es más fácil de comprender (para la formación de Haskell lector) y a la prueba exhaustiva por el compilador.

La segunda versión es ligeramente mejor que el anterior porque uno ve a la vez que todos los casos son manejados. Aún así, en el caso general, el lado derecho de la segunda ecuación podría ser mayor y podrían ser una donde cláusulas con un par de definiciones, el último de ellos podría ser algo como:

where
   ... many definitions here ...
   head xs = ... alternative redefnition of head ...

Para estar absolutamente seguro de entender lo que la RHS hace, uno tiene que asegurarse que los nombres comunes no han sido redefinidos.

La versión 3 es la peor, en mi humilde opinión: a) El 2º partido de falla a deconstruir la lista y todavía usa la cabeza y la cola. b) El caso es un poco más detallado de la notación equivalente con 2 ecuaciones.

8voto

MathematicalOrchid Puntos 15354

En muchos lenguajes de programación, si las declaraciones son fundamentales primitivas, y cosas como cambiar los bloques son sólo la sintaxis de azúcar para hacer anidadas si-declaraciones más fácil de escribir.

Haskell no al revés. La coincidencia de patrón fundamental es la primitiva, y si una expresión es, literalmente, acaba de sintaxis de azúcar para la coincidencia de patrones. Del mismo modo, las construcciones como null y head son simplemente funciones definidas por el usuario, que son, en última instancia, implementado mediante la coincidencia de patrones. Para el reconocimiento de patrones es la cosa en el fondo de todo. (Y por lo tanto potencialmente más eficaz que llamar a funciones definidas por el usuario.)

En muchos casos - como los de la lista de arriba, es simplemente una cuestión de estilo. El compilador ciertamente puede optimizar las cosas hasta el punto donde todas las versiones son prácticamente iguales en rendimiento. Pero generalmente (no siempre!] la coincidencia de patrón deja más claro exactamente lo que usted está tratando de lograr.

(Es demasiado fácil escribir un si-expresión donde se obtiene de las dos alternativas al revés. Te gustaría pensar que sería raro el error, pero es sorprendentemente común. Con una coincidencia de patrón, hay pocas probabilidades de hacer que el error específico, aunque todavía hay un montón de otras cosas de meter la pata.)

5voto

Will Ness Puntos 18581

Cada llamada a null, head y tail implica una coincidencia de patrón. Pero la 1era versión en su respuesta, es sólo una coincidencia de patrón, y reutiliza sus resultados a través de nombre de los componentes del patrón.

Sólo por eso, es mejor. Pero también es visualmente más aparente, más legible.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X