Tabla de Contenidos

Clase 4

Aplicación parcial (secciones)

Tomemos la siguente función Haskell que es una ligera variación de esMultiplo que está en el problemario.

  esDivisor :: Int -> Int -> Bool
  esDivisor p n = n `mod` p == 0

Dado que el constructor de tipos funcionales (ver Práctico 5) asocia a derecha, tenemos que en realidad:

esDivisor :: Int -> (Int -> Bool)

Esto lo podemos leer como que esDivisor es una función que toma un entero y devuelve una función de tipo Int→Bool. Esto es lo mismo que pasa en los ejercicios 5f, 5g y 5i del Práctico 5, y muestra que una función es un ciudadano de primera categoría en Haskell, puede ser devuelto como resultado de una función y también veremos que puede ser un parámetro de entrada.

Para ganar confianza podemos preguntar a Hugs por el tipo de la aplicación parcial.

Main> :t esDivisor 2
esDivisor 2 :: Int -> Bool

Pero … que significa esDivisor 2? Vemos la siguiente definición:

esPar :: Int -> Bool
esPar x = esDivisor 2 x

La función devuelve True si y solo si el entero es divisible por 2! Notamos que esta definición es idéntica a esta versión sin argumentos que utiliza el mecanismo de secciones

esPar' :: Int -> Bool
esPar' = esDivisor 2

Esta forma de escribir funciones se denomina currificación, en honor al lógico Haskell Curry 1).

Este mecanismo de aplicación parcial de argumentos resulta muy elegante para escribir funciones. Por ejemplo:

incrementa :: Int -> Int
incrementa = (+1)

o bien las usamos directamente en la función map

Main> map (+1) [2,3,4,5]
[3,4,5,6]
Main> map (*2) [2,3,4,5]
[4,6,8,10]
Main> map (==0) [2,3,4,5]
[False,False,False,False]
Main> map (esDivisor 2) [2,3,4,5]
[True,False,True,False]

Tiene que quedar claro que esta es una forma elegante y compacta de definir nuevas funciones a partir de otras, pero no es imprescindible. Por ejemplo, para el tercer ejemplo podemos definir la función auxiliar

igual0 :: Int -> Bool
igual0 x = x == 0

y aplicar esta función sobre la lista

Main> map igual0 [2,3,4,5]
[False,False,False,False]

Ejercicio

Generalización de las funciones vistas (map, filter, fold)

Aplicaciones

?No estamos ya cansados de escribir siempre lo mismo? Las funciones de tipo aplicación, son todas idénticas, salvo por la operación o función que se aplica a cada elemento.

duplicar :: [Int] -> [Int]
duplicar []     = []
duplicar (x:xs) = 2*x : duplicar xs
veintePorCiento :: [Float] -> [Float] 
veintePorCiento []     = []
veintePorCiento (x:xs) = 0.2*x : veintePorCiento xs
cuadrado :: Int -> Int
cuadrado x = x^2

cuadrados :: [Int] -> [Int]
cuadrados []     = []
cuadrados (x:xs) = cuadrado x : cuadrados xs
rangoEdades :: [Int] -> [String]
rangoEdades []     = []
rangoEdades (x:xs) = rangoEdad x : rangoEdades xs

Entonces generalizemos definiendo una función que toma como primer argumento la función que se aplicará a cada elemento de la lista.

Vemos nuevamente la aplicación del alto orden, un nombre muy pomposo para algo bastante natural: las funciones son un tipo más que puede ser tomado como parámetro y devuelto como resultado.

De nuevo, se dice que en Haskell las funciones son ciudadanos de primera categoría. Esto no ocurre en la mayoría de los lenguajes imperativos como C, C++, Pascal, Basic, etc. .

Notar que ya vimos funciones que devuelven funciones! Por ejemplo.

esDivisor :: Int -> (Int -> Bool)
esDivisor 2 :: Int -> Bool

Ejercicio

probar con mapNumeros.(*2).[0,1,2,3], mapNumeros.absoluto.[-10,0,10].

Filtros

Veamos funciones de tipo filtro que hemos creado:

