Sei sulla pagina 1di 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 matemticas y veremos cmo podemos usar a o esas funciones para construir programas usando la composicin de funciones. o Una funcin es un objeto matemtico que provee una correspondencia entre objetos tomados o a de un conjunto de valores llamado el dominio y objetos en alg n conjunto de llegada llamado u el codominio o rango. Una funcin asocia a un elemento del dominio un unico elemento del rango. Esta propiedad es o importante porque signica que no hay ambigedad sobre cual elemento del rango corresponde u a un elemento dado del dominio. Por esta razn, las funciones se dicen bien denidas o o determin sticas. Un ejemplo simple de funcin es aquella que hace corresponder a un entero dado un eleo mento del conjunto {menos, cero, mas} dependiendo si el entero es negativo, cero o positivo respectivamente. Llamaremos a esta funcin signo para reejar la correspondencia que realiza: o

signo(x) =

menos cero mas

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

El dominio de la funcin signo es el conjunto de los enteros y el rango es el conjunto de o valores {menos, cero, mas}. En la denicin de signo, x se llama el parmetro formal y representa cualquier elemento o a dado del dominio de la funcin. La parte derecha o cuerpo de la regla especica cual elemento del o rango corresponder al parmetro x. En este sentido, la regla para signo representa un n mero a a u innito de ecuaciones individuales, una para cada valor del dominio. Cuando una funcin provee correspondencia para todos los elementos del dominio se le llama o una funcin total. Por lo tanto, signo es una funcin total sobre los enteros. o o Si la regla de la funcin omite la correspondencia para uno o ms posibles elementos del o a dominio entonces es una funcin parcial. Por ejemplo, signo2 es una funcin parcial sobre el o o 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 est indenida cuando x = 0. a Podemos ver una funcin como una caja negra con una entrada representando el parmetro o a de la funcin y una salida representando el resultado computado por la funcin: o o

La escogencia de cul valor del rango se produce como salida de la funcin est determinada a o a por la regla de la funcin as como por el valor particular del dominio sobre el que se aplica la o funcin. Por ejemplo, signo(6) ; mas. El valor 6 que es suplido a la funcin es llamado el o o parmetro actual. Diremos que la expresin signo(6) se evalua ; a mas. a o El proceso de suplir un parmetro actual a la funcin se llama la aplicacin de la funcin y a o o o diremos que la funcin se aplica a ese parmetro actual. Llamaremos argumento de la funcin o a o al parmetro formal o actual dependiendo del contexto en que hablemos. a La visin de una funcin como una regla para transformar entradas a salidas es fundamental o o en la programacin funcional. Las cajas negras constituyen los bloque de construccin para un o o programa funcional y uniendo estos bloque juntos se construyen operaciones ms sosticadas. a Este proceso de acoplar las cajas se llama composicin funcional. o Para ilustrar el proceso de composicin de funciones, consideremos la funcin max que como o puta el mximo de un par de n meros m y n: a u max(m, n) = m n si m > n si m n

El dominio de max es el conjunto de pares de n meros y su rango es el conjunto de n meros. u u Podemos verla como una caja negra y utilizarla para computar el mximo de dos n meros: a u

que podemos escribir max(2, 5) ; 5. Tambin podemos utilizar max como un bloque de construccin para una funcin ms come o o a plicada. Suponga que requerimos una funcin max3 que compute el mximo de tres nmeros. o a u Una manera elegante de desarrollar max3 es utilizar la funcin max ya denida notando que o max3(a, b, c) = max(a, max(b, c))

Observemos el acoplamiento de los bloques de construccin: o

Como max3 provee una asociacin determin o stica de tripletas de n meros a nmeros, podemos u u 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 construccin para otras funciones. Por ejemplo, para o construir una funcin que calcule el signo del mximo de cuatro nmeros a, b, c y d utilizando o a u las funciones signo y max que ya hemos denido: 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 funcin puede ser vista como una caja negra para resolver un problema y o las funciones pueden ser acopladas juntas para denir funciones ms grandes y poderosas que a a su vez pueden ser vistas como cajas negras para construir funciones an ms grandes. u a En programacin funcional, dado un conjunto de cajas negras predenidas llamadas funciones o primitivas que hacen las operaciones bsicas, construimos nuevas funciones cajas negras que a hacen cosas ms sosticadas en trminos de estas primitivas1 . a e Luego podemos usar estas nuevas funciones como bloques de construccin para funciones ms o a sosticadas que a su vez pueden ser utilizadas de la misma manera para denir otras funciones, y as sucesivamente hasta lograr construir la funcin deseada que resuelve el problema planteado. o Podemos denir nuevas funciones tanto para simplicar la denicin de una funcin ms o o a complicada como para describir una operacin utilizada comnmente y as evitar escribir la o u misma expresin una y otra vez. o

