En esta sección se indicarán varios patrones de tratamiento de estructuras recursivas.
Estos patrones se aplicarán principalmente a listas, pero muchos de ellos pueden generalizarse
a otras estructuras como árboles, rosales, etc.
Ejemplo (
aplica)
> aplica :: (a -> b) -> [a] -> [b]
> aplica f [] = []
> aplica f (x:xs) = f x : aplica f xs
Nota: La función aplica está predefinida como la función
map
Obsérvese la definición de las siguientes funciones
Ejemplo (
sumaLs)
> sumaLs :: Num n => [n] -> n
> sumaLs [] = 0
> sumaLs (x:xs) = x + sumaLs xs
Ejemplo (
prodLs)
> prodLs :: Num n => [n] -> n
> prodLs [] = 1
> prodLs (x:xs) = x * prodLs xs
Ejemplo (
longLs)
> longLs :: Num n => [a] -> n
> longLs [] = 0
> longLs (x:xs) = 1 + longLs xs
En la definición de las siguientes funciones, puede observarse que se utiliza
siempre un mismo patrón. Un caso básico cuando la lista está vacía, cuyo valor es
0, 1 y 0;
y un caso recursivo en el cual se realiza una operación combinando el elemento x con
el resultado de la llamada recursiva. La operación, en el primer caso es \x r -> x + r,
en el segundo caso es \x r -> x * r y en el último caso es \x r -> 1 + r.
El patrón descrito podría generalizarse en una función foldRight que tome como parámetros
el valor e a devolver en el caso básico y la operación op a realizar en el caso recursivo.
Ejemplo (
foldRight)
> foldRight :: (a -> b -> b) -> b -> [a] -> b
> foldRight op e [] = e
> foldRight op e (x:xs) = x `op` foldRight op e xs
?-foldRight (+) 0 [2,3,4]9
Nota: La función foldRight está predefinida como la función foldr
A partir de la función foldr, pueden redefinirse las funciones
sumaLs,prodLs y longLs.
Ejemplo (
sumaLs')
> sumaLs' = foldr (+) 0
Ejemplo (
prodLs')
> prodLs' = foldr (*) 1
Ejemplo (
longLs')
> longLs' = foldr (\x r -> 1 + r) 0
Además de las funciones anteriores, existen numerosas funciones que trabajan sobre listas y
que pueden definirse a partir de la función foldr
Ejemplo (
andLs)
> andLs :: [Bool] -> Bool
> andLs = foldr (&&) True
Nota: La función andLs está predefinida como la función and
Ejemplo (
orLs)
> orLs :: [Bool] -> Bool
> orLs = foldr (||) False
Nota: La función orLs está predefinida como la función or
Ejemplo (
append)
> append :: [a] -> [a] -> [a]
> append xs ys = foldr (\x r -> x : r) ys xs
Nota: La función append está predefinida como el operador (++)
Ejemplo (
vuelta)
> vuelta :: [a] -> [a]
> vuelta = foldr (\x r -> r ++ [x]) []
Nota: La función vuelta está predefinida como la función reverse
Ejemplo (
tomaMientras)
> tomaMientras :: (a -> Bool) -> [a] -> [a]
> tomaMientras f = foldr (\x r -> if f x then x:r else []) []
?-tomaMientras (>3) [6,5,2,4,1] [6,5]
Nota: La función tomaMientras está predefinida como la función
takeWhile
Ejemplo (
filtra)
> filtra :: (a -> Bool) -> [a] -> [a]
> filtra f = foldr (\x r -> if f x then x:r else r) []
?-filtra (>3) [6,5,2,4,1] [6,5,4]
Nota: La función filtra está predefinida como la función
filter
Ejemplo (
comb)
> comb :: [a] -> [b] -> [(a,b)]
> comb xs [] = []
> comb [] ys = []
> comb (x:xs) (y:ys) = (x,y):comb xs ys
?-comb [2,3,4] "hola"[(2,'h'),(3,'o'),(4,'l')]
Nota: La función comb está predefinida como la función zip
El lenguaje Haskell admite una notación que facilita la definición de listas. Esta notación es
similar a las definiciones matemáticas intensionales
Ejemplo (
cpares20)
La función cpares20 calcula los cuadrados de los números pares entre 1 y 20
> cpares20 :: [Integer]
> cpares20 = [x ^ 2 | x <- [1..20], x `mod` 2 == 0]
?-cpares20[4,16,36,64,100,144,196,256,324,400]
Ejemplo (
divisores)
La función divisores calcula los divisores de un número
> divisores :: Integer -> [Integer]
> divisores n = [d | d <- [1..n], n `mod` d == 0]
?-divisores 24[1,2,3,4,6,8,12,24]
La función foldr actúa de la siguiente forma:
foldr op e [x1,x2,x3] = x1 `op` (x2 `op` (x3 `op` e))
Como puede observarse, la operación se asocia hacia la derecha. El nombre foldr
viene de fold-right, a la derecha.
Es posible definir una función fold-left que se denominará foldl y realiza la asociación
a la izquierda.
foldl op e [x1,x2,x3] = ((e `op` x1) `op` x2) `op` x3
Ejemplo (
foldLeft)
> foldLeft :: (b -> a -> b) -> b -> [a] -> b
> foldLeft op e [] = e
> foldLeft op e (x:xs) = foldLeft op (e `op` x) xs
?-foldLeft (+) 0 [1,3,4]8
Nota: La función foldLeft está predefinida en Haskell como foldl
Cuando la operación op es asociativa y e es el elemento neutro de op,
se cunple que:
foldr op e = foldl op e
En general, foldr es más eficiente que foldl.
Sin embargo, foldl puede ser útil en cierto tipo de definiciones
Ejemplo (
ls2n)
La función ls2n convierte una lista de dígitos en un número decimal
> ls2n :: [Integer] -> Integer
> ls2n = foldl (\r a -> r * 10 + a) 0
?-ls2n [1,3,4]134
Si las listas no están vacías, puede ser necesario aplicar la operación sobre los elementos sin
utilizar un elemento básico. Los operadores foldr1 y foldl1 actúan de
la siguiente forma:
foldr1 op [x1,x2,x3] = x1 `op` (x2 `op` x3)
foldl1 op [x1,x2,x3] = (x1 `op` x2) `op` x3
Ejemplo (
maxLs)
La función maxLs devuelve el valor máximo de una lista de enteros
> maxLs :: [Integer] -> Integer
> maxLs = foldr1 max
?-maxLs [-1,-3,-4]-1
Los operadores predefinidos scanr, scanl, scanr1 y scanl1
recorren la lista generando resultados parciales de la siguiente forma:
scanr op e [x1,x2,x3] =
[x1 `op` (x2 `op` (x3 `op` e)), x2 `op` (x3 `op` e), x3 `op` e, e]
scanl op e [x1,x2,x3] =
[e, e `op` x1, (e `op` x1) `op` x2, ((e `op` x1) `op` x2) `op` x3]
scanr1 op e [x1,x2,x3] =
[x1 `op` (x2 `op` x3), x2 `op` x3, x3]
scanl1 op e [x1,x2,x3] =
[x1, x1 `op` x2, (x1 `op` x2) `op` x3]
Ejemplo (
ruffini)
La función ruffini aplica la regla de ruffini
> ruffini :: Float -> [Float] -> [Float]
> ruffini a = scanl1 (\r x -> r * a + x)
?-ruffini 3 [2,4,3][2,10,33]
La generación de una lista a partir de un valor puede capturarse mediante
la siguiente función
Ejemplo (
unfold)
La función unfold realiza el proceso inverso a la función foldr
> unfold :: (a -> Bool) -> (a -> b) -> (a -> a) -> a -> [b]
> unfold p f g x = if p x then []
> else f x : unfold p f g (g x)
Ejemplo (
d2b)
La función d2b convierte un número decimal en su representación binaria
> d2b :: Integer -> [Integer]
> d2b = reverse . unfold (==0) (`mod` 2) (`div` 2)
?-d2b 13[1,1,0,1]
Ejemplo (
pals)
La función pals convierte devuelve la lista de palabras que forman una cadena de caracteres
> pals :: String -> [String]
> pals = palsAux . quitaEsp
> where palsAux = unfold (=="") tomaPal quitaPal
> tomaPal = takeWhile (not . esp)
> quitaPal = quitaEsp . dropWhile (not . esp)
> quitaEsp = dropWhile esp
> esp x = x == ' ' || x == '\n'
?-pals "mi perro es listo"["mi","perro","es","listo"]
Los combinadores definidos en las secciones anteriores pueden
generalizarse para otras estructuras recursivas.
Ejemplo (
foldA)
La función foldA recorre y transforma un árbol en un valor
> foldA :: (a -> b -> b -> b) -> b -> Arbol a -> b
> foldA f e Hoja = e
> foldA f e (Rama x i d) = f x (foldA f e i) (foldA f e d)
La función sumaArbol calcula la suma de los nodos de un árbol
> sumaArbol :: Num n => Arbol n -> n
> sumaArbol = foldA (\x i d -> x + i + d) 0
?-sumaArbol a114
La función nodosArbol devuelve la lista de nodos de un árbol
> nodosArbol :: Arbol a -> [a]
> nodosArbol = foldA (\x i d -> [x] ++ i ++ d) []
?-nodosArbol a1[3,4,5,2]
Ejemplo (
foldRosal)
La función foldRosal recorre y transforma un rosal en un valor
> foldRosal :: (a -> [b] -> b) -> b -> Rosal a -> b
> foldRosal f e Vacio = e
> foldRosal f e (Nodo x ls) = f x (map (foldRosal f e) ls)
La función sumaRosal calcula la suma de los nodos de un rosal
> sumaRosal :: Num n => Rosal n -> n
> sumaRosal = foldRosal (\x ls -> x + sum ls) 0
La función nodosRosal devuelve la lista de nodos de un rosal
> nodosRosal :: Rosal a -> [a]
> nodosRosal = foldRosal (\x ls -> [x] ++ concat ls) []
Nota:
Sería deseable definir una función fold genérica que recorriese y transformase
todo tipo de estructuras recursivas. Aunque Haskell no permite este tipo de definiciones,
existe una extensión del lenguaje denominada Generic Haskell que permite
definir funciones genéricas, también denominadas politípicas.
Muchas estructuras recursivas básicas que en otros lenguajes están predefinidas, pueden
ser definidas por el usuario
Ejemplo (
hasta)
La función hasta captura un patrón recursivo básico
> hasta :: (a -> Bool) -> (a -> a) -> a -> a
> hasta p f x = if p x then x
> else hasta p f (f x)