Está en la página 1de 28

Universidad Central de Venezuela

Facultad de Ciencias
Escuela de Computacin
Lecturas en Ciencias de la Computacin
ISSN 1316-6239

Programacin Funcional con


Haskell
Prof. Jorge Salas
ND 2006-01

Laboratorio Mefis
Caracas, Febrero, 2006.

UNIVERSIDAD CENTRAL DE VENEZUELA


FACULTAD DE CIENCIAS
ESCUELA DE COMPUTACION

PROGRAMACION FUNCIONAL CON HASKELL

Prof. Jorge F. Salas O.


Febrero, 2006

Funciones

Comenzaremos repasando la idea de funciones matematicas y veremos como podemos usar


esas funciones para construir programas usando la composicion de funciones.
Una funcion es un objeto matematico que provee una correspondencia entre objetos tomados
de un conjunto de valores llamado el dominio y objetos en alg
un conjunto de llegada llamado
el codominio o rango.
Una funcion asocia a un elemento del dominio un u
nico elemento del rango. Esta propiedad es
importante porque significa que no hay ambig
uedad sobre cual elemento del rango corresponde
a un elemento dado del dominio. Por esta razon, las funciones se dicen bien definidas o
determinsticas.
Un ejemplo simple de funcion es aquella que hace corresponder a un entero dado un elemento del conjunto {menos, cero, mas} dependiendo si el entero es negativo, cero o positivo
respectivamente. Llamaremos a esta funcion signo para reflejar la correspondencia que realiza:

signo(x) =

menos
cero

mas

si x < 0
si x = 0
si x > 0

El dominio de la funcion signo es el conjunto de los enteros y el rango es el conjunto de


valores {menos, cero, mas}.
En la definicion de signo, x se llama el par
ametro formal y representa cualquier elemento
dado del dominio de la funcion. La parte derecha o cuerpo de la regla especifica cual elemento del
rango correspondera al parametro x. En este sentido, la regla para signo representa un n
umero
infinito de ecuaciones individuales, una para cada valor del dominio.
Cuando una funcion provee correspondencia para todos los elementos del dominio se le llama
una funcion total. Por lo tanto, signo es una funcion total sobre los enteros.
Si la regla de la funcion omite la correspondencia para uno o mas posibles elementos del
dominio entonces es una funcion parcial. Por ejemplo, signo2 es una funcion parcial sobre el
dominio de los enteros porque ninguna regla cubre el caso cuando x es 0:


signo2(x) =

menos
mas

si x < 0
si x > 0

Diremos que signo2 esta indefinida cuando x = 0.


Podemos ver una funcion como una caja negra con una entrada representando el parametro
de la funcion y una salida representando el resultado computado por la funcion:

La escogencia de cual valor del rango se produce como salida de la funcion esta determinada
por la regla de la funcion as como por el valor particular del dominio sobre el que se aplica la
funcion. Por ejemplo, signo(6) ; mas. El valor 6 que es suplido a la funcion es llamado el
par
ametro actual. Diremos que la expresion signo(6) se evalua ; a mas.
El proceso de suplir un parametro actual a la funcion se llama la aplicaci
on de la funcion y
diremos que la funcion se aplica a ese parametro actual. Llamaremos argumento de la funcion
al parametro formal o actual dependiendo del contexto en que hablemos.
La vision de una funcion como una regla para transformar entradas a salidas es fundamental
en la programacion funcional. Las cajas negras constituyen los bloque de construcci
on para un
programa funcional y uniendo estos bloque juntos se construyen operaciones mas sofisticadas.
Este proceso de acoplar las cajas se llama composici
on funcional.
Para ilustrar el proceso de composicion de funciones, consideremos la funcion max que computa el maximo de un par de n
umeros m y n:


max(m, n) =

m
n

si m > n
si m n

El dominio de max es el conjunto de pares de n


umeros y su rango es el conjunto de n
umeros.
Podemos verla como una caja negra y utilizarla para computar el maximo de dos n
umeros:

que podemos escribir max(2, 5) ; 5.


Tambien podemos utilizar max como un bloque de construccion para una funcion mas complicada. Suponga que requerimos una funcion max3 que compute el maximo de tres n
umeros.
Una manera elegante de desarrollar max3 es utilizar la funcion max ya definida notando que
max3(a, b, c) = max(a, max(b, c))

Observemos el acoplamiento de los bloques de construccion:

Como max3 provee una asociacion determinstica de tripletas de n


umeros a n
umeros, podemos
tratarla como una caja negra a su vez:

Ahora podemos olvidarnos de los detalles internos de esta nueva caja negra y usarla como
una unidad computacional o bloque de construccion para otras funciones. Por ejemplo, para
construir una funcion que calcule el signo del maximo de cuatro n
umeros a, b, c y d utilizando
las funciones signo y max que ya hemos definido:
SM4(a, b, c, d) = signo(max(a, max3(b, c, d)))
y que podemos representar como una caja negra de la siguiente manera:

Resumiendo: una funcion puede ser vista como una caja negra para resolver un problema y
las funciones pueden ser acopladas juntas para definir funciones mas grandes y poderosas que a
su vez pueden ser vistas como cajas negras para construir funciones a
un mas grandes.
En programacion funcional, dado un conjunto de cajas negras predefinidas llamadas funciones
primitivas que hacen las operaciones basicas, construimos nuevas funciones cajas negras que
hacen cosas mas sofisticadas en terminos de estas primitivas1 .
Luego podemos usar estas nuevas funciones como bloques de construccion para funciones mas
sofisticadas que a su vez pueden ser utilizadas de la misma manera para definir otras funciones, y
as sucesivamente hasta lograr construir la funcion deseada que resuelve el problema planteado.
Podemos definir nuevas funciones tanto para simplificar la definicion de una funcion mas
complicada como para describir una operacion utilizada com
unmente y as evitar escribir la
misma expresion una y otra vez.

Transparencia Referencial