esPar :: Int -> Bool
esPar = esDivisor 2

soloPares :: [Int] -> [Int]
soloPares [] = []
soloPares (x:xs) | esPar x   =  x : soloPares xs
                 | otherwise =      soloPares xs
primeraEsM :: [Char] -> Bool
primeraEsM xs = cabeza xs == 'm'

empiezaM :: [String] -> [String]
empiezaM [] = []
empiezaM (x:xs) | primeraEsM x = x : empiezaM xs
                | otherwise    =     empiezaM xs

Entonces plantemos el mismo tipo de ejercicio, una generalización que tome como primer parámetro un predicado, es decir una función que devuelve un booleano, que decidirá si cada elemento se conserva o se filtra.

Ejercicio

probar con filtraNumeros.entre0y9.[], filtraNumeros.entre0y9.[10,20,30].

Acumuladores

Más y más funciones fueron acumuladores, veamos algunas:

sumatoria :: [Int] -> Int
sumatoria []     = 0
sumatoria (x:xs) = x + sumatoria xs
producto :: Int -> Int -> Int
producto x y = x*y

productoria :: [Int] -> Int
productoria []     = 1
productoria (x:xs) = producto x (productoria xs)
concatenaInt :: [[Int]] -> [Int]
concatenaInt []       = []
concatenaInt (xs:xss) = (++) xs (concatenaInt xss)

Ejercicio

Definir la primera generalización.

probar con paraTodoInt.multiplo2.[2,3,4], paraTodoInt.entre0y9.[1,2,4,2,1], paraTodoInt.multiplo2.[].

Y luego esta que es casi completa.

probar con acumulaInt.(+).0.[1,2,3], acumulaInt.(*).1.[1,2,3].

Escribamos las versiones más generales

Hasta ahora trabajamos con las generalizaciones de las funciones desde el punto de vista que le podíamos dar cualquier función para que operara sobre los valores. Es posible generalizar estas funciones a los tipos.

Aplicaciones (map)

Si escribimos la definición de mapNumeros vemos que solo importa que la función f toma un elemento del tipo de la primer lista y genera un elemento del tipo de la segunda lista, luego podemos utilizar variables de tipo y definir la función:

probar con mapa.ordena.[(1,0)(0,1)], mapa.segundo3.[(10,20,30),(12,22,32),(14,24,34)],
  mapa.longitud.[[],[1],[1,2]], mapa.(\x -> [x,x])."tartamuda".

La escribamos:

mapa :: (a -> b) -> [a] -> [b]
mapa f []     =
mapa f (x:xs) =

Filtros (filter)

Lo mismo para filtraNumeros podemos filtrar los elementos de una lista de cualquier tipo:

probar con filtro.esMultiplo2.[1,2,3,4,5,6], filtro.(esMultiplo.3).[1,2,3,4,5,6,7,8,9,10],
filtro.(==0).[1,2,3,4].

La escribamos:

filtro :: (a->Bool) -> [a] -> [a]
filtro p []     =
filtro p (x:xs) =

Acumuladores (fold)

La generalización de los acumuladores a distintos tipos es un poco más complicada. Veamos la función de acumulación que dice si todos los elementos son 0 y 1.

todos0y1 :: [Int] -> Bool
todos0y1 []     = True
todos0y1 (x:xs) = (x==0 || x==1) && todos0y1 xs

Detectamos:

Podemos reescribirla de la siguiente manera para que la función de acumulación sea más explícita.

ceroUnoYAnteriores :: Int -> Bool -> Bool
ceroUnoYAnteriores x b = (x==0 || x==1) && b

todos0y1' :: [Int] -> Bool
todos0y1' []     = True
todos0y1' (x:xs) = ceroUnoYAnteriores x (todos0y1' xs)

La defunición general sería:

probar con todos los ejemplos de las versiones menos generales.

Nuevamente la escribimos.

acumula :: (a->b->b) -> b -> [a] -> b
acumula f z []     =
acumula f z (x:xs) =

Reescribiendo funciones usando map, fold y filter

Ejercicios

1)
Si si, el lenguaje Haskell se llama así por Haskell Curry.