Programación Funcional en Haskell Paradigmas de Lenguajes de Programación Facultad de Ciencias Exactas y Naturales Universidad de Buenos Aires 27 de Agosto de 2013 Esquemas de recursión Definamos las siguientes funciones duplicar :: [Int] -> [Int] duplicar [] = [] duplicar (x:xs) = (2 * x) : duplicar xs longitudes :: [[a]] -> [Int] longitudes [] = [] longitudes (xs:xss) = (length xs) : longitudes xss Abstrayendo map :: (a -> b) -> [a] -> [b] map f xs = [f x | x <- xs] duplicar = map (2*) longitudes = map length 2 / 24 Esquemas de recursión sobre listas Definamos las siguientes funciones pares :: [Int] -> [Int] pares [] = [] pares (x:xs) = if mod x 2 == 0 then x : pares xs else pares xs deLongitudN :: Int -> [[a]] -> [[a]] deLongitudN n [] = [] deLongitudN n (xs:xss) = if length xs == n then xs : deLongitudN n xss else deLongitudN n xss Abstrayendo filter :: (a -> Bool) -> [a] -> [a] filter p xs = [k | k <- xs, p k] pares = filter ( \ x -> mod x 2 == 0) deLongitudN n = filter (\ xs -> length xs == n) 3 / 24 Esquemas de recursión sobre listas Definamos las siguientes funciones sum :: Num a => [a] -> Int sum [] = 0 sum (x:xs) = x + sum xs length :: [a] -> Int length [] = 0 length (x:xs) = 1 + length xs concat :: [[a]] -> [a] concat [] = [] concat (xs:xss) = xs ++ concat xss FoldR foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z foldr f z (x : xs) = f x (foldr f z xs) sum = foldr (+) 0 length = foldr (\ x rec -> 1 + rec) 0 concat = foldr (++) [] 4 / 24 Esquemas de recursión sobre listas FoldL foldl :: (b -> a -> b) -> b -> [a] -> b foldl f z [] = z foldl f z (x : xs) = foldl f (f z x) xs 5 / 24 Folds Desdoblando funciones a izquierda y a derecha foldr foldr foldr foldr recorre funciones de izquierda a derecha :: (a -> b -> b) -> b -> [a] -> b f z [] = z f z (x:xs) = f x (foldr f z xs) foldr f z (a1 : (a2 : f a1 (f a2 (f a3 z)) foldl foldl foldl foldl (a3 : []))) = lo hace a izquierda :: (b -> a -> b) -> b -> [a] -> b f z [] = z f z (x:xs) = foldl f (f z x) xs foldl f z (a1 : (a2 : f (f (f z a1) a2) a3 (a3 : []))) = Si el valor de f x y puede ser computado usando solo el valor x entonces foldr no necesita examinar toda la lista. 6 / 24 Ejercicios con foldr y foldl ¿Qué computan estas funciones? f1 :: [Bool] -> Bool f1 = foldr (&&) True f2 :: [a] -> [a] f2 = foldr (:) [] f3 :: [a] -> [a] -> [a] f3 xs ys = foldr (:) ys xs f4 :: [a] -> [a] f4 = foldl (flip (:)) [] 7 / 24 Ejemplo Definir en Haskell la función zip sin usar recursión explı́cita. zip zip zip zip :: [a] -> [b] -> [(a,b)] [] ys = [] (x:xs) [] = [] (x:xs) (y:ys) = (x,y):(zip xs ys) 8 / 24 Ejemplo Definir en Haskell la función zip sin usar recursión explı́cita. zip = foldr (\x rec -> (\lista -> case lista of [] -> [] (y:ys) -> (x,y):(rec ys))) (const []) Recordemos... foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) foldr f z (a1 : (a2 : (a3 : []))) = f a1 (f a2 (f a3 z)) 9 / 24 Tipos de datos Tipos de datos algebraicos. Determinados por la forma de cada elemento. abstractos. Determinados por las operaciones que manipulan sus elementos. Tipos algebraicos se acceden usando pattern matching están formados por constantes que son constructores, teniendo o no argumentos. se definen mediante la cláusula data 10 / 24 Ejercicio Sea el siguiente tipo: data AEB a = Hoja a | Bin (AEB a) a (AEB a) Ejemplo: miÁrbol = Bin (Hoja 3) 5 (Bin (Hoja 7) 8 (Hoja 1)) Definir el esquema de recursión estructural (fold) para árboles estrictamente binarios, y dar su tipo. El esquema debe permitir definir las funciones altura, ramas, #nodos, #hojas, espejo, etc. 11 / 24 ¿Cómo hacemos? Recordemos el tipo de foldr, el esquema de recursión estructural para listas. foldr :: (a -> b -> b) -> b -> [a] -> b ¿Por qué tiene ese tipo? (Pista: pensar en cuáles son los constructores del tipo [a]). Un esquema de recursión estructural espera recibir un argumento por cada constructor (para saber qué devolver en cada caso), y además la estructura que va a recorrer. El tipo de cada argumento va a depender de lo que reciba el constructor correspondiente. (Y todos van a devolver lo mismo!) Si el constructor es recursivo, el argumento correspondiente del fold va a recibir el resultado de cada llamada recursiva. 12 / 24 ¿Cómo hacemos? (Continuación) Miremos bien la estructura del tipo. Estamos ante un tipo inductivo con un constructor no recursivo y un constructor recursivo. ¿Cuál va a ser el tipo de nuestro fold? ¿Y la implementación? 13 / 24 Solución foldAEB::(a->b)->(b->a->b->b)->AEB a -> b = fHoja n foldAEB fHoja (Hoja n) foldAEB fHoja fBin (Bin t1 n t2) = fBin (foldAEB fHoja fBin t1) n (foldAEB fHoja fBin t2) Ejercicio para ustedes: definir las funciones altura, ramas, #nodos, #hojas y espejo usando foldAB. Si quieren podemos hacer alguna en el pizarrón. 14 / 24 Ejercicio Definir el esquema de recursión estructural para el siguiente tipo: data Num a=>Polinomio a = | | | X Cte a Suma (Polinomio a) (Polinomio a) Prod (Polinomio a) (Polinomio a) Luego usar el esquema definido para escribir la función: evaluar::Num a=>a->Polinomio a->a 15 / 24 Solución foldPoli::Num a=>b->(a->b)->(b->b->b)->(b->b->b)->Polinomio a->b foldPoli casoX casoCte casoSuma casoProd pol = case pol of X -> casoX Cte n -> casoCte n Suma p q -> casoSuma (rec p) (rec q) Prod p q -> casoProd (rec p) (rec q) where rec = foldPoli casoX casoCte casoSuma casoProd evaluar::Num a=>a->Polinomio a->a evaluar n = foldPoli n id (+) (*) 16 / 24 Algo un poco más complejo... ¿o no? 1 Definir el tipo de datos RoseTree de árboles no vacı́os, donde cada nodo tiene una cantidad indeterminada de hijos. 2 Escribir el esquema de recursión estructural para RoseTree. Es importante escribir primero su tipo. Usando el esquema definido, escribir las siguientes funciones: 3 hojas, que dado un RoseTree, devuelve una lista con sus hojas ordenadas de izquierda a derecha, según su aparición en el RoseTree. 2 distancias, que dado un RoseTree, devuelve las distancias de su raı́z a cada una de sus hojas. 3 altura, que devuelve la altura de un RoseTree (la cantidad de nodos de la rama m·s larga). Si el RoseTree es una hoja, se considera que su altura es 1. 4 cantNodos, que devuelve la cantidad total de nodos del RoseTree. 1 17 / 24 Tipo RoseTree data RoseTree a = Rose a [RoseTree a] ¿Cómo hacemos el fold? ¿Hay caso/s base? ¿Cuántos llamados recursivos hay que hacer? 18 / 24 Empecemos por el tipo Recordar: un fold siempre toma un argumento por cada constructor del tipo, y además la estructura que va a recorrer. Los argumentos que corresponden a los constructores devuelven siempre algo del tipo que queremos devolver, y reciben tantos argumentos como sus respectivos constructores. Conclusión: reemplazar cada aparición del tipo de lo que estamos recorriendo por el tipo que queremos devolver. Cada argumento del fold va a saber qué hacer con una estructura construida con el constructor correspondiente. 19 / 24 ¿Cómo se aplica esto al RoseTree? data RoseTree a = Rose a [RoseTree a] En el caso del RoseTree, eso no cambia. Si nuestro constructor toma una lista de RoseTrees, tendremos una lista de resultados de las recursiones. Entonces el tipo del fold es... foldRT::(a->[b]->b)->RoseTree a->b 20 / 24 Ahora implementémoslo foldRT::(a->[b]->b)->RoseTree a->b foldRT f (Rose x hs) = f x (map (foldRT f) hs) 21 / 24 Y finalmente... 1 Definir el tipo de datos RoseTree de árboles no vacı́os, donde cada nodo tiene una cantidad indeterminada de hijos. X 2 Escribir el esquema de recursión estructural para RoseTree. Es importante escribir primero su tipo. X Usando el esquema definido, escribir las siguientes funciones: 3 1 2 3 4 5 hojas, que dado un RoseTree, devuelve una lista con sus hojas ordenadas de izquierda a derecha, según su aparición en el RoseTree. distancias, que dado un RoseTree, devuelve las distancias de su raı́z a cada una de sus hojas. altura, que devuelve la altura de un RoseTree (la cantidad de nodos de la rama más larga). Si el RoseTree es una hoja, se considera que su altura es 1. cantNodos, que devuelve la cantidad total de nodos del RoseTree. espejo, análogo al de árbol binario. 22 / 24 Soluciones (prueben resolverlo primero) cantNodos::RoseTree a->Int cantNodos = foldRT (\ rs->1+(sum rs)) espejo::RoseTree a->RoseTree a espejo = foldRT (\x rs-> Rose x (reverse rs)) hojas::RoseTree a->[a] hojas = foldRT (\x rs->if (null rs) then [x] else concat rs) distancias :: RoseTree a -> [Int] distancias = foldRT (\x rs->if (null rs) then [0] else map (+1) (concat rs)) altura::RoseTree a->Int altura = foldRT (\x rs->if (null rs) then 1 else 1+(maxlista rs)) where maxlista = foldr1 max O, alternativamente... altura = (+1).maxlista.distancias where maxlista = foldr1 max (La lista de distancias nunca es vacı́a, ası́ que podemos usar foldr1.) 23 / 24 ¿? ¿? ¿? ¿? ¿? ¿? ¿? ¿? ¿? ¿? ¿? ¿? ¿?