Вы находитесь на странице: 1из 11

Estrategias de evaluacin

Introduccin
La operacin que realizamos en funcional es aplicar funciones, la idea del tema que
vamos a tratar a continuacin es saber qu se tiene que tener en cuenta para determinar
el orden en en que aplicarn las funciones de una expresin.
Primer ejemplo
masUno x = x + 1
La expresin masUno (2*3) puede ser evaluada de la siguiente forma
masUno (2*3)
aplicamos *
masUno 6
aplicamos masUno
6 + 1
aplicamos +
7
Alternativamente podemos evaluar la misma expresin pero aplicando las funciones en
el orden inverso
masUno (2*3)
aplicamos masUno
(2*3) + 1
aplicamos *
6 + 1
aplicamos +
7
No importa el orden en que apliquemos las funciones vamos a llegar al mismo resultado
final. Esto no solo vale para ejemplos sencillos sino que se cumple siempre en Haskell.
Esta propiedad no se cumple en la mayora de los "lenguajes imperativos" (Pascal, C,
Smalltalk, Java, C#, etc.), veamos un ejemplo en Smalltalk:
Si tenemos la expresin n + (n := 1) y n empieza apuntando a 0.
Si empezamos a evaluar de izquierda a derecha
n + (n := 1)
aplicamos n
0 + (n := 1)
aplicamos :=
0 + 1
aplicamos +
1
Si empezamos a evaluar de derecha a izquierda
n + (n:= 1)
aplicamos :=
n + 1
aplicamos n
1 + 1
aplicamos +
2
Como se puede observar, si evaluamos las expresiones con distintas estrategias
obtenemos resultados distintos; esto sucede porque las operaciones involucradas no
tienen transparencia referencial en este caso particular debido a la introduccin de una
asignacin destructiva (ms sobre esto en la prxima clase terica).
Estrategias bsicas
A una expresin que consta de una funcin aplicada a uno o ms parmetros y que
puede ser "reducida" aplicando dicha funcin la vamos a llamar Redex (Reducible
Expression). Se le dice reduccin al hecho de aplicar la funcin no necesariamente
vamos a obtener una expresin "ms corta" como veremos ms adelante. Consideremos
la funcin mult que tiene como dominio una tupla de 2 nmeros
mult (x,y) = x * y
Si queremos reducir la expresin mult (1+2,2+3) est expresin contiene 3 redexs
1. 1+2 (la funcin + aplicada a 2 parmetros)
2. 2+3 (la funcin + aplicada a 2 parmetros)
3. mult (1+2,2+3) (la funcin mult aplicada a 1 parmetro que es una tupla)
Si queremos evaluar la expresin qu estrategia usamos?
De adentro hacia afuera
Tambin conocida como call-by-value
Una de las estrategias ms comunes es comenzar desde adentro hacia afuera
(innermost evaluation), esta estrategia elige el redex que est "ms adentro" entendiendo
por esto al redex que no contiene otro redex. Si existe ms de un redex que cumple
dicha condicin se elige el que est ms a la izquierda. Vamos al ejemplo
mult (1+2,2+3)
aplicamos el primer +
mult (3,2+3)
aplicamos el +
mult (3,5)
aplicamos mult
3 * 5
aplicamos *
15
Esta estrategia me asegura que los parmetros de una funcin estn completamente
evaluados antes de que la funcin sea aplicada. Por eso se dice que los parmetros se
pasan por valor.
De afuera hacia adentro
Tambin conocida como call-by-name
Otra de las estrategias ms comunes es comenzar desde afuera hacia adentro
(outtermost evaluation), esta estrategia elige el redex que est "ms afuera" entendiendo
por esto al redex que no esta contenido en otro redex. Si existe ms de un redex que
cumple dicha condicin se elige el que est ms a la izquierda. Vamos al ejemplo
mult (1+2,2+3)
aplicamos mult
(1+2) * (2+3)
aplicamos el primer + (Si seguimos lo que dijimos arriba deberamos aplicar primero
el * pero vamos a explicar porque no lo hacemos ms abajo)
3 * (2+3)
aplicamos +
3 * 5
aplicamos *
15
Usando esta estrategia las funciones se aplican antes que los parmetros sean
evaluados. Por esto se dice que los parmetros se pasan por nombre. Nota: Hay que
tener en cuenta que muchas funciones que ya vienen con Haskell requieren que sus
parmetros estn evaluados antes de que la funcin sea aplicada, incluso cuando
usamos la estrategia "de afuera hacia adentro". Por ejemplo, el operador * y el + no
pueden ser aplicados hasta que sus dos parmetros hayan sido evaluados a nmeros. A
las funciones que cumplen con esta propiedad las vamos a llamar funciones estrictas.
Funciones estrictas que nos van a interesar a nosotros:
Operaciones aritmticas (+,*,/,etc.)
Pattern-Matching (sobre listas, tuplas, etc.)
Evaluaciones que no terminan
Tengan en cuenta la siguiente definicin
inf = 1 + inf
Intentar reducir la expresin inf siempre nos va a dar como resultado una expresin ms
y ms grande (independientemente de la estrategia de evaluacin que usemos)
inf
aplicamos inf
1 + inf
aplicamos inf (porque + es estricta)
1 + (1 + inf)
aplicamos inf (porque + es estricta)
....
1 + (1 + (1 + (1 + (1 + (1 + .... + inf )))))
Por ende, est evaluacin nunca terminara.
Sabiendo que
fst (x,_) = x
Consideremos la expresin fst (0,inf)
Usando la estrategia call-by-value
fst (0,inf)
aplicamos inf
fst (0, 1 + inf )
aplicamos inf
fst (0, 1 + (1 + inf) )
aplicamos inf
fst (0, 1 + (1 + (1 + inf) ) )
aplicamos inf
...
Usando call-by-value la evaluacin de la expresin no termina.
Usemos call-by-name:
fst (0,inf)
aplicamos fst
0
Usando call-by-name la expresin se evala por completo con solo una reduccin. En
este ejemplo se puede ver que ciertas expresiones que pueden no terminar cuando se
evalan con la estrategia call-by-value pueden terminar cuando se usa la estrategia call-
by-name.
De forma ms general: Si existe alguna secuencia de evaluacin que haga terminar la
evaluacin de la expresin entonces con la estrategia call-by-name tambin se termina la
evaluacin y se produce el mismo resultado final.
Corolario: si te es mucho muy importante que una expresin termine la estrategia que
quers usar es call-by-name
Lazy Evaluation
.Tcnicas de programacin funcional perezosa.
Visin tcnica
Si tenemos la siguiente definicin
alCuadrado x = x * x
Vamos a evaluar la expresin alCuadrado (2*3) usando call-by-value
alCuadrado (1+2)
aplicamos +
alCuadrado 3
aplicamos alCuadrado
3 * 3
aplicamos *
9
Ahora vamos a evaluar la misma expresin usando call-by-name
alCuadrado (1+2)
aplicamos alCuadrado el primer +
(1+2) * (1+2)
aplicamos el +
3 * (1+2)
aplicamos el *
3 * 3
aplicamos el *
9

Llegamos la mismo resultado pero en el segundo ejemplo realizamos una reduccin ms
(4 reducciones vs 3 reducciones).
Con call-by-name la expresin (1+2) se evalu dos veces.
Corolario: cuando usamos call-by-value los parmetros son evaluados una y solo una
vez; cuando usamos call-by-name el mismo parmetro puede llegar a ser evaluado ms
de una vez.
Para evitar este quilombo en vez de tener la expresin (1+2) vamos a tener un "puntero a
la expresin" llammoslo p.
alCuadrado (1+2)
aplicamos alCuadrado
let p = (1+2) in p * p
aplicamos +
let p = 3 in p * p
aplicamos *
9

Cualquier reduccin que se haga en una expresin se va a conocer automticamente por
los punteros a dicha expresin. Al uso de punteros para compartir expresiones que
representan la mismo parmetro lo vamos a llamar Sharing. Al uso de la estrategia call-
by-name ms el Sharing lo vamos a llamar Lazy Evaluation (esta es la estrategia que usa
Haskell). El Sharing nos asegura que usar Lazy Evaluation nunca requiera ms pasos
que la estrategia call-by-value.
Visin operativa
A efectos de resumir lo que vimos hasta ahora vamos a entender lo siguiente ...
Lazy Evaluation: con esta estrategia los parmetros solo se resuelven cuando son
necesarios (y son evaluados solo lo necesario). Tambin conocida como evaluacin
perezosa o diferida.
A la estrategia call-by-value (y sus variantes) tambin se las conoce como Eager
Evaluation. Eager Evaluation: con esta estrategia los parmetros tienen que resolverse
antes de aplicar la funcin. Tambin conocida como evaluacin ansiosa.
Estructuras infinitas
Pensemos en la siguiente definicin
unos = 1 : unos
(A partir de ahora vamos a pensar que evaluamos todo en Haskell as que la estrategia
que usamos es Lazy Evaluation)
unos
aplicamos unos
1 : unos
aplicamos unos
1 : ( 1 : unos )
aplicamos unos
...
En Haskell
> unos
[1,1,1,1,1,1,1........
Como se puede ver la evaluacin de unos no termina. A pesar de esto podemos usar la
expresin unos dentro de nuestro programa y aplicarla a otras funciones. Por ejemplo
Siendo head (x:_) = x y la expresin head unos
head unos
deberamos aplicar head pero como head me fuerza a tener la lista separada en
cabeza:cola tenemos que evaluar unos por el pattern-matching
head (1:unos)
aplicamos head
1
Con este ejemplo podemos ver que unos no es una lista infinita sino potencialmente
infinita, si aplicamos sobre ella funciones que no la fuerzan a evaluarse por completo la
computacin termina (eso son apocalptico). La potencia de Lazy Evaluation est en
que la expresin unos se evala solo lo necesario para que pueda usarla la funcin que
la recibe como parmetro.
Listas infinitas
Ya vimos la lista de unos que es "infinita", ahora veamos como hacer una lista que tenga
todos los nmeros naturales
naturalesDesde x = x : naturalesDesde (x+1)
> naturalesDesde 1
[1,2,3,4,5,6,7,8,9,...........
Haskell trae un atajo para esto
naturalesDesde x = [x..]
Tambin sirve para hacer listas con alguna condicin entre dos de sus elementos
consecutivos
> [1,3..]
[1,3,5,7,9,11,.........
Ejemplos
Dada la siguiente definicin de take
take 0 _ = []
take _ [] = []
take (n+1) (x:xs) = x : (take n xs)
take 3 [1..]
aplicamos ..
take 3 (1:[2..])
aplicamos take - 3ra lnea
1 : (take 2 [2..])
aplicamos ..
1 : (take 2 (2:[3..]))
aplicamos take - 3ra lnea
1 : ( 2 : (take 1 [3..]))
aplicamos ..
1 : ( 2 : (take 1 (3:[4..])))
aplicamos take - 3ra lnea
1 : ( 2 : ( 3 : (take 0 [ 4.. ]))))
aplicamos take - 1ra lnea
1 : ( 2 : ( 3 : [] ))) = [1,2,3]
Vamos a otro ejemplo
take 3 [4+5,2/0,3*2]
aplicamos el + (porque dice x: en take 3ra lnea)
take 3 [9,2/0,3*2]
aplicamos take - 3ra lnea
9 : take 2 [2/0,3*2]
aplicamos /
9 : ERROR DIVISON BY ZERO !!!
Dada la definicin de (!!)
(!!) 0 (x:_) = x
(!!) (n+1) (_:xs) = (!!) n xs
(!!) 2 [4+5,2/0,3*2]
aplicamos el !! (no es necesario aplicar el + porque en (!!) dice (_:xs) )
(!!) 1 [2/0,3*2]
aplicamos el !! (no es necesario aplicar la / por lo anterior)
(!!) 0 [3*2]
aplicamos * (porque la primer lnea de !! lo pide)
(!!) 0 [6]
aplicamos !!
6
> head (filter (3<) [1..])
4
Ms ejemplos heavies
primos = achurar [2..]
achurar (x:xs) = x : achurar [e | e <- xs , e `mod` x \= 0]
> primos
[1,3,5,7,11,13...........
--Si queremos obtener los primeros 19 mltiplos de 13
> [13,26..24*13]

[13,26,39,52,65,78,91,104,117,130,143,156,169,182,195,208,221,234,
247]
-- O bien
> take 24 [13,26..]

[13,26,39,52,65,78,91,104,117,130,143,156,169,182,195,208,221,234,
247]

Вам также может понравиться