Transparencia Referencial

La propiedad fundamental de las funciones matemticas que nos permite acoplar las cajas negras a de la manera antes descrita es la transparencia referencial que signica que cada expresin o denota un unico valor que no puede ser cambiado evaluando la expresin o permitiendo que o diferentes partes del programa compartan la expresin. o La evaluacin de una expresin simplemente cambia la forma de la expresin pero nunca su o o o valor. Todas las referencias a la expresin son por lo tanto equivalentes al valor mismo y el hecho o que la expresin pueda ser referenciada desde otras partes del programa no altera ese valor. o Por su transparencia referencial, una funcin matemtica puede ser vista como una caja negra o a que computa valores de salida solamente en trminos de sus valores de entrada. Esta propiedad es e lo que distingue a las funciones matemticas de las funciones que se pueden escribir en lenguajes a de programacin imperativa. o 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 invocacin o a otra. Tales cambios dinmicos de los valores de los datos globales se llaman efectos laterales a y debido a ellos el valor producido por una funcin puede variar an cuando sus argumentos sean o u los mismos cada vez que es invocada. La presencia de efectos laterales hace que la funcin sea dif de entender pues para detero cil minar cul valor generar la funcin se debe considerar el valor actual de los datos globales. Esto a a o a su vez requiere considerar la historia de la computacin desde el comienzo de la ejecucin del o o 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.
En el lenguaje Haskell estas funciones bsicas predenidas estn contenidas en el archivo prelude que se a a carga al inicializarse el interpretador Hug.
1

programa ejemplo ; var ag: booleana; funcion f ( n : entero) : entero; comenzar si ag ent f := n dlc f := 2 * n; ag := not ag n; comenzar ag := verdad; escribir(f(1) + f(2)); escribir(f(2) + f(1)) n. Al ejecutar este programa obtenemos como salida los n meros 5 y 4. En apariencia no se u estar cumpliendo la ley conmutativa de la suma, pues la expresin f (1) + f (2) estar dando a o a un resultado diferente a f (2) + f (1). La causa de la anoma es la asignacin destructiva a o f lag := not f lag. La asignacin destructiva no est permitida en el razonamiento matemtico el cual est o a a a basado en la nocin de la igualdad y en el reemplazo de una expresin por otra que signique la o o misma cosa, es decir, que denote el mismo valor. Por ejemplo, podemos reemplazar la expresin o 4 + 5 por 9 porque ambas expresiones son denotaciones del mismo valor (el nmero 9). u Una caracter stica de la programacin funcional es que no existen asignaciones destructivas. o En vez de considerar a las variables como receptculos para valores que pueden ser actualizados a periodicamente por medio de la asignacin de diferentes valores, las variables en un programa o funcional son como las variables matemticas: si existen tienen un valor que no puede cambiar. a Por lo tanto, no hay nocin de estado del programa ni de historia del programa. o En la programacin funcional un programa no es un secuencia de imperativas que describen al o computador cmo debe resolver un problema en trminos de cambios de estado (modicaciones o e a valores de variables) sino que un programa funcional describe qu es lo que se va a computar, es e decir, un programa funcional es una expresin denida en trminos de funciones primitivas o e y otras denidas por el usuario. El valor de la expresin constituye el resultado del programa. o

Denicin de Funciones o

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

3.1

Denicin por Combinacin o o

La forma ms fcil y natural de denir funciones es por combinacin utilizando otras funciones: a a o 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 aplicacin funcional asocia por la izquierda. o

3.2

Deniciones Locales

Podemos denir funciones que utilizen nombres locales a la denicin2 : o 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 deniciones locales. Estas deniciones slo o son visibles dentro del cuerpo de la denicin de funcin donde aparece where. o o

3.3

Denicin por Casos o

Algunas veces es necesario distinguir diferentes casos en la denicin de una funcin3 : o o 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 deniciones de los diferentes casos son precedidas por expresiones booleanas llamadas guardas. Si se llama una funcin denida por casos, se tratan las guardas una a una, de arriba hacia o abajo, hasta que se encuentre una guarda con el valor True. Entonces, se eval a la expresin a u o la derecha del s mbolo = y este valor se retorna como resultado de la llamada a la funcin. o En ocasiones, la ultima guarda es True o la constante otherwise. En cada expresin guardada debe existir: el s o mbolo |, una expresin booleana, el s o mbolo = y una expresin resultado. o
2 3

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

3.4

Denicin por Patrones o