La propiedad fundamental de las funciones matematicas que nos permite acoplar las cajas negras
de la manera antes descrita es la transparencia referencial que significa que cada expresion
denota un u
nico valor que no puede ser cambiado evaluando la expresion o permitiendo que
diferentes partes del programa compartan la expresion.
La evaluacion de una expresion simplemente cambia la forma de la expresion pero nunca su
valor. Todas las referencias a la expresion son por lo tanto equivalentes al valor mismo y el hecho
que la expresion pueda ser referenciada desde otras partes del programa no altera ese valor.
Por su transparencia referencial, una funcion matematica puede ser vista como una caja negra
que computa valores de salida solamente en terminos de sus valores de entrada. Esta propiedad es
lo que distingue a las funciones matematicas de las funciones que se pueden escribir en lenguajes
de programacion imperativa.
En los lenguajes imperativos se permite que las funciones referencien datos globales y se
permiten asignaciones destructivas a esos datos que pueden cambiar sus valores de una invocacion
a otra. Tales cambios dinamicos de los valores de los datos globales se llaman efectos laterales
y debido a ellos el valor producido por una funcion puede variar a
un cuando sus argumentos sean
los mismos cada vez que es invocada.
La presencia de efectos laterales hace que la funcion sea difcil de entender pues para determinar cual valor generara la funcion se debe considerar el valor actual de los datos globales. Esto
a su vez requiere considerar la historia de la computacion desde el comienzo de la ejecucion del
programa hasta el momento actual. Por esto es que se dice que los lenguajes imperativos son
referencialmente opacos.
Para ilustrar la opacidad referencial de los lenguajes imperativos consideremos el siguiente
programa escrito en una sintaxis tipo Pascal.
1

En el lenguaje Haskell estas funciones basicas predefinidas estan contenidas en el archivo prelude que se
carga al inicializarse el interpretador Hug.

programa ejemplo ;
var flag: booleana;
funcion f ( n : entero) : entero;
comenzar
si flag ent f := n dlc f := 2 * n;
flag := not flag
fin;
comenzar
flag := verdad;
escribir(f(1) + f(2));
escribir(f(2) + f(1))
fin.
Al ejecutar este programa obtenemos como salida los n
umeros 5 y 4. En apariencia no se
estara cumpliendo la ley conmutativa de la suma, pues la expresion f (1) + f (2) estara dando
un resultado diferente a f (2) + f (1). La causa de la anomaa es la asignaci
on destructiva
f lag := not f lag.
La asignacion destructiva no esta permitida en el razonamiento matematico el cual esta
basado en la nocion de la igualdad y en el reemplazo de una expresion por otra que signifique la
misma cosa, es decir, que denote el mismo valor. Por ejemplo, podemos reemplazar la expresi
on
4 + 5 por 9 porque ambas expresiones son denotaciones del mismo valor (el n
umero 9).
Una caracterstica de la programacion funcional es que no existen asignaciones destructivas.
En vez de considerar a las variables como receptaculos para valores que pueden ser actualizados
periodicamente por medio de la asignacion de diferentes valores, las variables en un programa
funcional son como las variables matematicas: si existen tienen un valor que no puede cambiar.
Por lo tanto, no hay nocion de estado del programa ni de historia del programa.
En la programacion funcional un programa no es un secuencia de imperativas que describen al
computador como debe resolver un problema en terminos de cambios de estado (modificaciones
a valores de variables) sino que un programa funcional describe que es lo que se va a computar, es
decir, un programa funcional es una expresi
on definida en terminos de funciones primitivas
y otras definidas por el usuario. El valor de la expresion constituye el resultado del programa.

Definici
on de Funciones

Ahora veremos diferentes formas de definir funciones en el lenguaje funcional Haskell.

3.1

Definici
on por Combinaci
on

La forma mas facil y natural de definir funciones es por combinacion utilizando otras funciones:
max :: Int -> Int -> Int
max a b = if a > b then a else b
max3 :: Int -> Int -> Int -> Int
max3 a b c = max a (max b c)
6

fac :: Int -> Int


fac n = product [1..n]
comb :: Int -> Int -> Int
comb n k = fac n / (fac k * fac (n-k))
Es de notar que la aplicacion funcional asocia por la izquierda.

3.2

Definiciones Locales

Podemos definir funciones que utilizen nombres locales a la definicion2 :


raices :: Float -> Float -> Float -> (Float, Float)
raices a b c = ((-b + d)/n, (-b - d)/n)
where d = sqrt (b*b - 4*a*c)
n = 2.0*a
max3 :: Ord a => a -> a -> a -> a
max3 a b c = max a (max b c)
where max x y = if x>y then x else y
La palabra reservada where sirve para declarar definiciones locales. Estas definiciones solo
son visibles dentro del cuerpo de la definicion de funcion donde aparece where.

3.3

Definici
on por Casos

Algunas veces es necesario distinguir diferentes casos en la definicion de una funcion3 :


abs :: Int -> Int
abs x | x < 0 = -x
| x >= 0 = x
signo :: (Num a, Ord a) => a -> a
signo x | x > 0 = 1
| x == 0 = 0
| x < 0 = -1
Las definiciones de los diferentes casos son precedidas por expresiones booleanas llamadas
guardas.
Si se llama una funcion definida por casos, se tratan las guardas una a una, de arriba hacia
abajo, hasta que se encuentre una guarda con el valor True. Entonces, se eval
ua la expresion a
la derecha del smbolo = y este valor se retorna como resultado de la llamada a la funcion.
En ocasiones, la u
ltima guarda es True o la constante otherwise.
En cada expresion guardada debe existir: el smbolo |, una expresion booleana, el smbolo =
y una expresion resultado.
2
3

Ord es la clase de tipos cuyos elementos se pueden ordenar.


Num es la clase tipos numericos.

3.4

Definici
on por Patrones

Los parametros formales en una definicion de funcion no estan restringidos a ser solo nombres.
Ellos pueden ser patrones. Las siguientes construcciones se pueden utilizar como patrones:
N
umeros. Por ejemplo: 3
Las constantes True y False
Nombres. Por ejemplo: x
El smbolo dont care
Listas cuyos elementos tambien son patrones. Por ejemplo: [1,x,y]
El operador : con patrones a la izquierda y a la derecha. Por ejemplo: a:b
Formalmente, un patron es un termino lineal, es decir, en el cual no se repiten las variables.
Ejemplos de funciones definidas por analisis de patrones:
and :: Bool -> Bool -> Bool
and True x = x
and False = False
lon :: [a] -> Int
lon [] = 0
lon ( :y) = 1 + (lon y)
cab :: [a] -> a
cab (x: ) = x
cab [ ] = error "invalido"
cola :: [a] -> [a]
cola ( :x) = x
cola [ ] = error "invalido"

3.5

Definici
on Recursiva

