¿Hasta qué punto es perezoso Haskell?
lazy-evaluation (4)
A menos que sea necesario obtener el valor de esa variable, no se evaluará. Básicamente, Haskell es tan perezoso a menos que se le diga que no lo sea.
Puedes confirmar esto, así
Prelude> :set +m
Prelude> let anotherFunction = (100, 1 `div` 0)
Prelude|
Prelude> let myFunction arg
Prelude| | arg == 1 = a
Prelude| | otherwise = b
Prelude| where
Prelude| (a, b) = anotherFunction
Prelude|
Aquí, 1 `div` 0
generará divide by zero
error de divide by zero
. Si evalúa todos los elementos, incluso cuando invoque myFunction
con 1
, habría recibido ese error, pero
Prelude> myFunction 1
100
solo cuando lo invoca con cualquier otro valor, es necesario evaluar el segundo valor de la tupla, y fallará con el error de divide by zero
.
Prelude> myFunction 2
*** Exception: divide by zero
Necesito una aclaración sobre la pereza con Haskell.
Si tengo esta función:
myFunction arg
| arg == 1 = a
| arg == 2 = a*b
| arg == 3 = b+c
| otherwise = (a+b)*c
where
a = ...
b = ...
c = ...
d = ...
Cuando llamo a mi myFunction 1
, Haskell evaluará solo la función a a = ...
, ni b ni c , ni d .
Pero si escribo
myFunction arg
| arg == 1 = a
| arg == 2 = a*b
| arg == 3 = b+c
| otherwise = (a+b)*c
where
(a,b,c,d) = anotherFunction arg
¿Cuál será el comportamiento de Haskell?
- ¿Evaluará solo una y ''propagará'' la pereza a otra
anotherFunction
? - O, ¿evaluará la tupla entera (a, b, c, d) como resultado de otra
anotherFunction
?
Bueno, solo está interesado en a
, así que eso significa que hay una función implícita :
thea :: (a,b,c,d) -> a
thea (a,_,_,_) = a
En otras palabras, Haskell no está interesado en los otros elementos de la tupla. A veces, sin embargo, los elementos de la tupla comparten alguna estructura. Decir otra función se define como:
anotherFunction :: Int -> (Int,Int,Int,Int)
anotherFunction x = (z,t,f,g)
where f = x*x
g = f+x
z = g-2
t = 3
En ese caso, para evaluar el primer elemento, también se evaluará el tercer y cuarto elemento. Pero como no haces nada con ellos, a Haskell no le interesará específicamente su resultado.
Como otros ya han señalado, solo a
será evaluada.
Sin embargo, tenga en cuenta que, para aprovechar la pereza, es crucial que anotherFunction
devuelva una tupla antes de evaluar sus componentes. Por ejemplo, considere
anotherFunction n = if p > 1000 then (n, p) else (n, 0)
where p = product [1..n]
Lo anterior siempre evaluará el product [1..n]
, incluso si la persona que llama solo exige el primer componente del par (que es n
). Esto se debe a que el if
debe evaluarse antes de que se pueda devolver el par, y eso fuerza a p
. Por el contrario,
anotherFunction n = (n, if p > 1000 then p else 0)
where p = product [1..n]
Inmediatamente devolverá el par. Si solo se evalúa su primer componente, entonces p
no se computará en absoluto.
En ambos casos, no evaluará nada a menos que se solicite el valor. Una forma de exigir el valor es llamar a la función en ghci (que imprime el valor en ghci
y, por lo tanto, lo exige). Suponiendo que está ejecutando la función, en su segundo caso evaluará la forma normal de la tupla a cabeza débil (WHNF) y luego evaluará el primer elemento en (a,b,c,d)
porque solo se exige ese valor. Los otros elementos b
, d
estarán en la forma thunk. De hecho puedes verificar que tú mismo:
myFunction arg
| arg == 1 = a
| arg == 2 = a*b
| arg == 3 = b+c
| otherwise = (a+b)*c
where
(a,b,c,d) = anotherFunction arg
anotherFunction x = (x, undefined, undefined, undefined)
Demostración en ghci:
λ> myFunction 1
1