Los parmetros formales en una denicin de funcin no estn restringidos a ser slo nombres. a o o a o Ellos pueden ser patrones. Las siguientes construcciones se pueden utilizar como patrones: N meros. Por ejemplo: 3 u Las constantes True y False Nombres. Por ejemplo: x El s mbolo dont care Listas cuyos elementos tambin son patrones. Por ejemplo: [1,x,y] e El operador : con patrones a la izquierda y a la derecha. Por ejemplo: a:b Formalmente, un patrn es un trmino lineal, es decir, en el cual no se repiten las variables. o e Ejemplos de funciones denidas por anlisis de patrones: a 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

Denicin Recursiva o

En la denicin de una funcin se pueden usar las funciones estndares, las funciones denidas o o a previamente por el usuario, las deniciones locales y tambin la propia funcin que se dene en e o la denicin. Cuando ocurre esto ultimo la llamamos una denicin recursiva. o o Las deniciones recursivas requieren las siguientes dos condiciones: 1. Existe al menos una denicin no recursiva para un caso base. o 2. El parmetro de la llamada recursiva es ms simple que el parmetro de la funcin que se a a a o quiere denir. Formalmente, la secuencia de parmetros en las llamadas recursivas debe a ser un conjunto bien fundado, es decir, no deben existir cadenas decrecientes innitas. Ejemplos de deniciones 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 denicin recursiva en la cual se usan patrones para distinguir diferentes casos (en lugar o de expresiones booleanas) se llama tambin denicin inductiva. e o

3.6

Deniciones Annimas o

En vez de utilizar ecuaciones para denir funciones, tambin podemos denirlas annimamente e o v abstracciones lambda: a \x -> x + 1 Tambin podemos denir annimamente funciones de ms de un parmetro: e o a a \x y -> x + y que es equivalente a \x -> \y -> x + y Una denicin annima puede utilizarse por si misma o asociarse al nombre de una funcin: o o o 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

Composicin de Funciones o
f . g = \x -> f (g x)

La composicin de funciones es un operador injo: o (.) :: (b -> c) -> (a -> b) -> (a -> c) Por ejemplo:

sum2 6 where sum2 = inc . inc ; 8.

Curricacin o

Considere la denicin de la funcin mas que suma dos enteros: o o mas :: Int -> Int -> Int mas x y = x + y En una expresin, esta funcin espera recibir dos parmetros, por ejemplo, suma 2 3. Sin o o a embargo, en los lenguajes funcionales, se pueden dar menos parmetros a una funcin que los que a o aparecen en su denicin. En este caso, el resultado de la expresin es una funcin especializada o o o que espera por los parmetros restantes. Por ejemplo, la funcin inc incrementa en uno su a o parmetro: a 9

inc :: Int -> Int inc = mas 1 En otro ejemplo, la funcin max0 retorna el mximo entre cero y su parmetro: o a a max0 :: Int -> Int max0 = max 0 Notemos que la aplicacin funcional asocia por la izquierda y -> asocia por la derecha: o max :: Int -> Int -> Int max :: Int -> (Int -> Int)

Por lo tanto, dado un solo parmetro, max devuelve una funcin con tipo Int -> Int. a o Llamar una funcin con menos parmetros de los que espera se llama instanciar o aplicar o a parcialmente la funcin. o En realidad, en los lenguajes funcionales, existen solamente funciones de un parmetro. Estas a funciones pueden devolver otra funcin que tiene un parmetro. De esta manera, parece que la o a funcin original tiene dos o ms parmetros. o a a Esta estrategia de describir las funciones de ms de un parmetro por funciones de un a a parmetro que devuelven otra funcin, se llama curricacin. La funcin devuelta se llama a o o o funcin curricada. Este mtodo se debe a Moses Schnnkel y Haskell Curry. o e o

4.1

Secciones de Operadores

