concatenación con (++)
Tal vez estoy pensando profundamente en esto, pero, por lo que yo entiendo, si intenta concatenar listas utilizando (++)
por ejemplo:
[1, 2, 3] ++ [4, 5]
(++)
tiene que recorrer la lista completa de la izquierda. Echando un vistazo a code of (++) lo hace mucho más claro.
(++) :: [a] -> [a] -> [a]
(++) [] ys = ys
(++) (x:xs) ys = x : xs ++ ys
Por lo tanto, sería deseable evitar el uso de (++)
, ya que con cada llamada reverse(xs)++[x]
la lista es cada vez más grande (o más pequeño dependiendo en el punto de vista. De todas formas, el programa simplemente tiene que atravesar otra lista con cada llamada)
Ejemplo:
digamos que implemento inversa como se propone a través de la concatenación.
reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs)++[x]
Invertir una lista [1, 2, 3, 4] se vería algo como esto:
reversex [1, 2, 3, 4]
reversex [2, 3, 4] ++ [1]
reversex [3, 4] ++ [2] ++ [1]
reversex [4] ++ [3] ++ [2] ++ [1]
reversex [] ++ [4] ++ [3] ++ [2] ++ [1]
[] ++ [4] ++ [3] ++ [2] ++ [1]
[4] ++ [3] ++ [2] ++ [1]
[4, 3] ++ [2] ++ [1]
[4, 3, 2] ++ [1]
[4, 3, 2, 1]
recursión de cola usando el operador contras (:) !!!
Un método para hacer frente a las pilas de llamadas es agregando un accumulator. (que no siempre es posible sólo tiene que añadir un acumulador. Pero la mayoría de los funciones recursivas uno se ocupa de son primitive recursive y puede pues transformarse en tail recursive functions.)
Con la ayuda del acumulador es posible hacer este ejemplo funciona, utilizando el operador de cons (:)
. El acumulador - ys
en mi ejemplo - acumula el resultado actual y se transfiere como parámetro. Debido al acumulador, ahora podemos utilizar el operador contra para acumular el resultado al agregar el encabezado de nuestra lista inicial cada vez.
reverse' :: (Ord a) => [a] -> [a] -> [a]
reverse' (x:xs) ys = reverse' xs (x:ys)
reverse' [] ys = ys
Hay una cosa a tener en cuenta aquí.
El acumulador es un argumento adicional. No sé si Haskell proporciona parámetros por defecto, pero en este caso sería bueno, porque siempre llamarían a esta función con una lista vacía como el acumulador de este modo: reverse' [1, 2, 3, 4] []
hay un montón de literatura acerca de la recursividad de cola y estoy seguro de que hay muchas preguntas similares en StackExchange/StackOverflow. Por favor corrígeme si encuentra algún error.
Saludos cordiales,
EDIT 1:
Will Ness señalaron algunos enlaces para realmente buenas respuestas para aquellos de ustedes que están interesados:
EDITAR 2:
Ok. Gracias a dFeuer y sus correcciones, creo que entiendo Haskell un poco mejor.
1.El $!
está más allá de mi comprensión. En todas mis pruebas, parecía empeorar las cosas.
2.As dFeuer señaló: El procesador que representa la aplicación de (:)
a x
y y
es semánticamente idénticos a x:y
pero toma más memoria. Así que esto es especial para el operador de cons (y constructores perezosos) y no hay necesidad de forzar las cosas de ninguna manera.
3. Si I en lugar sumUp números enteros de una lista utilizando una función muy similar, evaluación estricta a través de BangPatterns o la función seq
evitará que la pila de crecer demasiado grande si se utiliza adecuadamente. e.g .:
sumUp' :: (Num a, Ord a) => [a] -> a -> a
sumUp' (x:xs) !y = reverse' xs (x + y)
sumUp' [] y = y
Observe la explosión delante de y. Lo probé en ghci y lleva menos memoria.
Como nota al margen, puede (y debe) llamada sin utilizar paréntesis: 'inversa (x: xs) = xs ++ revertir [x] ', o te tropiezas cuando trabajas con funciones con múltiples argumentos. –
No invoque funciones como 'func (arg)'. Ese es el pobre Haskell. Siempre llame a funciones como 'func arg'.Código con espacios claros hace que el código sea más seguro y legible. – AJFarmar