En la definicion de una funcion se pueden usar las funciones estandares, las funciones definidas
previamente por el usuario, las definiciones locales y tambien la propia funcion que se define en
la definicion. Cuando ocurre esto u
ltimo la llamamos una definici
on recursiva.
Las definiciones recursivas requieren las siguientes dos condiciones:
1. Existe al menos una definicion no recursiva para un caso base.
2. El parametro de la llamada recursiva es mas simple que el parametro de la funcion que se
quiere definir. Formalmente, la secuencia de parametros en las llamadas recursivas debe
ser un conjunto bien fundado, es decir, no deben existir cadenas decrecientes infinitas.
Ejemplos de definiciones recursivas:
fact :: Int -> Int
fact n | n==0 = 1
| n>0 = n * fact (n-1)
8

suml :: [Int] -> Int


suml [ ] = 0
suml (x:y) = x + suml y
lon :: [a] -> Int
lon [ ] = 0
lon ( :y) = 1 + lon y
Una definicion recursiva en la cual se usan patrones para distinguir diferentes casos (en lugar
de expresiones booleanas) se llama tambien definici
on inductiva.

3.6

Definiciones An
onimas

En vez de utilizar ecuaciones para definir funciones, tambien podemos definirlas anonimamente
va abstracciones lambda:
\x -> x + 1
Tambien podemos definir anonimamente funciones de mas de un parametro:
\x y -> x + y que es equivalente a \x -> \y -> x + y
Una definicion anonima puede utilizarse por si misma o asociarse al nombre de una funcion:
inc = \x -> x + 1

mas = \x y -> x + y

En general, si x tiene tipo t1 y exp tiene tipo t2 , entonces \x -> exp tiene tipo t1 -> t2 .

3.7

Composici
on de Funciones

La composicion de funciones es un operador infijo:


(.) :: (b -> c) -> (a -> b) -> (a -> c)
Por ejemplo:

f . g = \x -> f (g x)

sum2 6 where sum2 = inc . inc ; 8.

Currificaci
on

Considere la definicion de la funcion mas que suma dos enteros:


mas :: Int -> Int -> Int
mas x y = x + y
En una expresion, esta funcion espera recibir dos parametros, por ejemplo, suma 2 3. Sin
embargo, en los lenguajes funcionales, se pueden dar menos parametros a una funcion que los que
aparecen en su definicion. En este caso, el resultado de la expresion es una funci
on especializada
que espera por los parametros restantes. Por ejemplo, la funcion inc incrementa en uno su
parametro:
9

inc :: Int -> Int


inc = mas 1
En otro ejemplo, la funcion max0 retorna el maximo entre cero y su parametro:
max0 :: Int -> Int
max0 = max 0
Notemos que la aplicacion funcional asocia por la izquierda y -> asocia por la derecha:
max :: Int -> Int -> Int

max :: Int -> (Int -> Int)

Por lo tanto, dado un solo parametro, max devuelve una funcion con tipo Int -> Int.
Llamar una funcion con menos parametros de los que espera se llama instanciar o aplicar
parcialmente la funcion.
En realidad, en los lenguajes funcionales, existen solamente funciones de un parametro. Estas
funciones pueden devolver otra funcion que tiene un parametro. De esta manera, parece que la
funcion original tiene dos o mas parametros.
Esta estrategia de describir las funciones de mas de un parametro por funciones de un
parametro que devuelven otra funcion, se llama currificaci
on. La funcion devuelta se llama
funci
on currificada. Este metodo se debe a Moses Schonfinkel y Haskell Curry.

4.1

Secciones de Operadores

Como los operadores infijos en realidad son funciones, tiene sentido poder aplicarlos parcialmente
tambien. En programacion funcional, la aplicacion parcial de un operador infijo se llama una
seccion. Por ejemplo:
(x+)
\y -> x + y
(+y)
\x -> x + y
(+) \x y -> x + y
La u
ltima forma de seccion esencialmente coerciona un operador infijo en un equivalente valor
funcional y es conveniente cuando se necesita pasar un operador infijo como un argumento a una
funcion. Por ejemplo:
foldr (+) 0 [1,2,3] ; (1+(2+(3+0)))
As como podemos coercionar a un operador infijo a un valor funcional, tambien podemos
hacer lo contrario simplemente encerrando entre comillas izquierdas (backquotes) un identificador
asociado a un valor funcional binario. Por ejemplo:
x mas y es equivalente a mas x y

10

Funciones de Orden Superior

Hemos vistos que en las funciones, los parametros son n


umeros, valores booleanos o listas. Pero un
parametro de una funcion tambien puede ser una funcion. De hecho, en los lenguajes funcionales,
las funciones se comportan de muchas maneras igual que otros valores:
las funciones tienen un tipo,
pueden retornar otras funciones como resultados (p.e. en la currificacion),
pueden ser parametros de otras funciones
Con la u
ltima posibilidad, se pueden escribir funciones globales cuyo resultado depende de
una funcion que es uno de los parametros de la funcion global.
A las funciones que tienen funciones como parametros se las llama funciones de orden superior.
La funcion map es una funcion de orden superior que recorre todos los elementos de una lista
aplicandole la funcion parametro a cada elemento de la lista:
map :: (a -> b) -> [a] -> [b]
map f [ ] = [ ]
map f (x:y) = f x : map f y
La funcion filter devuelve los elementos de una lista que cumplen una condicion:
filter :: (a -> Bool) -> [a] -> [a]
filter p [ ] = [ ]
filter p (x:y) | p x = x : filter p y
| otherwise = filter p y
La funcion foldr pliega una lista a un valor colocando un operador parametro entre los
elementos de la lista empezando por la derecha con un valor inicial dado como parametro4 :
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr op e [ ] = e
foldr op e (x : y) = x op foldr op e y
suml :: [Int] -> Int
suml = foldr (+) 0
prodl :: [Int] -> Int
prodl = foldr (*) 1
andl :: [Bool] -> Bool
and = foldr (&&) True
Las funciones de orden superior tienen similar papel en los lenguajes funcionales que las
estructuras de control en los lenguajes imperativos. Pero mientras en los lenguajes imperativos
las estructuras de control son primitivas, en los lenguajes funcionales uno puede definir estas
funciones de orden superior. Esto hace a los lenguajes funcionales mas flexibles: hay pocas
primitivas pero uno puede definir todas las demas que necesite (ortogonalidad ).
4

Un operador entre parentesis se comporta como la funcion correspondiente y una funci


on entre comillas
izquierdas se comporta como el operador correspondiente.

11

Expresiones de Tipo y Polimorfismo

Todas las expresiones de un lenguaje funcional tienen tipo. En el caso especial de una funcion,
este puede ser especificado en su definicion.
En la programacion funcional se describe una funcion en dos pasos:
(1) describimos el tipo de la funcion el cual es una especificacion explcita del dominio y rango
de la funcion;
(2) describimos que hace la funcion en terminos de una regla que especifica que debe hacer la
funcion con sus argumentos para generar el resultado requerido.
Por ejemplo, en haskell:

cuad :: Int -> Int


cuad x = x * x

(1)
(2)

En la declaracion de tipo indicamos:


(a) el nombre de la funcion (cuad) seguido del smbolo :: que se lee es de tipo
(b) una expresion de tipo que describe el tipo de la funcion.
Podemos describir la sintaxis de las expresiones de tipo mediante la sintaxis:
exp tipo

::=
|
|
|
|
|

tipo estandar
var tipo
exp tipo -> exp tipo
( exp tipo , exp tipo )
[exp tipo]
... tipos definidos por el programador ...

Existen cuatro tipos estandar:


Int: el tipo de los enteros;
Float: el tipo de los n
umeros de punto flotante;
Bool: el tipo de los valores booleanos: True y False;
Char: el tipo de los caracteres.
Los lenguajes funcionales son fuertemente tipeados. Esto significa que cada funcion esta
definida para operar sobre objetos de un tipo especfico de tal manera que una aplicacion de la
funcion a un objeto de tipo inapropiado genera un error.
Aunque el tipo de cada funcion debe declararse obligatoriamente en algunos lenguajes, por
ejemplo Hope, esto no es un requerimiento general para el tipeo fuerte.
En la mayora de los lenguajes funcionales, el tipo de cada funcion puede ser inferido automaticamente sin necesidad de una declaracion explcita por parte del programador. Sin embargo,
12

en la programacion funcional, el tipeo es una parte importante del proceso intelectual de la


programacion y por lo tanto, se recomienda hacerlo.
La principal ventaja del tipeo fuerte es que muchos errores de programacion pueden ser
eliminados antes que el programa sea ejecutado.
Aunque la declaracion del tipo es superflua, tiene dos ventajas:
se comprueba si la funcion tiene el tipo que esta declarado
la declaracion del tipo ayuda a entender la funcion
No hace falta escribir la declaracion directamente delante la definicion. Por ejemplo, se
pueden escribir primero las declaraciones de los tipos de todas las funciones que estan definidas
en el programa. Las declaraciones en este caso funcionan mas o menos como ndice.

6.1

Polimorfismo

El polimorfismo significa que una funcion puede ser aplicada a una variedad de tipos de argumentos. En los lenguajes imperativos tenemos especies limitadas de polimorfismo:
Sobrecarga de operadores, tales como los operadores aritmeticos, donde una misma operaci
on como la suma o la resta puede ser aplicada a tipos Integer, Real, etc.
Polimorfismo Ad hoc, cuando dos operaciones diferentes tienen el mismo nombre, por
ejemplo, el + para la suma y para la concatenacion de cadenas. Este polimorfismo es
confuso y puede ser fuente de muchos errores.
Polimorfismo por plantillas o templates, que se utiliza en lenguajes como C++ o Java y
consiste que una funcion o metodo pueda definirse varias veces con el mismo nombre y
diferentes tipos de parametros. A tiempo de llamada y de acuerdo al tipo de los parametros
actuales, se selecciona el cuerpo de la funcion o metodo a ser ejecutado.
En los lenguajes funcionales, se utiliza polimorfismo parametrizado donde una operacion
es aplicable a todas las situaciones consistentes con la especificacion de la funcion:
mas :: Num a => a -> a -> a
en este caso, la funcion mas es aplicable a cualquier tipo numerico y el parametro de tipo a es
reemplazado consistentemente con el mismo tipo de acuerdo a la aplicacion de la funcion:
mas :: Int -> Int -> Int
El polimorfismo parametrizado es muy poderoso porque permite expresar una funcion en
terminos generales no dependiendo de las caractersticas particulares del parametro seleccionado.
Por ejemplo, podemos contar la longitud de una lista u ordenar la lista sin necesidad de saber el
tipo de datos de los elementos almacenados en la lista:
lon :: [a] -> Int

sort :: [a] -> [a]


13

Listas

Las listas o secuencias se usan para agrupar varios elementos del mismo tipo. Para cada tipo
existe un tipo lista de tipo . Por tanto, existen listas de enteros, lista de floats, listas de
funciones de entero a entero, etc.:
[1, 2, 3, 0] :: [Int]
[x>y, true, 1/=a] :: [Bool]
[\x -> x + 1, fac, (\x y -> x*y) 2] :: [Int -> Int]
Tambien se pueden agrupar en una lista listas del mismo tipo. El resultado sera, por ejemplo,
lista de listas de enteros, lista de listas de booleanos, etc.:
[[1, 2, 3], [0, -1, 4], [9]] :: [[Int]]
[[[sin, cos]], [[tan, cos], [sin]]] :: [[[Float -> Float]]]

7.1

Construcci
on de listas

Existen cuatro maneras de construir listas: enumeracion, constructor :, intervalos numericos y


por comprension.
7.1.1

Enumeraci
on

Es la manera mas facil de construir listas. Consiste en la enumeracion de los elementos de la


lista. Los elementos enumerados deben ser del mismo tipo:
[1, 2+x, 3] :: [Int]
[sin, cos, tan] :: [Float -> Float]
[True, False, True, a==7] :: [Bool]
[ ] :: [a]
[[1, 2], [3*7], [ ]] :: [[Int]]
7.1.2

Constructor :

Otra manera de construir listar es por medio del operador constructor :. Este operador a
nade
un elemento al principio de una lista y construye de esta manera una lista m
as larga:
1 : [2, 3, 4, 5] ; [1, 2, 3, 4, 5]
El constructor : tiene tipo
(:) :: a -> [a] -> [a]
Usando la lista vaca y el operador : se puede construir cualquier lista:
1 : (2 : (3 : [ ])) es la lista [1, 2, 3]
El operador : asocia por la derecha:
1 :

2 :

3 : []

14

1 : (2 : (3 : [ ]))

7.1.3

Intervalos num
ericos

Se pueden construir listas con la notracion de intervalos: dos expresiones numericas separadas
por dos puntos y rodeadas de corchetes:
[1 .. 5] es la lista [1, 2, 3, 4, 5]
El valor de la expresion [x..y] se calcula por una llamada de la funcion enumFromTo x y
cuya definicion es:
enumFromTo :: Enum a => a -> a -> [a]
enumFromTo x y | y < x = [ ]
| otherwise = x : enumFromTo (x+1) y
7.1.4

Por comprensi
on