Como los operadores injos en realidad son funciones, tiene sentido poder aplicarlos parcialmente tambin. En programacin funcional, la aplicacin parcial de un operador injo se llama una e o o seccin. Por ejemplo: o (x+) \y -> x + y (+y) \x -> x + y (+) \x y -> x + y La ultima forma de seccin esencialmente coerciona un operador injo en un equivalente valor o funcional y es conveniente cuando se necesita pasar un operador injo como un argumento a una funcin. Por ejemplo: o foldr (+) 0 [1,2,3] ; (1+(2+(3+0))) As como podemos coercionar a un operador injo a un valor funcional, tambin podemos e hacer lo contrario simplemente encerrando entre comillas izquierdas (backquotes) un identicador 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 parmetros son n meros, valores booleanos o listas. Pero un a u parmetro de una funcin tambin puede ser una funcin. De hecho, en los lenguajes funcionales, a o e o 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 curricacin), o pueden ser parmetros de otras funciones a Con la ultima posibilidad, se pueden escribir funciones globales cuyo resultado depende de una funcin que es uno de los parmetros de la funcin global. o a o A las funciones que tienen funciones como parmetros se las llama funciones de orden superior. a La funcin map es una funcin de orden superior que recorre todos los elementos de una lista o o aplicndole la funcin parmetro a cada elemento de la lista: a o a map :: (a -> b) -> [a] -> [b] map f [ ] = [ ] map f (x:y) = f x : map f y La funcin filter devuelve los elementos de una lista que cumplen una condicin: o o filter :: (a -> Bool) -> [a] -> [a] filter p [ ] = [ ] filter p (x:y) | p x = x : filter p y | otherwise = filter p y La funcin foldr pliega una lista a un valor colocando un operador parmetro entre los o a elementos de la lista empezando por la derecha con un valor inicial dado como parmetro4 : a 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 denir estas funciones de orden superior. Esto hace a los lenguajes funcionales ms exibles: hay pocas a primitivas pero uno puede denir todas las dems que necesite (ortogonalidad ). a
Un operador entre parntesis se comporta como la funcin correspondiente y una funcin entre comillas e o o izquierdas se comporta como el operador correspondiente.
4

11

Expresiones de Tipo y Polimorsmo

Todas las expresiones de un lenguaje funcional tienen tipo. En el caso especial de una funcin, o ste puede ser especicado en su denicin. e o En la programacin funcional se describe una funcin en dos pasos: o o (1) describimos el tipo de la funcin el cual es una especicacin expl o o cita del dominio y rango de la funcin; o (2) describimos qu hace la funcin en trminos de una regla que especica qu debe hacer la e o e e funcin con sus argumentos para generar el resultado requerido. o Por ejemplo, en haskell: cuad :: Int -> Int cuad x = x * x (1) (2)

En la declaracin de tipo indicamos: o (a) el nombre de la funcin (cuad) seguido del s o mbolo :: que se lee es de tipo (b) una expresin de tipo que describe el tipo de la funcin. o o 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 denidos por el programador ...

Existen cuatro tipos estndar: a Int: el tipo de los enteros; Float: el tipo de los n meros de punto otante; u Bool: el tipo de los valores booleanos: True y False; Char: el tipo de los caracteres. Los lenguajes funcionales son fuertemente tipeados. Esto signica que cada funcin est o a denida para operar sobre objetos de un tipo espec co de tal manera que una aplicacin de la o funcin a un objeto de tipo inapropiado genera un error. o Aunque el tipo de cada funcin debe declararse obligatoriamente en algunos lenguajes, por o ejemplo Hope, esto no es un requerimiento general para el tipeo fuerte. En la mayor de los lenguajes funcionales, el tipo de cada funcin puede ser inferido autoa o mticamente sin necesidad de una declaracin expl a o cita por parte del programador. Sin embargo, 12

en la programacin funcional, el tipeo es una parte importante del proceso intelectual de la o programacin y por lo tanto, se recomienda hacerlo. o La principal ventaja del tipeo fuerte es que muchos errores de programacin pueden ser o eliminados antes que el programa sea ejecutado. Aunque la declaracin del tipo es superua, tiene dos ventajas: o se comprueba si la funcin tiene el tipo que est declarado o a la declaracin del tipo ayuda a entender la funcin o o No hace falta escribir la declaracin directamente delante la denicin. Por ejemplo, se o o pueden escribir primero las declaraciones de los tipos de todas las funciones que estn denidas a en el programa. Las declaraciones en este caso funcionan ms o menos como a ndice.

6.1

Polimorsmo

El polimorsmo signica que una funcin puede ser aplicada a una variedad de tipos de arguo mentos. En los lenguajes imperativos tenemos especies limitadas de polimorsmo: Sobrecarga de operadores, tales como los operadores aritmticos, donde una misma opee racin como la suma o la resta puede ser aplicada a tipos Integer, Real, etc. o Polimorsmo Ad hoc, cuando dos operaciones diferentes tienen el mismo nombre, por ejemplo, el + para la suma y para la concatenacin de cadenas. Este polimorsmo es o confuso y puede ser fuente de muchos errores. Polimorsmo por plantillas o templates, que se utiliza en lenguajes como C++ o Java y consiste que una funcin o mtodo pueda denirse varias veces con el mismo nombre y o e diferentes tipos de parmetros. A tiempo de llamada y de acuerdo al tipo de los parmetros a a actuales, se selecciona el cuerpo de la funcin o mtodo a ser ejecutado. o e En los lenguajes funcionales, se utiliza polimorsmo parametrizado donde una operacin o es aplicable a todas las situaciones consistentes con la especicacin de la funcin: o o mas :: Num a => a -> a -> a en este caso, la funcin mas es aplicable a cualquier tipo numrico y el parmetro de tipo a es o e a reemplazado consistentemente con el mismo tipo de acuerdo a la aplicacin de la funcin: o o mas :: Int -> Int -> Int El polimorsmo parametrizado es muy poderoso porque permite expresar una funcin en o trminos generales no dependiendo de las caracter e sticas particulares del parmetro seleccionado. a 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 13 sort :: [a] -> [a]

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 oats, 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] Tambin se pueden agrupar en una lista listas del mismo tipo. El resultado ser, por ejemplo, e a 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

