¿Por qué recursivo `let` hace que el espacio sea eficiente?
Encontré esta declaración mientras estudiaba la Programación reactiva funcional, de"Enchufar una fuga de espacio con una flecha" por Hai Liu y Paul Hudak (página 5):
Suppose we wish to define a function that repeats its argument indefinitely:
repeat x = x : repeat x
or, in lambdas:
repeat = λx → x : repeat x
This requires O(n) space. But we can achieve O(1) space by writing instead:
repeat = λx → let xs = x : xs
in xs
La diferencia aquí parece pequeña pero aumenta enormemente la eficiencia del espacio. ¿Por qué y cómo sucede? Lo mejor que he hecho es evaluarlos a mano:
r = \x -> x: r x
r 3
-> 3: r 3
-> 3: 3: 3: ........
-> [3,3,3,......]
Como se mencionó anteriormente, necesitaremos crear infinitos trucos nuevos para estas recursiones. Entonces trato de evaluar el segundo:
r = \x -> let xs = x:xs in xs
r 3
-> let xs = 3:xs in xs
-> xs, according to the definition above:
-> 3:xs, where xs = 3:xs
-> 3:xs:xs, where xs = 3:xs
En la segunda forma laxs
aparece y se puede compartir entre todos los lugares en los que ocurre, por lo que supongo que es por eso que solo podemos exigirO(1)
espacios en lugar deO(n)
. Pero no estoy seguro de si estoy en lo correcto o no.
BTW: La palabra clave "compartida" proviene de la misma página 4 del artículo:
El problema aquí es que las reglas de evaluación estándar de llamada por necesidad no pueden reconocer que la función:
f = λdt → integralC (1 + dt) (f dt)
es lo mismo que:
f = λdt → let x = integralC (1 + dt) x in x
La primera definición hace que el trabajo se repita en la llamada recursiva a f, mientras que en el último caso se comparte el cálculo.