2010-12-26 28 views
10

sé que si tuviera que calcular una lista de casillas en Haskell, que podría hacer esto:Haskell evaluación perezosa y Reciclar

squares = [ x ** 2 | x <- [1 ..] ] 

Luego, cuando me llaman plazas de esta manera:

print $ take 4 squares 

Y se imprimiría [1.0, 4.0, 9.0, 16.0]. Esto se evalúa como [1 ** 2, 2 ** 2, 3 ** 2, 4 ** 2]. Ahora que Haskell es funcional y el resultado sería el mismo cada vez, si volviera a llamar a las casillas en otro lugar, ¿volvería a evaluar las respuestas que ya ha calculado? Si tuviera que volver a usar cuadrados después de haber llamado a la línea anterior, ¿volvería a calcular los primeros 4 valores?

print $ take 5 squares 

¿Sería evaluar [1,0, 4,0, 9,0, 16,0, 5 ** 2]?

Respuesta

18

En este caso, no se volverá a calcular porque la lista realmente se genera y la lista de cuadrados continúa existiendo después de la llamada. Sin embargo, las funciones de Haskell en general no son memorables. Esto solo funciona para un caso como este en el que no llamas explícitamente una función, simplemente exploras una lista (in) finita.

+1

1 para la explicación clara –

+0

¿Hay alguna posibilidad de que puedas darme un ejemplo de algo que no sería memorable? Pensé que los cuadrados eran una función que devolvía una lista infinita. ¿Hay algo que pueda pasar a ghc para ver lo que Haskell está haciendo bajo las sábanas? –

+3

No, los cuadrados son * no * una función; es una lista Las estructuras de datos se memorizan automáticamente en Haskell, no en las funciones. Aún puede memorizar funciones, pero tiene que hacerlo explícitamente. Si está confundido acerca de que los cuadrados no son una función, es posible que le guste la publicación del blog [* "Todo es una función" en Haskell? *] (Http://conal.net/blog/posts/everything-is-a- function-in-haskell /). – Conal

2

Para aclarar una respuesta ya dada, ésta es una función Haskell:

thisManySquares n = map (^2) [1..n] 

lo tanto, si sustituiste llamadas de "thisManySquares 4" para su take 4 squares, entonces sí, sería llamar a la función repetidamente.

12

Este valor squares es potencialmente polimórfica:

Prelude> :t [ x ** 2 | x <- [1 ..] ] 
[ x ** 2 | x <- [1 ..] ] :: (Floating t, Enum t) => [t] 

AFAIK, si es o no se volverá a calcular (en GHC) depende de si el valor de nivel superior squares se da un tipo polimórfico. Creo que GHC no hace ninguna memoria de los valores polimórficos que involucran clases de tipos (funciones de tipos a valores), del mismo modo que no hace ninguna memoria de las funciones ordinarias (funciones de valores a valores).

Eso significa que si se define squares por

squares :: [Double] 
squares = [ x ** 2 | x <- [1 ..] ] 

continuación squares Sólo se computará una vez, mientras que si se define por

squares :: (Floating t, Enum t) => [t] 
squares = [ x ** 2 | x <- [1 ..] ] 

entonces es probable que se calcula cada vez que se usa , incluso si se usa repetidamente en el mismo tipo. (No he probado esto, sin embargo, y es posible que GHC, si ve varios usos de squares :: [Double], podría especializar el valor squares a ese tipo y compartir el valor resultante.) Ciertamente, si squares se usa en varios tipos diferentes, como squares :: [Double] y squares :: [Float], se volverá a calcular.

Si no proporciona ninguna firma de tipo para squares, se aplicará el monomorphism restriction, a menos que lo tenga desactivado. El resultado será que squares se le asigna un tipo monomórfico, inferido del resto de su programa (o de acuerdo con las reglas de incumplimiento). El objetivo de la restricción de monomorfismo es garantizar que los valores que parecen evaluados una sola vez, como su squares, realmente solo se evalúen una vez.

2

Por qué no usar ghci para probar (si GHC es su compilador):

Prelude> let squares = [ x ** 2 | x <- [1 ..] ] :: [Float] 
Prelude> :print squares 
squares = (_t6::[Float]) 

Así que todo ghci sabe en este momento es que usted tiene una lista.

Prelude> print $ take 4 squares 
[1.0,4.0,9.0,16.0] 
Prelude> :print squares 
squares = 1.0 : 4.0 : 9.0 : 16.0 : (_t7::[Float]) 

Sepa que sabe que su lista comienza con cosas, porque tuvo que evaluar para imprimirlo.

Prelude> let z = take 5 squares 
Prelude> :print z 
z = (_t8::[Float]) 

tomar 5 por sí mismo no evaluar nada

Prelude> length z --Evaluate the skeleton 
5 
Prelude> :print z 
z = [1.0,4.0,9.0,16.0,(_t9::Float)] 

Tomando la longitud provoca take que seguir su curso, y vemos que tenías razón. También puedes probar lo que está sucediendo cuando es polimórfico, simplemente omitiendo la definición de tipo en los cuadrados. Otro buen truco si no desea utilizar ghci es utilizar undefined en el código (el programa se bloquea exactamente cuando se trata de evaluar un _|_, que undefined es un tipo de.)

Cuestiones relacionadas