Construccin de listas o

Existen cuatro maneras de construir listas: enumeracin, constructor :, intervalos numricos y o e por comprensin. o 7.1.1 Enumeracin o

Es la manera ms fcil de construir listas. Consiste en la enumeracin de los elementos de la a a o 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 ade n un elemento al principio de una lista y construye de esta manera una lista ms larga: a 1 : [2, 3, 4, 5] ; [1, 2, 3, 4, 5] El constructor : tiene tipo (:) :: a -> [a] -> [a] Usando la lista vac y el operador : se puede construir cualquier lista: a 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 numricos e

Se pueden construir listas con la notracin de intervalos: dos expresiones numricas separadas o e por dos puntos y rodeadas de corchetes: [1 .. 5] es la lista [1, 2, 3, 4, 5] El valor de la expresin [x..y] se calcula por una llamada de la funcin enumFromTo x y o o cuya denicin es: o enumFromTo :: Enum a => a -> a -> [a] enumFromTo x y | y < x = [ ] | otherwise = x : enumFromTo (x+1) y 7.1.4 Por comprensin o

La comprensin de listas es otra manera de denir listas y operaciones sobre ellas. Es una forma o natural de denicin utilizada en teor de conjuntos. Por ejemplo, para denir el conjunto de o a enteros pares entre 0 y 20 escribimos: [x | x <- [0 .. 20], even x] donde <- signica y la coma es la conjuncin lgica. De esta forma se puede escribir casi todo o o lo que se pueda expresar como un conjunto. La frase x <- xs se llama un generador y puede haber ms de uno en una denicin. Por ejemplo, para multiplicar los elementos de una lista a o por los de otra, podemos denir la siguiente funcin: o mul xs ys = [x*y | x <- xs, y <- ys]

7.2

Funciones sobre listas

Las funciones sobre listas ms usuales son recursivas y denidas por medio de patrones, es decir, a son funciones denidas inductivamente. Puesto que cada lista o es vac o tiene un primer a elemento x que est delante de una lista xs (que puede ser vac la funcin se dene para el a a), o caso base de la lista vac [ ] y para una lista que tiene la forma x : xs. En el caso recursivo, a con el patrn x : xs la funcin se llama a si misma con el parmetro xs que es menor que x : xs o o a cumpliendo as el requerimiento del buen fundamento en la recursin. o Estudiemos ahora algunas deniciones de funciones sobre lista. 7.2.1 Comparar y ordenar listas

Se pueden comparar y ordenar listas entre si con la condicin que se puedan comparar y ordenar o sus elementos. Es decir, el tipo [ ] pertenece a los tipos comparables Eq si est en Eq y el tipo a [ ] est en la clase Ord de los tipos ordenables si est en Ord. a a 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 dene 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., tambin se pueden ordenar e entre si las listas seg n el orden lexicogrco. El primer elemento de las listas decide el orden de u a 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 ms corta en la menor. Por tanto, a 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 dene 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 denir otros operadores de comparacin: o xs xs xs xs /= ys >= ys < ys > ys = = = = not (xs == ys) ys <= xs xs <= ys && xs /= ys ys < xs

Ejercicio: Denir inductivamente los operadores de comparacin anteriores. o 7.2.2 Concatenacin de listas o

Se pueden unir dos listas del mismo tipo en una sola lista con el operador ++. Esta operacin se o llama concatenacin: o [1,2,3] ++ [4,5] ; [1,2,3,4,5] El elemento neutro de la concatenacin es la lista vac [ ]. o a El operador ++ es una funcin estndar que se realiza en el preludio y puede denirse as o a : (++) :: [a] -> [a] -> [a] [ ] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) 16

Ejercicio: Denir la funcin concat que se aplica a una lista de listas. Todas las listas en la o 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 Seleccin de partes de una lista o