La comprension de listas es otra manera de definir listas y operaciones sobre ellas. Es una forma
natural de definicion utilizada en teora de conjuntos. Por ejemplo, para definir el conjunto de
enteros pares entre 0 y 20 escribimos:
[x | x <- [0 .. 20], even x]
donde <- significa y la coma es la conjuncion logica. De esta forma se puede escribir casi todo
lo que se pueda expresar como un conjunto. La frase x <- xs se llama un generador y puede
haber mas de uno en una definicion. Por ejemplo, para multiplicar los elementos de una lista
por los de otra, podemos definir la siguiente funcion:
mul xs ys = [x*y | x <- xs, y <- ys]

7.2

Funciones sobre listas

Las funciones sobre listas mas usuales son recursivas y definidas por medio de patrones, es decir,
son funciones definidas inductivamente. Puesto que cada lista o es vaca o tiene un primer
elemento x que esta delante de una lista xs (que puede ser vaca), la funcion se define para el
caso base de la lista vaca [ ] y para una lista que tiene la forma x : xs. En el caso recursivo,
con el patron x : xs la funcion se llama a si misma con el parametro xs que es menor que x : xs
cumpliendo as el requerimiento del buen fundamento en la recursion.
Estudiemos ahora algunas definiciones de funciones sobre lista.
7.2.1

Comparar y ordenar listas

Se pueden comparar y ordenar listas entre si con la condicion que se puedan comparar y ordenar
sus elementos. Es decir, el tipo [ ] pertenece a los tipos comparables Eq si esta en Eq y el tipo
[ ] esta en la clase Ord de los tipos ordenables si esta en Ord.
Dos listas son iguales si tienen exactamente los mismos elementos en el mismo orden:

15

igl [ ] [ ]
= True
igl (x:xs) (y:ys) = x == y && (igl xs ys)
igl
= False
El operador == de igualdad sobre listas se define de la siguiente manera:
(==) :: Eq
[ ] ==
[ ] ==
(x:xs) ==
(x:xs) ==

a => [a] -> [a] -> Bool


[ ]
= True
(y:ys) = False
[ ]
= False
(y:ys) = x == y && xs == ys

Si se pueden ordenar los elementos de una lista con <, <=, etc., tambien se pueden ordenar
entre si las listas seg
un el orden lexicogr
afico. El primer elemento de las listas decide el orden de
las listas, a no ser que sean iguales. En ese caso, decide el segundo elemento, a no ser que sean
iguales, etc. Si una de las listas es el comienzo de la otra, la mas corta en la menor. Por tanto,
las siguientes expresiones son verdaderas:
[2,3] < [3,1]

[2,1] < [2,2]

[2,3] < [2,3,4]

El operador <= de ordenamiento entre listas se define de la siguiente manera:


(<=) :: Ord a => [a] -> [a] -> Bool
[ ] <= ys
= True
(x:xs) <= [ ]
= False
(x:xs) <= (y:ys) = x < y  (x == y && xs <= ys)
Con los operadores == y <= se pueden definir otros operadores de comparacion:
xs
xs
xs
xs

/= ys
>= ys
< ys
> ys

=
=
=
=

not (xs == ys)


ys <= xs
xs <= ys && xs /= ys
ys < xs

Ejercicio: Definir inductivamente los operadores de comparacion anteriores.


7.2.2

Concatenaci
on de listas

Se pueden unir dos listas del mismo tipo en una sola lista con el operador ++. Esta operacion se
llama concatenaci
on:
[1,2,3] ++ [4,5] ; [1,2,3,4,5]
El elemento neutro de la concatenacion es la lista vaca [ ].
El operador ++ es una funcion estandar que se realiza en el preludio y puede definirse as:
(++) :: [a] -> [a] -> [a]
[ ] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
16

Ejercicio: Definir la funcion concat que se aplica a una lista de listas. Todas las listas en la
lista de listas se concatenan en una sola lista:
concat [[1,2,3], [4,5], [ ], [6]] ; [1,2,3,4,5,6]
7.2.3

Selecci
on de partes de una lista

En el preludio se definen varias funciones que sirven para seleccionar partes de una lista. Para
algunas funciones el resultado es una sublista de la lista inicial, en otras es un elemento de ella.
Ya que una lista se construye con una cabeza y una cola, es facil recuperar estos componentes:
head :: [a] -> a
head (x:xs) = x
tail :: [a] -> [a]
tail (x:xs) = xs
La funcion recursiva last selecciona el u
ltimo elemento de una lista:
last :: [a] -> a
last [x] = x
last (x:xs) = last xs
Note que las tres funciones anteriores no estan definidas para una lista vaca, si se les llama
con [ ] como parametro, el resultado es un mensaje de error.
La funcion init selecciona todo excepto el u
ltimo elemento de una lista:
init :: [a] -> [a]
init [x] = [ ]
init (x:xs) = x : init xs
La funcion take tiene ademas de una lista, un entero como parametro el cual determina
cuantos de los primeros elementos de la lista estar
an en el resultado:
take
take
take
take

:: Int -> [a] -> [a]


0 xs = [ ]
n [ ] = [ ]
n (x:xs) = x : take (n-1) xs

Note que si la lista es mas peque


na que el entero especificado, se selecciona la lista completa.
La funcion drop elimina los primeros n elementos de una lista:
drop
drop
drop
drop

:: Int -> [a] -> [a]


0 xs = xs
n [ ] = [ ]
n (x:xs) = drop (n-1) xs

Note que si la lista es mas peque


na que el entero especificado, se elimina la lista completa.

17

El operador !! selecciona un elemento de una lista. El primer elemento tiene ndice 0:


(!!) :: [a] -> Int -> a
(x:xs) !! 0 = x
(x:xs) !! n = xs !! (n-1)
7.2.4

Reverso de una lista

La funcion reverse del preludio invierte los elementos de la lista:


reverse :: [a] -> [a]
reverse [ ] = [ ]
reverse (x:xs) = reverse xs ++ [x]
7.2.5

Propiedades de listas

Una propiedad importante de una lista es su tama


no que se puede calcular con la funcion length
definida en el preludio:
length :: [a] -> Int
length [ ] = 0
length (x:xs) = 1 + length xs
En el preludio esta la funcion elem que comprueba si un elemento dado esta en una lista:
elem :: Eq a => a -> [a] -> Bool
elem e [ ] = False
elem e (x:xs) = if e==x then True else elem e xs
De manera opuesta, la funcion notElem comprueba si un elemento dado no esta en la lista:
notElem e xs = not (elem e xs)

7.3

Funciones de orden superior sobre listas

