Programación Funcional

Anuncio
Programación Funcional
Alberto Pardo
Marcos Viera
Instituto de Computación, Facultad de Ingenierı́a
Universidad de la República, Uruguay
Alberto Pardo, Marcos Viera
Programación Funcional
Lazy evaluation
Alberto Pardo, Marcos Viera
Programación Funcional
Estrategias de evaluación
Una expresión formada por una función aplicada a argumentos
que está en condiciones de ser reducida es llamada un redex
(por reducible expression).
Existen dos estrategias que son comunmente usadas para
evaluar expresiones.
Dichas estrategias difieren en el redex que escogen para
reducir en cada paso.
Consideremos la siguiente expresión:
square (1 + 2)
where
square x = x ∗ x
y veamos en que orden la podemos evaluar.
Alberto Pardo, Marcos Viera
Programación Funcional
Estrategia: leftmost innermost
Se elije el redex más interno (el que no contiene otros redexes)
más a la izquierda.
square (1 + 2)
{ def. + }
=
square 3
{ def. square }
=
3∗3
{ def. + }
=
9
Alberto Pardo, Marcos Viera
Programación Funcional
Estrategia: leftmost innermost
Se elije el redex más interno (el que no contiene otros redexes)
más a la izquierda.
square (1 + 2)
{ def. + }
=
square 3
{ def. square }
=
3∗3
{ def. + }
=
9
Corresponde a pasaje por valor (call-by-value).
Alberto Pardo, Marcos Viera
Programación Funcional
Estrategia: leftmost outermost
Se elije el redex más externo (no contenido por otros redexes) más
a la izquierda.
square (1 + 2)
=
{ def. square }
(1 + 2) ∗ (1 + 2)
=
{ def. + }
3 ∗ (1 + 2)
=
{ def. + }
3∗3
=
{ def. * }
9
Alberto Pardo, Marcos Viera
Programación Funcional
Estrategia: leftmost outermost
Se elije el redex más externo (no contenido por otros redexes) más
a la izquierda.
square (1 + 2)
=
{ def. square }
(1 + 2) ∗ (1 + 2)
=
{ def. + }
3 ∗ (1 + 2)
=
{ def. + }
3∗3
=
{ def. * }
9
Corresponde a pasaje por nombre (call-by-name).
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación
La evaluación de una expresión puede
parar (o terminar) retornando un valor, o
seguir por siempre (no terminación).
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación
La evaluación de una expresión puede
parar (o terminar) retornando un valor, o
seguir por siempre (no terminación).
Propiedad 1: Si dos secuencias de reducción distintas de una
misma expresión terminan, entonces lo van a hacer con el mismo
resultado.
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación
La evaluación de una expresión puede
parar (o terminar) retornando un valor, o
seguir por siempre (no terminación).
Propiedad 1: Si dos secuencias de reducción distintas de una
misma expresión terminan, entonces lo van a hacer con el mismo
resultado.
Propiedad 2: Dada una expresión, si existe alguna secuencia de
evaluación que termina con un cierto resultado, entonces la
estrategia call-by-name también terminará produciendo el mismo
resultado final.
En cambio, la estrategia call-by-value no garantiza llegar a ese
resultado.
Alberto Pardo, Marcos Viera
Programación Funcional
No terminación
Sea la siguiente definición:
inf :: Int
inf = 1 + inf
La evaluación de inf no termina independiente de la estrategia de
evaluación. Produce expresiones cada vez más grandes.
Decimos que su valor es indefinido.
De todas formas su ecuación se usa como regla de reescritura.
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación: call-by-value
fst (length [ ], inf )
=
fst (0, inf )
=
fst (0, 1 + inf )
=
fst (0, 1 + (1 + inf ))
=
fst (0, 1 + (1 + (1 + inf )))
=
...
La evaluación no termina.
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación: call-by-name
fst (length [ ], inf )
=
length [ ]
=
0
El redex más externo es la propia expresión (fst aplicado al par).
Esto muestra que un argumento es evaluado sólo si es necesario
(por demanda).
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación: call-by-name
three :: Int → Int
three x = 3
three inf
=
3
Alberto Pardo, Marcos Viera
Programación Funcional
Terminación: call-by-name
length [inf , 2]
=
1 + length [2]
=
1 + (1 + length [ ])
=
1 + (1 + 0)
=
2
Alberto Pardo, Marcos Viera
Programación Funcional
Funciones estrictas
Al valor indefinido se lo suele denotar con el sı́mbolo especial
⊥ que se pronuncia “bottom”.
Denota tanto el caso de no terminación como el de detención
anormal de un programa.
Un valor ⊥ existe en todos los tipos. Por ejemplo, el valor de
inf es el valor ⊥ del tipo Int, mientras que el valor de 1/0 es
el valor indefinido de tipo Float.
Se dice que una función es estricta si f ⊥ = ⊥. En otro caso
se dice que es no estricta.
Ejemplos: +, *, -, /, div , mod son todas funciones estrictas.
En cambio, three es no estricta.
Alberto Pardo, Marcos Viera
Programación Funcional
Lazy evaluation
Retomemos la evaluación de square (1 + 2) con call-by-name.
square (1 + 2)
=
(1 + 2) ∗ (1 + 2)
=
3 ∗ (1 + 2)
=
3∗3
=
9
Problema: la subexpresión 1 + 2 es evaluada dos veces.
Propiedad: con call-by-value los argumentos son evaluados
una sola vez, en cambio con call-by-name pueden llegar a ser
evaluados muchas veces.
Alberto Pardo, Marcos Viera
Programación Funcional
Lazy evaluation (2)
El problema se resuelve usando punteros para indicar que hay
subexpresiones que se comparten durante la evaluación.
Las expresiones se escriben entonces como grafos en lugar de
como árboles.
En lugar de copiar fı́sicamente el argumento en todos los
lugares del cuerpo de la función donde se usa, simplemente
tenemos una copia del argumento y colocamos varios punteros
a él desde las posiciones del cuerpo.
De esta manera, cualquier reducción que se haga sobre el
argumento va a ser compartida por todas las posiciones que
tienen punteros al argumento. Por lo tanto, la evaluación de
un argumento duplicado se hace a lo más una vez.
El uso de call-by-name junto con sharing es llamado lazy
evaluation o call-by-need.
Alberto Pardo, Marcos Viera
Programación Funcional
Ejemplo de lazy evaluation
Escribimos variables locales para denotar punteros.
square (1 + 2)
=
a∗a
where a = 1 + 2
=
a∗a
where a = 3
=
9
Alberto Pardo, Marcos Viera
Programación Funcional
Funciones no estrictas
True && b = b
False && b = False
True || b = True
False || b = b
if True then e else = e
if False then else e = e
if ⊥
then else = ⊥
El if es estricto en la condición, pero no en las ramas.
Alberto Pardo, Marcos Viera
Programación Funcional
Explotando lazy evaluation
Igualdad de listas.
[]
== [ ]
= True
[]
== ( : ) = False
= False
( : ) == [ ]
(x : xs) == (y : ys) = (x == y ) && (xs == ys)
Ejemplos:
[1, 2] == [1, 2]
True
[1, 2, inf ] == [1, 2]
Alberto Pardo, Marcos Viera
Programación Funcional
Explotando lazy evaluation
Igualdad de listas.
[]
== [ ]
= True
[]
== ( : ) = False
= False
( : ) == [ ]
(x : xs) == (y : ys) = (x == y ) && (xs == ys)
Ejemplos:
[1, 2] == [1, 2]
True
[1, 2, inf ] == [1, 2]
False
[1, inf , 2] == [1, 2, 3]
Alberto Pardo, Marcos Viera
Programación Funcional
Explotando lazy evaluation
Igualdad de listas.
[]
== [ ]
= True
[]
== ( : ) = False
= False
( : ) == [ ]
(x : xs) == (y : ys) = (x == y ) && (xs == ys)
Ejemplos:
[1, 2] == [1, 2]
True
[1, 2, inf ] == [1, 2]
[1, inf , 2] == [1, 2, 3]
False
⊥
Alberto Pardo, Marcos Viera
Programación Funcional
Explotando lazy evaluation
Determinar si dos árboles contienen los mismos valores en las hojas
y en el mismo orden. Los árboles pueden tener formas distintas.
data Btree a = Leaf a | Fork (Btree a) (Btree a)
eqleaves :: Ord a ⇒ Btree a → Btree a → Bool
eqleaves t t 0 = leaves t == leaves t 0
leaves (Leaf a) = [a]
leaves (Fork l r ) = leaves l +
+ leaves r
Alberto Pardo, Marcos Viera
Programación Funcional
Explotando lazy evaluation
Determinar si dos árboles contienen los mismos valores en las hojas
y en el mismo orden. Los árboles pueden tener formas distintas.
data Btree a = Leaf a | Fork (Btree a) (Btree a)
eqleaves :: Ord a ⇒ Btree a → Btree a → Bool
eqleaves t t 0 = leaves t == leaves t 0
leaves (Leaf a) = [a]
leaves (Fork l r ) = leaves l +
+ leaves r
Las listas leaves t y leaves t 0 se van construyendo a demanda.
Alberto Pardo, Marcos Viera
Programación Funcional
Ejemplo de evaluación
eqleaves (Fork (Leaf 2) (Leaf 3)) (Leaf 2)
⇒ leaves (Fork (Leaf 2) (Leaf 3)) == leaves (Leaf 2)
⇒ leaves (Leaf 2) +
+ leaves (Leaf 3) == leaves (Leaf 2)
⇒ [2] ++ leaves (Leaf 3) == leaves (Leaf 2)
⇒ 2 : ([ ] ++ leaves (Leaf 3)) == leaves (Leaf 2)
⇒ 2 : ([ ] ++ leaves (Leaf 3)) == 2 : [ ]
⇒ (2 == 2) && ([ ] +
+ leaves (Leaf 3) == [ ])
⇒ True && ([ ] +
+ leaves (Leaf 3) == [ ])
⇒ [ ] ++ leaves (Leaf 3) == [ ]
⇒ leaves (Leaf 3) == [ ]
⇒ 3 : [ ] == [ ]
⇒ False
Alberto Pardo, Marcos Viera
Programación Funcional
Definición de eqleaves en un lenguaje no lazy
eqleaves t t 0 = comparestacks [t ] [t 0 ]
comparestacks
comparestacks
comparestacks
comparestacks
comparestacks
comparestacks
[] []
= True
[ ] (t : ts)
= False
(t : ts) [ ]
= False
(Fork l r : ts) ts 0 = comparestacks (l : r : ts) ts 0
ts (Fork l r : ts 0 ) = comparestacks ts (l : r : ts 0 )
(Leaf a : ts) (Leaf b : ts 0 )
| a == b = comparestacks ts ts 0
| otherwise = False
Alberto Pardo, Marcos Viera
Programación Funcional
Explotando lazy evaluation
Determinar si existe algún elemento de una lista que cumple un
predicado.
exists :: (a → Bool) → [a] → Bool
exists p [ ]
= False
exists p (x : xs) | p a
= True
| otherwise = exists p xs
exists xs = not ◦ null ◦ filter p
El procesamiento termina en cuanto un elemento pase el filter .
En Haskell esta función es llamada any .
Alberto Pardo, Marcos Viera
Programación Funcional
Estructuras infinitas
Lazy evaluation permite algo que en principio parece
imposible: programar con estructuras infinitas.
Un ejemplo de esto lo vimos cuando evaluamos
fst (length [ ], inf )
Gracias a lazy evaluation, evitamos producir la expresión
infinita 1 + (1 + (1 + ...)) denotada por inf .
Alberto Pardo, Marcos Viera
Programación Funcional
Listas infinitas
Un ejemplo clásico de lista infinita es la lista de enteros ones:
ones = 1 : ones
Alberto Pardo, Marcos Viera
Programación Funcional
Listas infinitas
Un ejemplo clásico de lista infinita es la lista de enteros ones:
ones = 1 : ones
Evaluar ones produce una lista infinita de unos.
> ones
[1, 1, 1, 1, 1, 1, 1, 1, ...
Alberto Pardo, Marcos Viera
Programación Funcional
Listas infinitas
Un ejemplo clásico de lista infinita es la lista de enteros ones:
ones = 1 : ones
Evaluar ones produce una lista infinita de unos.
> ones
[1, 1, 1, 1, 1, 1, 1, 1, ...
Pero gracias a lazy evaluation podemos procesar sólo lo
necesario de esta lista:
head (tail ones)
⇒ head (tail (1 : ones))
⇒ head ones
⇒ head (1 : ones)
⇒1
Alberto Pardo, Marcos Viera
Programación Funcional
Listas infinitas
Lazy evaluation nos provee la siguente propiedad: de una
expresión se evalúa únicamente lo que sea requerido por el
contexto en la cual se usa.
Bajo este concepto, más que como una lista infinita, ones
puede verse como una lista potencialmente infinita.
Esta idea no está restringida a listas, se aplica a cualquier
estructura de datos.
Alberto Pardo, Marcos Viera
Programación Funcional
Programación modular
Lazy evaluation permite separar control de datos.
Por ejemplo, un lista de tres 1s puede ser generada tomando
los tres primeros elementos (control) de la lista ones (datos).
> take 3 ones
[1, 1, 1]
Alberto Pardo, Marcos Viera
Programación Funcional
Programación modular
Lazy evaluation permite separar control de datos.
Por ejemplo, un lista de tres 1s puede ser generada tomando
los tres primeros elementos (control) de la lista ones (datos).
> take 3 ones
[1, 1, 1]
Algún cuidado es igualmente necesario para evitar no
terminación:
> filter (6 5) [1 . .]
[1, 2, 3, 4, 5
Alberto Pardo, Marcos Viera
Programación Funcional
Programación modular
Lazy evaluation permite separar control de datos.
Por ejemplo, un lista de tres 1s puede ser generada tomando
los tres primeros elementos (control) de la lista ones (datos).
> take 3 ones
[1, 1, 1]
Algún cuidado es igualmente necesario para evitar no
terminación:
> filter (6 5) [1 . .]
[1, 2, 3, 4, 5
En este caso es mejor algo de este estilo, ya que termina:
> takeWhile (6 5) [1 . .]
[1, 2, 3, 4, 5]
Alberto Pardo, Marcos Viera
Programación Funcional
Funciones sobre listas infinitas
iterate f x = [power f i x | i ← [0 . .]]
power f 0 = id
power f n = f ◦ power f (n − 1)
iterate :: (a → a) → a → [a]
iterate f x = x : iterate f (f x)
[n . .] = iterate (+1) n
nats = iterate (+1) 0
[m . . n ] = takeWhile (6 n) (iterate (+1) m)
pot2 = iterate (∗2) 1
Alberto Pardo, Marcos Viera
Programación Funcional
Funciones sobre listas infinitas
Dı́gitos de un número.
digits :: Int → [Int ]
digits = reverse ◦
map (‘mod‘10) ◦ takeWhile (6≡ 0) ◦ iterate (‘div ‘10)
Alberto Pardo, Marcos Viera
Programación Funcional
Funciones sobre listas infinitas
Dı́gitos de un número.
digits :: Int → [Int ]
digits = reverse ◦
map (‘mod‘10) ◦ takeWhile (6≡ 0) ◦ iterate (‘div ‘10)
Quebrar una lista en segmentos de largo n.
group :: Int → [[a]]
group n = map (take n) ◦
takeWhile (not ◦ null) ◦ iterate (drop n)
Alberto Pardo, Marcos Viera
Programación Funcional
Funciones sobre listas infinitas
Dı́gitos de un número.
digits :: Int → [Int ]
digits = reverse ◦
map (‘mod‘10) ◦ takeWhile (6≡ 0) ◦ iterate (‘div ‘10)
Quebrar una lista en segmentos de largo n.
group :: Int → [[a]]
group n = map (take n) ◦
takeWhile (not ◦ null) ◦ iterate (drop n)
Esquema de generación de listas infinitas (streams):
unfold :: (a → b) → (a → Bool) → (a → a) → a → [b ]
unfold h p t = map h ◦ takeWhile p ◦ iterate t
Alberto Pardo, Marcos Viera
Programación Funcional
La Criba de Eratóstenes
La criba de Eratóstenes es un algoritmo que permite hallar todos
los números primos.
La descripción del algoritmo es esencialmente la siguiente:
1
Liste todos los números naturales desde el 2.
2
Marque el primer elemento p de esta lista como primo.
3
Tache todos los elementos múltiplos de p de la lista.
4
Retorne al paso 2.
Una solución:
primos = criba [2 . .]
criba (p : xs) = p : criba [x | x ← xs, x ‘mod‘ p 6≡ 0]
Alberto Pardo, Marcos Viera
Programación Funcional
Otras listas infinitas
Definición alternativa de la lista de naturales:
nats 0 = 0 : zipWith (+) ones nats 0
Lista de factoriales:
factorials = 1 : zipWith (∗) [1 . .] factorials
Otra forma menos eficiente:
factorials 0 = map fact nats
fact 0 = 1
fact n = n ∗ fact (n − 1)
Alberto Pardo, Marcos Viera
Programación Funcional
Árboles infinitos
btree1 = Fork (Leaf 5) btree1
btree2 = Fork (Fork btree2 (Leaf 2)) (Fork (Leaf 3) btree1 )
leftmost (Leaf x) = x
leftmost (Fork l r ) = leftmost l
Alberto Pardo, Marcos Viera
Programación Funcional
Árboles infinitos
btree1 = Fork (Leaf 5) btree1
btree2 = Fork (Fork btree2 (Leaf 2)) (Fork (Leaf 3) btree1 )
leftmost (Leaf x) = x
leftmost (Fork l r ) = leftmost l
ejemplo1 = leftmost btree1
Alberto Pardo, Marcos Viera
Programación Funcional
Árboles infinitos
btree1 = Fork (Leaf 5) btree1
btree2 = Fork (Fork btree2 (Leaf 2)) (Fork (Leaf 3) btree1 )
leftmost (Leaf x) = x
leftmost (Fork l r ) = leftmost l
ejemplo1 = leftmost btree1
ejemplo2 = leftmost btree2
Alberto Pardo, Marcos Viera
Programación Funcional
Árboles infinitos
btree1 = Fork (Leaf 5) btree1
btree2 = Fork (Fork btree2 (Leaf 2)) (Fork (Leaf 3) btree1 )
leftmost (Leaf x) = x
leftmost (Fork l r ) = leftmost l
ejemplo1 = leftmost btree1
ejemplo2 = leftmost btree2
ejemplo3 = take 5 $ map (∗2) $ leaves btree1
Alberto Pardo, Marcos Viera
Programación Funcional
Árboles infinitos
btree1 = Fork (Leaf 5) btree1
btree2 = Fork (Fork btree2 (Leaf 2)) (Fork (Leaf 3) btree1 )
leftmost (Leaf x) = x
leftmost (Fork l r ) = leftmost l
ejemplo1 = leftmost btree1
ejemplo2 = leftmost btree2
ejemplo3 = take 5 $ map (∗2) $ leaves btree1
ejemplo4 = take 5 $ map (∗2) $ leaves btree2
Alberto Pardo, Marcos Viera
Programación Funcional
Descargar