En el preludio se denen 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 fcil recuperar estos componentes: a head :: [a] -> a head (x:xs) = x tail :: [a] -> [a] tail (x:xs) = xs La funcin recursiva last selecciona el ultimo elemento de una lista: o last :: [a] -> a last [x] = x last (x:xs) = last xs Note que las tres funciones anteriores no estn denidas para una lista vac si se les llama a a, con [ ] como parmetro, el resultado es un mensaje de error. a La funcin init selecciona todo excepto el ultimo elemento de una lista: o init :: [a] -> [a] init [x] = [ ] init (x:xs) = x : init xs La funcin take tiene adems de una lista, un entero como parmetro el cual determina o a a cuntos de los primeros elementos de la lista estarn en el resultado: a a take take take take :: Int -> [a] -> [a] 0 xs = [ ] n [ ] = [ ] n (x:xs) = x : take (n-1) xs

Note que si la lista es ms peque a que el entero especicado, se selecciona la lista completa. a n La funcin drop elimina los primeros n elementos de una lista: o drop drop drop drop :: Int -> [a] -> [a] 0 xs = xs n [ ] = [ ] n (x:xs) = drop (n-1) xs

Note que si la lista es ms peque a que el entero especicado, se elimina la lista completa. a n

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 funcin reverse del preludio invierte los elementos de la lista: o 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 o que se puede calcular con la funcin length n o denida en el preludio: length :: [a] -> Int length [ ] = 0 length (x:xs) = 1 + length xs En el preludio est la funcin elem que comprueba si un elemento dado est en una lista: a o a 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 funcin notElem comprueba si un elemento dado no est en la lista: o a notElem e xs = not (elem e xs)

7.3

Funciones de orden superior sobre listas

Las funciones pueden ser ms exibles si utilizan funciones como parmetros. Muchas funciones a a sobre listas tienen una funcin como parmetro, es decir, son funciones de orden superior. o a 7.3.1 map

La funcin map aplica su parmetro funcin a todos los elementos de una lista. Por ejemplo: o a o map (\x -> x * x) [1,2,3] ; [1,4,9] La denicin de map es: o map :: (a -> b) -> [a] -> [b] map f [ ] = [ ] map f (x:xs) = (f x) : map f xs

Tambin podemos expresar map en trminos de comprensin de listas: e e o map f xs = [f x | x <- xs] 18

La funcin map se usa para denir otras funciones del preludio tal como elem: o elem e xs = or (map (==e) xs) Tambin se puede escribir la denicin utilizando el operador de composicin de funciones: e o o elem e = or . (map (==e)) De igual forma, la funcin notElem: o notElem e = and . (map (/=e)) 7.3.2 foldr

La funcin foldr inserta un operador entre todos los elementos de una lista, empezando a la o 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 denicin de foldr: o foldr :: (a -> b -> b) -> b -> [a] -> b foldr op e [ ] = e foldr op e (x:y) = x op foldr op e y

7.3.3

foldl