Las funciones pueden ser mas flexibles si utilizan funciones como parametros. Muchas funciones
sobre listas tienen una funcion como parametro, es decir, son funciones de orden superior.
7.3.1

map

La funcion map aplica su parametro funcion a todos los elementos de una lista. Por ejemplo:
map (\x -> x * x) [1,2,3] ; [1,4,9]
La definicion de map es:

map :: (a -> b) -> [a] -> [b]


map f [ ] = [ ]
map f (x:xs) = (f x) : map f xs

Tambien podemos expresar map en terminos de comprension de listas:


map f xs = [f x | x <- xs]
18

La funcion map se usa para definir otras funciones del preludio tal como elem:
elem e xs = or (map (==e) xs)
Tambien se puede escribir la definicion utilizando el operador de composicion de funciones:
elem e = or . (map (==e))
De igual forma, la funcion notElem:
notElem e = and . (map (/=e))
7.3.2

foldr

La funcion foldr inserta un operador entre todos los elementos de una lista, empezando a la
derecha con un valor dado que es el resultado para la lista vacia:
foldr (+) 0 [1,2,3,4,5] ; (1 + (2 + (3 + (4 + (5 + 0)))))
La definicion de foldr:

7.3.3

foldr :: (a -> b -> b) -> b -> [a] -> b


foldr op e [ ] = e
foldr op e (x:y) = x op foldr op e y

foldl