La funcin foldl inserta un operador entre todos los elementos de una lista empezando a la o izquierda de la lista. Tiene un parmetro extra que indica cul es el resultado para la lista vac a a a: foldl (+) 0 [1,2,3,4,5] ; (((((0 + 1) + 2) + 3) + 4 + 5) La denicin de foldl: o 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 lter

La funcin filter elimina los elementos de una lista que no cumplan con una condicin especio o cada como una funcin booleana: o filter even [1,2,3,4,5] ; [2,4] La denicin de filter: o filter :: (a -> Bool) -> [a] -> [a] filter p [ ] = [ ] filter p x:y | p x = x : filter p y | otherwise = filter p y filter p xs = [x | x <- xs, p x] 19

Tambin podemos expresar filter en trminos de comprensin de listas: e e o

7.3.5

takeWhile

Una variante de la funcin filter es la funcin takeWhile. Esta funcin tiene como parmetros o o o a un predicado (funcin con resultado de tipo Bool) y una lista. La funcin takeWhile empieza o o 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 est en el resultado, mientras que con la funcin filter s estar a o a. La denicin de takeWhile: o takeWhile :: (a -> Bool) -> [a] -> [a] takeWhile p [ ] = [ ] takeWhile p x:y | p x = x : takeWhile p y | otherwise = [ ]

7.3.6

dropWhile

La funcin dropWhile elimina la parte inicial de una lista que cumple con una condicin: o o dropWhile even [2,4,6,7,8,9] ; [7,8,9] La denicin de dropWhile: o dropWhile :: (a -> Bool) -> [a] -> [a] dropWhile p [ ] = [ ] dropWhile p x:y | p x = dropWhile p y | otherwise = x:y

7.4

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 ms una vez para determinar el resultado. Una funcin a o 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 funcin sort: o sort :: Ord a => [a] -> [a] sort opera sobre listas de cualquier tipo a con la condicin que el tipo a est en la clase de los o e tipos cuyos elementos se pueden ordenar (Ord).

20

7.4.1

Ordenamiento por insercin o

Suponiendo que tenemos una lista ya ordenada, podemos insertar un nuevo elemento en el lugar apropiado con la siguiente funcin: o insert insert insert | | :: Ord a => a -> [a] -> [a] e [ ] = [e] e (x:xs) e <= x = e : x : xs otherwise = x : insert e xs ; [2,4,5,6,8,10].

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

La funcin insert se puede usar para ordenar una lista que no est ordenada: se puede o e empezar con una lista vac e insertar primero el ultimo elemento de la lista; el resultado es una a lista ordenada en la que se puede insertar el pen ltimo elemento de la lista, obteniendo una lista u 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 expresin es exactamente la de foldr con insert como operador y [ ] o como valor inicial. Por lo tanto, el algoritmo de ordenamiento por insercin lo podemos escribir: o insertsort = foldr insert [ ] 7.4.2 Ordenamiento por fusin (mergesort) o

La siguiente funcin merge sirve para fusionar dos listas ya ordenadas en una lista ordenada: o 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 parmetros estn ordenados. a e El mergesort se basa en que la lista vac y las listas con slo un elemento siempre estn a o a ordenadas. Adems, una lista ms grande puede ser dividida en dos partes de (casi) el mismo a a tama o las cuales pueden ser ordenadas por llamadas recursivas a mergesort y nalmente, las n dos sublistas ordenadas son fusionadas utilizando la funcin merge: o 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 informacin del registro de una o 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 mero jo de valores que pueden ser de diferentes tipos y que estn u a agrupados como una entidad. Las tuplas se escriben entre parntesis, sus elementos separados por comas: e (1, a) ("pepe", False, 45) ([1,2], sqrt) (1, (2,3))

Para cada combinacin de tipos se crea un nuevo tipo. El orden de los elementos es imporo tante. El tipo de una tupla est denido por los tipos de sus elementos entre parntesis: a e (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
fst fst snd snd

Funciones sobre tuplas


:: (a,b) -> a (x,y) = x :: (a,b) -> b (x,y) = y :: Int -> [a] -> ([a],[a]) 0 xs = ([ ], xs) n [ ] = ([ ], [ ]) n (x:xs) = (x:ys, zs) where (ys, zs) = splitAt (n-1) xs

Las funciones sobre tuplas se denen mediante anlisis por patrones: a

splitAt splitAt splitAt splitAt

Por ejemplo: splitAt 2 [1,2,3] ; ([1,2], [3]) Tambin podemos denirla por medio de take y drop: e splitAt n xs = (take n xs, drop n xs) 22

Denicin de Tipos o

Cuando se usan mucho las listas y tuplas, las declaraciones de tipo pueden llegar a ser muy complicadas. En esos casos, una denicin de tipo puede ser muy util ya que es posible dar un o nombre a un tipo, por ejemplo: type Punto = (Float, Float) Con esta denicin se pueden escribir las declaraciones de tipo ms claras: o a distancia :: Punto -> Float diferencia :: Punto -> Punto -> Float sup poligono :: [Punto] -> Float trans poligono :: (Punto -> Punto) -> [Punto] -> [Punto] Mejor todav si hacemos una denicin de tipo para pol a o gono: Type Poligono = [Punto] sup poligono :: Poligono -> Float trans poligono :: (Punto -> Punto) -> Poligono -> Poligono En las deniciones de tipos el nombre de un tipo debe comenzar por una letra may scula. u Este nombre es utilizado como una abreviatura. Por ejemplo, si se pide al interpretador el tipo de una expresin, mostrar (Float, Float) en vez de Punto. o a 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 ste e es lo mismo que un (Float, Float). Ms adelante se ver cmo denir un tipo realmente nuevo. a a o

10

Listas Innitas

El n mero de elementos de una lista puede ser innito. Por ejemplo, la siguiente funcin devuelve u o una lista innita: desde :: Num a => a -> [a] desde n = n : desde (n+1) Una lista innita se puede usar como resultado intermedio de una computacin aunque el o resultado nal sea nito. 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 mtodo puede ser aplicado gracias a que el interpretador es perezoso: siempre trata de e aplazar el trabajo lo ms posible. Por eso, no se calcula el resultado de map (3^ ) (desde 1) a completamente (no podr hacerlo pues tardar un tiempo innito). Sino que primero calcula el a a 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 n momento takeWhile no pedir el siguiente elemento (cuando acabe de procesar el primer u a elemento 1000). Por lo tanto, los otros elementos no sern calculados por map. a 23

10.1

Evaluacin perezosa o

La manera cmo se calculan las expresiones en un lenguaje se llama el mtodo de evaluacin del o e o lenguaje. En los lenguajes funcionales modernos como Haskell, Gofer, Miranda, etc., se utiliza el mtodo conocido como evaluacin perezosa donde slo se calcula una expresin si realmente e o o o se necesita su valor. Lo opuesto a evaluacin perezosa es la evaluacin voraz o estricta en la cual se calculan o o completamente los parmetros actuales para evaluar una funcin. a o Las listas innitas son posibles gracias a la evaluacin perezosa. En los lenguajes que usan o evaluacin voraz como son todos los lenguajes imperativos y algunos funcionales (Ml o Caml), las o listas innitas no son posibles. Una ventaja de la evaluacin perezosa se aprecia en el siguiente ejemplo: o 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 evaluacin perezosa no se calculan todas los divisores de x y se compara el resultado o con [1,x] a menos que esto sea en verdad necesario (cuando x en realidad es primo y en este caso, slo habrn dos divisores). o a

10.2

Funciones sobre listas innitas

En el preludio estn denidas algunas funciones que devuelven listan innitas. La funcin desde a o se llama en realidad enumFrom :: Enum a => a -> [a]. Tambin podemos escribir [n..] que e es equivalente a enumFrom n (o desde n). Una lista innita en la cual se repite un unico elemento puede ser denida con la funcin o repeat :: a -> [a] repeat x = x : repeat x Una lista innita generada por repeat puede ser usada como resultado intermedio por una funcin que tiene un resultado nito: o copy :: Int -> a -> [a] copy n x = take n (repeat x) Gracias a la evaluacin perezosa, copy puede usar el resultado innito de repeat. o Una funcin de orden superior que devuelve una lista innita: o iterate :: (a -> a) -> a -> [a] iterate f x = x : iterate f (f x) El resultado es una lista innita en que cada siguiente elemento es el resultado de la aplicacin o de la funcin al elemento anterior: o take 10 (iterate (*2) 1) ; [1,2,4,8,16,32,64,128,256,512] 24

10.3

Lista de todos los primos


take 10 (filter primo [2..]) ; [2,3,5,7,11,13,17,19,23,29]

La expresin filter primo [2..] genera la lista de todos los nmeros primos: o u

Podemos utilizar iterate para generar la lista de los primos de manera ms eciente: a 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 ltiplo de x u Esta funcin no genera y chequea los divisores de cada nmero entero en la lista [2..] sino o u 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 mtiplos de 2. La nueva lista es [3, 5, 7, 9, 11, ...]. u De esta lista toma el primer elemento (3) y forma una nueva lista innita eliminado los mltiplos u de 3. La nueva lista es [5, 7, 11, 13, 17, ...]. De esta lista toma el primer elemento (5) y forma una nueva lista innita eliminado los mltiplos de 5. As sucesivamente, va formando u listas cuyo primer elemento no es divisible por los primos menores que l. Por lo tanto, estas e listas innitas tienen la propiedad que sus cabezas son siempre nmeros primos. Por ejemplo: u 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 informacin, se puede denir un tipo de dato. o Un tipo de dato est caracterizado por la forma en que se pueden construir los elementos del a tipo. Por ejemplo, una lista es una estructura lineal que se puede construir como la lista vac o a aplicando el operador :. Algunas veces no se requiere una estructura lineal sino un rbol que no a es un tipo primitivo pero que se puede declarar por medio de una denicin de datos. o

11.1

Deniciones de Datos

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

Los arboles pueden formarse usando las funciones constructoras en una expresin: o 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 denirse por patrones con las funciones constructoras. Por ejemplo, para contar el nmero de elementos en un rbol: u a tama~o :: Arbol a -> Int n tama~o Hoja = 0 n tama~o (Nodo x p q) = 1 + tama~o p + tama~o q n n n Pudimos haber denido el tipo Arbol de otras formas. Por ejemplo, los arboles cuyo n mero u de ramas a partir de una nodo son variables: data Arbolv a = Nodov a [Arbolv a]

11.2

Tipos nitos

Las funciones constructoras en una denicin de tipo de dato pueden no tener ningn parmetro o u a como vimos con la funcin constructora Hoja del tipo Arbol. Tambin es posible que ninguna o e funcin constructora tenga parmetros. En esta situacin, el resultado es un tipo nito. o a o Un tipo nito es aquel que todas sus funciones constructoras son constantes que indican los unicos 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 :: 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)

11.3

Unin de tipos o

Utilizando las denciones 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 unico precio es que se tiene que marcar cada elemento con una funcin constructora Ent o o Car. Estas funciones pueden interpretarse como funciones de conversin: o Ent :: Int -> IntChar Car :: Char -> IntChar

26

Bibliograf a
- 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/Denition, 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

Potrebbero piacerti anche