La funcion foldl inserta un operador entre todos los elementos de una lista empezando a la
izquierda de la lista. Tiene un parametro extra que indica cual es el resultado para la lista vaca:
foldl (+) 0 [1,2,3,4,5] ; (((((0 + 1) + 2) + 3) + 4 + 5)
La definicion de foldl:

foldl :: (a -> b -> a) -> a -> [b] -> a


foldl op e [ ] = e
foldl op e (x:y) = foldl op (e op x) y

Para operadores asociativos como + no importa mucho si se usa foldr o foldl. Para operadores no asociativos como - el resultado con foldr puede ser diferente al obtenido con foldl.
7.3.4

filter

La funcion filter elimina los elementos de una lista que no cumplan con una condicion especificada como una funcion booleana:
filter even [1,2,3,4,5] ; [2,4]
La definicion de filter:

filter :: (a -> Bool) -> [a] -> [a]


filter p [ ] = [ ]
filter p x:y
| p x
= x : filter p y
| otherwise = filter p y

Tambien podemos expresar filter en terminos de comprension de listas:


filter p xs = [x | x <- xs, p x]
19

7.3.5

takeWhile

Una variante de la funcion filter es la funcion takeWhile. Esta funcion tiene como parametros
un predicado (funcion con resultado de tipo Bool) y una lista. La funcion takeWhile empieza
inspeccionando al principio de la lista y termina de buscar cuando encuentra un elemento que
no satisface el predicado:
takeWhile even [2,4,6,7,8,9] ; [2,4,6]
note que el elemento 8 no esta en el resultado, mientras que con la funcion filter s estara.
La definicion de takeWhile:

7.3.6

takeWhile :: (a -> Bool) -> [a] -> [a]


takeWhile p [ ] = [ ]
takeWhile p x:y
| p x
= x : takeWhile p y
| otherwise = [ ]

dropWhile

La funcion dropWhile elimina la parte inicial de una lista que cumple con una condici
on:
dropWhile even [2,4,6,7,8,9] ; [7,8,9]
La definicion de dropWhile:

7.4

dropWhile :: (a -> Bool) -> [a] -> [a]


dropWhile p [ ] = [ ]
dropWhile p x:y
| p x
= dropWhile p y
| otherwise = x:y

Ordenamiento de listas

Todas las funciones sobre listas que hemos visto hasta ahora son O(n) porque cada elemento de
la lista se visita recursivamente cuando mas una vez para determinar el resultado. Una funcion
que no se puede escribir de esta manera es el ordenamiento ascendente de una lista ya que se
tienen que cambiar las posiciones de muchos de los elementos de la lista.
Consideraremos tres algoritmos de ordenamiento de listas. En todos, es necesario que se
puedan ordenar los elementos de las listas. Por tanto, se puede ordenar una lista de enteros, o
una lista de listas de enteros, pero no es posible ordenar una lista de funciones. Expresamos este
requerimiento en el tipo de la funcion sort:
sort :: Ord a => [a] -> [a]
sort opera sobre listas de cualquier tipo a con la condicion que el tipo a este en la clase de los
tipos cuyos elementos se pueden ordenar (Ord).

20

7.4.1

Ordenamiento por inserci


on

Suponiendo que tenemos una lista ya ordenada, podemos insertar un nuevo elemento en el lugar
apropiado con la siguiente funcion:
insert
insert
insert
|
|

:: Ord a => a -> [a] -> [a]


e [ ] = [e]
e (x:xs)
e <= x
= e : x : xs
otherwise = x : insert e xs

Por ejemplo: insert 5 [2,4,6,8,10]

[2,4,5,6,8,10].

La funcion insert se puede usar para ordenar una lista que no este ordenada: se puede
empezar con una lista vaca e insertar primero el u
ltimo elemento de la lista; el resultado es una
lista ordenada en la que se puede insertar el pen
ultimo elemento de la lista, obteniendo una lista
de los elementos ordenada; y as sucesivamente hast insertar el primer elemento de la lista y
obteniendo entonces una lista ordenada con todos los elementos de la lista original:
x1 insert (x2 insert (. . .(xn1 insert (xn insert [ ])). . .))
La estructura de esta expresion es exactamente la de foldr con insert como operador y [ ]
como valor inicial. Por lo tanto, el algoritmo de ordenamiento por insercion lo podemos escribir:
insertsort = foldr insert [ ]
7.4.2

Ordenamiento por fusi


on (mergesort)

La siguiente funcion merge sirve para fusionar dos listas ya ordenadas en una lista ordenada:
merge
merge
merge
merge

:: Ord a => [a] -> [a] -> [a]


[ ] ys = ys
xs [ ] = xs
(x:xs) (y:ys)
| x <= y
= x : merge xs (y:ys)
| otherwise = y : merge (x:xs) ys

Al igual que insert, merge espera que sus parametros esten ordenados.
El mergesort se basa en que la lista vaca y las listas con solo un elemento siempre estan
ordenadas. Ademas, una lista mas grande puede ser dividida en dos partes de (casi) el mismo
tama
no las cuales pueden ser ordenadas por llamadas recursivas a mergesort y finalmente, las
dos sublistas ordenadas son fusionadas utilizando la funcion merge:
mergesort xs
| medio < 1 = xs
| otherwise = merge (mergesort ys) (mergesort zs)
where ys = take medio xs
zs = drop medio xs
medio = (length xs) / 2
21

7.4.3

Quicksort

El siguiente algoritmo de ordenamiento de listas tiene complejidad O(n log2 n):


quicksort [ ] = [ ]
quicksort (x:xs) = yx ++ [x] ++ xy
where yx = quicksort [ y | y <- xs, y < x ]
xy = quicksort [ y | y <- xs, y >= x ]

Tuplas

Cada elemento de una lista debe ser del mismo tipo. Sin embargo, hay situaciones donde es
necesario agrupar elementos de diferentes tipos. Por ejemplo, la informacion del registro de una
persona puede contener nombres (cadenas), sexo (booleano), fecha de nacimiento (enteros), etc.
Estos datos se corresponden, pero no es posible ponerlos en una lista. Para esas situaciones,
existe otra manera de construir tipos compuestos: las tuplas.
Una tupla consiste de un n
umero fijo de valores que pueden ser de diferentes tipos y que estan
agrupados como una entidad.
Las tuplas se escriben entre parentesis, sus elementos separados por comas:
(1, a)

("pepe", False, 45)

([1,2], sqrt)

(1, (2,3))

Para cada combinacion de tipos se crea un nuevo tipo. El orden de los elementos es importante. El tipo de una tupla esta definido por los tipos de sus elementos entre parentesis:
(1, a) :: (Int, Char)
("pepe", False, 45) :: ([Char], Bool, Int)
([1,2], sqrt) :: ([Int], Float -> Float)
(1, (2,3)) :: (Int, (Int,Int))
No existen tuplas de un elemento: (7) es el entero 7.

8.1

Funciones sobre tuplas

Las funciones sobre tuplas se definen mediante analisis por patrones:


fst
fst
snd
snd

:: (a,b) -> a
(x,y) = x
:: (a,b) -> b
(x,y) = y

splitAt
splitAt
splitAt
splitAt

:: Int -> [a] -> ([a],[a])


0 xs = ([ ], xs)
n [ ] = ([ ], [ ])
n (x:xs) = (x:ys, zs) where (ys, zs) = splitAt (n-1) xs

Por ejemplo: splitAt 2 [1,2,3] ; ([1,2], [3])


Tambien podemos definirla por medio de take y drop:
splitAt n xs = (take n xs, drop n xs)
22

Definici
on de Tipos

Cuando se usan mucho las listas y tuplas, las declaraciones de tipo pueden llegar a ser muy
complicadas. En esos casos, una definici
on de tipo puede ser muy u
til ya que es posible dar un
nombre a un tipo, por ejemplo:
type Punto = (Float, Float)
Con esta definicion se pueden escribir las declaraciones de tipo mas claras:
distancia :: Punto -> Float
diferencia :: Punto -> Punto -> Float
sup poligono :: [Punto] -> Float
trans poligono :: (Punto -> Punto) -> [Punto] -> [Punto]
Mejor todava si hacemos una definicion de tipo para polgono:
Type Poligono = [Punto]
sup poligono :: Poligono -> Float
trans poligono :: (Punto -> Punto) -> Poligono -> Poligono
En las definiciones de tipos el nombre de un tipo debe comenzar por una letra may
uscula.
Este nombre es utilizado como una abreviatura. Por ejemplo, si se pide al interpretador el tipo
de una expresion, mostrara (Float, Float) en vez de Punto.
Si se dan dos nombres a un tipo, por ejemplo: type Complejo = (Float, Float), entonces
se pueden usar los dos nombres indistintamente: un Punto es lo mismo que un Complejo y este
es lo mismo que un (Float, Float).
Mas adelante se vera como definir un tipo realmente nuevo.

10

Listas Infinitas

El n
umero de elementos de una lista puede ser infinito. Por ejemplo, la siguiente funcion devuelve
una lista infinita:
desde :: Num a => a -> [a]
desde n = n : desde (n+1)
Una lista infinita se puede usar como resultado intermedio de una computacion aunque el
resultado final sea finito. Por ejemplo, para calcular todas las potencias de 3 menores que 1000:
takeWhile (<1000) (map (3^ ) (desde 1))
[3, 9, 27, 81, 243, 729]
Este metodo puede ser aplicado gracias a que el interpretador es perezoso: siempre trata de
aplazar el trabajo lo mas posible. Por eso, no se calcula el resultado de map (3^ ) (desde 1)
completamente (no podra hacerlo pues tardara un tiempo infinito). Sino que primero calcula el
primer elemento de la lista. Este se pasa a takeWhile. Solamente si se ha utilizado este elemento
y takeWhile pide el siguiente, se calcula el segundo elemento. Y as sucesivamente hasta que en
alg
un momento takeWhile no pedira el siguiente elemento (cuando acabe de procesar el primer
elemento 1000). Por lo tanto, los otros elementos no seran calculados por map.
23

10.1

Evaluaci
on perezosa

La manera como se calculan las expresiones en un lenguaje se llama el metodo de evaluacion del
lenguaje. En los lenguajes funcionales modernos como Haskell, Gofer, Miranda, etc., se utiliza
el metodo conocido como evaluaci
on perezosa donde solo se calcula una expresion si realmente
se necesita su valor.
Lo opuesto a evaluacion perezosa es la evaluaci
on voraz o estricta en la cual se calculan
completamente los parametros actuales para evaluar una funcion.
Las listas infinitas son posibles gracias a la evaluacion perezosa. En los lenguajes que usan
evaluacion voraz como son todos los lenguajes imperativos y algunos funcionales (Ml o Caml), las
listas infinitas no son posibles.
Una ventaja de la evaluacion perezosa se aprecia en el siguiente ejemplo:
divisible :: Int -> Int -> Bool
divisible x y = x rem y == 0
divisores :: Int -> [Int]
divisores x = filter (divisible x) [1..x]
primo :: Int -> Bool
primo x = divisores x == [1,x]
Con la evaluacion perezosa no se calculan todas los divisores de x y se compara el resultado
con [1,x] a menos que esto sea en verdad necesario (cuando x en realidad es primo y en este
caso, solo habran dos divisores).

10.2

Funciones sobre listas infinitas

En el preludio estan definidas algunas funciones que devuelven listan infinitas. La funcion desde
se llama en realidad enumFrom :: Enum a => a -> [a]. Tambien podemos escribir [n..] que
es equivalente a enumFrom n (o desde n).
Una lista infinita en la cual se repite un u
nico elemento puede ser definida con la funcion
repeat :: a -> [a]
repeat x = x : repeat x
Una lista infinita generada por repeat puede ser usada como resultado intermedio por una
funcion que tiene un resultado finito:
copy :: Int -> a -> [a]
copy n x = take n (repeat x)
Gracias a la evaluacion perezosa, copy puede usar el resultado infinito de repeat.
Una funcion de orden superior que devuelve una lista infinita:
iterate :: (a -> a) -> a -> [a]
iterate f x = x : iterate f (f x)
El resultado es una lista infinita en que cada siguiente elemento es el resultado de la aplicacion
de la funcion al elemento anterior:
take 10 (iterate (*2) 1) ; [1,2,4,8,16,32,64,128,256,512]
24

10.3

Lista de todos los primos

La expresion filter primo [2..] genera la lista de todos los n


umeros primos:
take 10 (filter primo [2..]) ; [2,3,5,7,11,13,17,19,23,29]
Podemos utilizar iterate para generar la lista de los primos de manera mas eficiente:
primos :: [Int]
primos = map head (iterate eliminar [2..])
where eliminar (x:xs) = filter (not.multiplo x) xs
multiplo x y = divisible y x
y es m
ultiplo de x
Esta funcion no genera y chequea los divisores de cada n
umero entero en la lista [2..] sino
que comenzando con la lista [2..] toma el primer elemento (2) de esta lista y genera otra lista a
partir de ella pero eliminado todos los m
utiplos de 2. La nueva lista es [3, 5, 7, 9, 11, ...].
De esta lista toma el primer elemento (3) y forma una nueva lista infinita eliminado los m
ultiplos
de 3. La nueva lista es [5, 7, 11, 13, 17, ...]. De esta lista toma el primer elemento (5)
y forma una nueva lista infinita eliminado los m
ultiplos de 5. As sucesivamente, va formando
listas cuyo primer elemento no es divisible por los primos menores que el. Por lo tanto, estas
listas infinitas tienen la propiedad que sus cabezas son siempre n
umeros primos. Por ejemplo:
take 10 primos ; [2,3,5,7,11,13,17,19,23,29]

11

Otros Tipos de Datos

Las listas y las tuplas son dos maneras primitivas de estructurar datos. Si ellas no ofrecen todo
lo que se necesita para representar determinada informacion, se puede definir un tipo de dato.
Un tipo de dato esta caracterizado por la forma en que se pueden construir los elementos del
tipo. Por ejemplo, una lista es una estructura lineal que se puede construir como la lista vaca o
aplicando el operador :. Algunas veces no se requiere una estructura lineal sino un arbol que no
es un tipo primitivo pero que se puede declarar por medio de una definici
on de datos.

11.1

Definiciones de Datos

Las funciones que se usan para construir una estructura de datos se llaman funciones constructoras. En una definicion de datos se especifican las funciones constructoras que se pueden usar
con el nuevo tipo. En la definicion tambien estan los tipos de los parametros de las funciones
constructoras. Por ejemplo, una definicion de datos para arboles binarios:
data Arbol a = Nodo a (Arbol a) (Arbol a)
| Hoja
Esta definicion se lee: un arbol con elementos de tipo a puede ser construido de dos formas:
1. aplicando la funcion Nodo a tres parametros (uno de tipo a y dos de tipo arbol sobre a), o
2. usando la constante Hoja.
25

Los arboles pueden formarse usando las funciones constructoras en una expresion:
Nodo 4 (Nodo 2 (Nodo 1 Hoja Hoja) (Nodo 3 Hoja Hoja))
(Nodo 6 (Nodo 5 Hoja Hoja) (Nodo 7 Hoja Hoja))
Las funciones sobre arboles pueden definirse por patrones con las funciones constructoras.
Por ejemplo, para contar el n
umero de elementos en un arbol:
tama~
no :: Arbol a -> Int
tama~
no Hoja = 0
tama~
no (Nodo x p q) = 1 + tama~
no p + tama~
no q
Pudimos haber definido el tipo Arbol de otras formas. Por ejemplo, los arboles cuyo n
umero
de ramas a partir de una nodo son variables:
data Arbolv a = Nodov a [Arbolv a]

11.2

Tipos finitos

Las funciones constructoras en una definicion de tipo de dato pueden no tener ning
un parametro
como vimos con la funcion constructora Hoja del tipo Arbol. Tambien es posible que ninguna
funcion constructora tenga parametros. En esta situacion, el resultado es un tipo finito.
Un tipo finito es aquel que todas sus funciones constructoras son constantes que indican los
u
nicos elementos del tipo. Por ejemplo:
data Bool = True | False
data Dir = Norte | Sur | Este | Oeste
Se pueden escribir funciones para estos tipos con el uso de patrones:
mover
mover
mover
mover
mover

11.3

:: Dir -> (Int, Int) -> (Int, Int)


Norte (x,y) = (x, y+1)
Sur (x,y) = (x, y-1)
Este (x,y) = (x+1, y)
Oeste (x,y) = (x-1, y)

Uni
on de tipos

Utilizando las definciones de tipos, es posible, por ejemplo, que una lista pueda tener elementos
de tipo Int y de tipo Char:
data IntChar = Ent Int | Car Char
xs :: [IntChar]
xs = [Ent 1, Car a, Ent 2, Car b]
El u
nico precio es que se tiene que marcar cada elemento con una funcion constructora Ent
o Car. Estas funciones pueden interpretarse como funciones de conversion:
Ent :: Int -> IntChar
Car :: Char -> IntChar

26

Bibliografa
- Cousineau, G & Huet, G.
THE CAML PRIMER
http://caml.inria.fr/, 1997
- Hudak, P. & Peterson, J. & Fasel, J.
A GENTLE INTRODUCTION TO HASKELL 98
http://haskell.org/tutorial/, 2000
- Simon Peyton Jones
HASSKELL 98 LANGUAGE AND LIBRARIES - THE REVISED REPORT
http://www.haskell.org/haskellwiki/Definition, 2003
- Jones, M. & Reid A.
HUGS 98 USERS GUIDE
Yale Haskell Group
http://cvs.haskell.org/Hugs/pages/users guide/index.html, 2004
- Jones, M. & Reid A.
THE HUGS 98 USER MANUAL
Yale Haskell Group
http://cvs.haskell.org/Hugs/pages/hugsman/index.html, 2002
- Paulson, L.
ML FOR THE WORKING PROGRAMMER
Cambridge University Press, 2nd. edition, 1998.
Febrero 11, 2006

27

También podría gustarte