Introducción Haskell 1
Valores, Tipos, Expresiones Los valores son entidades abstractas que podemos considerar como la respuesta a un cálculo 5-1 8 Cada valor tiene asociado un tipo (<valor> :: <tipo>) 2 :: Int Int {...-3,-2,-1,0,1,2,3...} Las expresiones son términos construidos a partir de valores (2*3)+(4-5) La reducción o evaluación de una expresión consiste en aplicar una serie de reglas que transforman la expresión en un valor (2*3)+(4-5) 6+(4-5) 6+(-1) 6-1 5 2
Valores, Tipos, Expresiones (II) Un valor es una expresión irreducible Toda expresión tiene un tipo: el tipo del valor que resulta de reducir la expresión (Sistema Fuertemente Tipado). (2*3)+(4-5) :: Int El tipo de una expresión debe establecerse en tiempo de compilación sin evaluar la expresión (Tipado Estático) El compilador calcula el tipo de una expresión, normalmente, sin ninguna anotación del programador (Inferencia de Tipos) Las funciones también son valores (y por lo tanto tendrán tipo) 3
Tipos Básicos Int, subconjunto de los enteros (normalmente, los enteros representables por una palabra del procesador): 2, -1... Integer, enteros (precisión absoluta) Char, caracteres: a, 5, \n, \xf4... Float/Double, números en punto flotante de simple/doble precisión: 3.14159, 2, 2.5e+2... String, cadenas de caracteres: "hola",... Bool, booleanos: True, False 4
Definiciones Asociamos identificador y expresión (<id> = <expr>) x = (2*3)+(4-5) y = x^2 Opcionalmente, podemos incluir una anotación de tipo: x :: Int x = (2*3)+(4-5) A pesar de que son inferidas automáticamente, las anotaciones de tipo se suelen añadir para ciertas definiciones por: Documentación Localización de errores Facilitar optimización (sobrecarga) 5
Definición local let...in Definición local: let <id> = <e 1 > in <e 2 > let x = 2*3 in x+1 La definición local es una expresión, cuyo tipo es el de e 2 (let x = 2*3 in x+1) + 5 Semántica: Sustituimos id por su definición e 1 en e 2 let x = 2*3 in x+1 x+1[2*3/x] (2*3)+1 7 Ámbito de la definición let x = 1 in let y = 2 in x+y y x 6
Definición local where Definición local: <e 2 > where <id> = <e 1 > x+1 where x = 2*3 Ambigüedad entre let...in y where x where let x = 5 in x*x x let...in where x = 6 Se permite definir múltiples identificadores simultáneamente: let x = 5 x+y where x = 5 y = 6 y = 6 in x+y 7
Regla del Contexto Las declaraciones de una definición local, se delimitan con llaves ({}), y punto y coma (;) a+b where { a=5; b=4 } Opcionalmente, podemos usar el contexto como delimitador a+b where a=5 b=4 El contexto se puede usar en cualquier lista de declaraciones de let..in, where, y case let y = 7 x = y where y = 8 z = 2 in y+x+z z = 4 8
Expresión Condicional Expresión Condicional: if <e 1 > then <e 2 > else <e 3 > Semántica: if 2+3==5 then "ho. el se "la" Si e 1 True entonces if... e 2 Si e 1 False entonces if... e 3 if 2+3==5 then ho else la ho La guardia de la expresión condicional debe ser de tipo Bool Las ramas then y else deben tener igual tipo El tipo de la expresión condicional es el tipo de la rama then 9
Funciones Función matemática: Transparencia Referencial (f x = f x) Definición: <función> <argumento> = <expresión> sucesor x = x+1 cuadrado y = y*y Una función que toma como argumento algo de tipo T 1 y lo transforma en algo de tipo T 2 tiene por tipo T 1 T 2 sucesor :: Int ->Int La aplicación de una función f a e (f e) se define como la sustitución del argumento de f por e en el cuerpo de f sucesor 5 x+1[5/x] 5+1 6 10
Funciones (II) Todas las definiciones dentro del mismo contexto son mutuamente recursivas: f x = g (x-1) g x = if x == 0 then 0 else f (x-1) f 3 g 2 f 1 g 0 0 f 4 g 3 f 2 g 1 f 0 g (-1)... Si f no está definida en x, decimos que f x = Función anónima: \<arg> -> <expr> (\x ->x+2) 4 x+2[4/x] 4+2 6 La definición de funciones es una ayuda sintáctica: sucesor x = x+1 sucesor = \x ->x+1 11
Guardias Ayuda sintáctica para la definición de funciones: <función> <argumento> <guardia 1 > = <expr 1 > <guardia 2 > = <expr 2 >.. <guardia n > = <expr n > Las guardias deben ser de tipo Bool; las expr i tienen que ser del mismo tipo (imagen de la función) Las guardias se evalúan de arriba a abajo, hasta encontrar una guardia i que sea True la función devuelve expr i otherwise (o True) puede usarse como caso por defecto fact n n == 0 = 1 otherwise = n * fact (n-1) 12
Funciones de Orden Superior Funciones que reciben como argumento una función o que devuelven una función como resultado: Función como argumento: f1 :: (Int -> Int) -> Int f1 g = 2 + g 3 Función como resultado: f2 :: Int -> (Int -> Int) f2 x = \y -> x+y Función como argumento y como resultado: f3 :: (Int -> Int) -> (Int -> Int) f3 g = \y -> 2 * g (y+1) 13
Funciones de Orden Superior (II) Diferentes formas de escribir lo mismo f = \x -> (\y -> x+y) f = \x -> \y -> x+y f x = \y -> x+y f x y = x+y f = \x y -> x+y (f 3) es una función Int ->Int que dado y, devuelve 3+y. (f 3) 4 ((\x ->x+y) 3) 4 (\y ->3+y) 4 3+4 7 La aplicación asocia por la izquierda: (f 3) 4 f 3 4 El constructor de tipo -> asocia por la derecha: Int ->(Int ->Int) Int ->Int ->Int (Int ->Int) ->Int Int ->Int ->Int 14
Funciones con Varios Argumentos Todas las funciones tienen un único argumento (Dominio) y devuelven un único resultado (Imagen) Una función que devuelve una función como resultado, puede considerarse como una función de dos argumentos: f :: Int ->Int ->Int f 3 4 7 f x y = x+y f 3 \y ->3+y (Aplicación parcial) Si es un operador infijo, ( ) es una función prefija equivalente a+b (+) a b Si f es una función de 2 argumentos, f es un operador infijo equivalente mod a b a mod b 15
Pattern-Matching Definir ecuaciones basadas en la forma del argumento Forma más simple: constantes e identificadores fact 0 = 1 fact n = n * fact (n-1) Comprueba de arriba a abajo hasta encontrar el patrón que encaje Una constante sólo encaja con un argumento igual Un identificador encaja con cualquier argumento El comodín _ hace las veces de identificador Se puede combinar con las guardias fib 0 = 1 fib 1 = 1 fib n n > 1 = fib (n-2) + fib (n-1) otherwise = error "fib de un número negativo" 16
Producto cartesiano Tipo Producto (Tuplas) A = {1, 2, 3} B = {a, b} A B = {(1, a), (1, b), (2, a), (2, b), (3, a), (3, b)} Si a :: T 1 y b :: T 2 entonces (a,b) :: (T 1,T 2 ) Generalizable para tuplas de aridad n > 2 (True, "hola", \x -> x ++ ".") :: (Bool, String, String -> String) Funciones sobre tuplas: Uso de pattern-matching mayor :: (Int,Int,Int) -> Int mayor (x,y,z) x > y && x > z = x y > x && y > z = y otherwise = z Los componentes de una tupla pueden ser otras tuplas ((1,2),3) :: ((Int,Int),Int) (1,(2,3)) :: (Int,(Int,Int)) (1,2,3) :: (Int,Int,Int) 17
Tipo Producto (Tuplas) (II) Un uso: implementar funciones de varios argumentos suma :: (Int,Int) -> Int suma (x,y) = x+y Otro uso común: Funciones que devuelven varios resultados sumayproducto :: Int -> Int -> (Int,Int) sumayproducto m n m > n = (0,1) otherwise = let (s,p) = sumayproducto (m+1) n in (m+s, m*p) El uso de sinónimos de tipo simplifica las anotaciones de tipo type Jugador = (String, String, Int) roberto :: Jugador roberto = ("Roberto Carlos", "Brasil", 3) nacionalidad :: Jugador -> String nacionalidad (_,pais,_) = pais 18
Polimorfismo El mismo código puede ser utilizado con distintos tipos Distinguimos tipos (con mayúsculas) de variables de tipo (con minúsculas) pueden ser reemplazadas por cualquier tipo El tipo de id dada su definición id x = x es a ->a. La variable de tipo a puede reemplazarse por cualquier tipo válido, dando lugar a todos las posibles usos de id: Sustitución de a Int (Int,Int) Int ->Int. Tipo resultante para id Int ->Int (Int,Int) ->(Int,Int) (Int ->Int) ->(Int ->Int). Un tipo e que contiene una variable de tipo a (esquema de tipo) es realmente a tipos. e 19
Polimorfismo (II) Polimorfismo + Funciones de Orden Superior = Abstracción prod :: (a -> b) -> (c -> d) -> (a,c) -> (b,d) prod (f,g) = par (f.fst, g. snd) sumayproducto :: Int -> Int -> (Int,Int) sumayproducto m n m > n = (0,1) otherwise = prod (+m) (*m) (sumayproducto (m+1) n) Si es un operador binario, ( a) es (\x ->x a) y (a ) es (\x ->a x) (secciones) Composición de funciones: operador. (predefinido) (.) :: (a -> b) -> (c -> a) -> (c -> b) f. g = \x -> f (g x) f = (+1). (^2) 20
Currificación Dos formas de representar funciones multiargumento: Currificada: Utilizando funciones de orden superior suma :: Int -> Int -> Int suma x y = x+y Decurrificada: Utilizando funciones sobre tuplas suma :: (Int,Int) -> Int suma (x,y) = x+y Preferencia: currificadas (por el uso de aplicación parcial) Es posible transformar un formato en el otro (predefinidas) curry :: ((a,b) -> c) -> (a -> b -> c) curry f = \x -> \y -> f (x,y) uncurry :: (a -> b -> c) -> ((a,b) -> c) uncurry f = \(x,y) -> f x y El término currificación se establece en honor a Haskell B. Curry 21
Listas Colección ordenada de valores homogéneos Si a 1 ::T, a 2 ::T,..., a n ::T entonces [a 1,a 2,...,a n ] :: [T ] Algunos ejemplos: [1,2,3] :: [Int] [ (True,1), (False,2) ] :: [(Bool,Int)] [ (+1), (^2), (\x -> x) ] :: [Int -> Int] [ [1,2,3], [4], [5,6] ] :: [[Int]] Las listas se crean a partir de dos constructores: [] :: [a], la lista vacía (:) :: a ->[a] ->[a], constructor infijo que a partir de un elemento y una lista construye una lista nueva, resultado de pegar el elemento en la cabeza de la lista 1:(2:(3:[])) 1:2:3:[] [1,2,3] a:(b:c) (a:b):c 22
Listas (II) Funciones sobre listas: pattern-matching null :: [a] -> Bool null [] = True null _ = False -- predefinida head :: [a] -> a head (x:_) = x tail :: [a] -> [a] tail (_:xs) = xs -- predefinida -- predefinida last :: [a] -> a last (x::xs) = if null xs then x else last xs init :: [a] -> [a] init (x:xs) = in null xs then [] else init xs concat :: [[a]] -> [a] concat [] = [] concat (xs:xss) = xs ++ concat xss Herramienta fundamental: inducción 23
sum :: [Int] -> Int sum [] = 0 sum (x:xs) = x + sum xs -- predefinida sum :: [Int] -> Int sum xs = if xs == [] then 0 else head xs + sum (tail xs) 24
Funciones sobre listas (II) reverse :: [a] -> [a] reverse [] = [] reverse (x:xs) = reverse xs ++ [x] length :: [a] -> Int length [] = 0 length (_:xs) = 1 + length xs -- predefinida (++) :: [a] -> [a] -> [a] -- predefinida [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) take :: Int -> [a] -> [a] take 0 xs = [] take _ [] = [] take n (x:xs) = x: take (n - 1) xs drop :: Int -> [a] -> [a] drop 0 xs = xs drop _ [] = [] drop n (x:xs) = drop (n - 1) xs 25
Funciones sobre listas (III) (!!) :: [a] -> Int -> a (x:xs)!! 0 = x (x:xs)!! n = xs!! (n - 1) map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = fx : map f xs filter :: (a -> Bool) -> [a] -> [a] filter p [] = [] filter p (x:xs) = if p x then x: filter p xs else filter p xs zip :: [a] -> [b] -> [(a,b)] zip [] _ = [] zip _ [] = [] zip (x:xs) (y:ys) = (x,y) : zip xs ys zipwith :: (a -> b -> c) -> [a] -> [b] -> [c] zipwith f xs ys = map (uncurry f) (zip xs ys) 26
Funciones sobre listas (IV) zipp :: ([a], [b]) -> [(a,b)] zipp = uncurry zip zippwith :: (a -> b -> c) -> ([a], [b]) -> [c] zippwith f = map (uncurry f). zipp par (f,g) x = (f x, g x) unzip :: [(a,b)] -> ([a],[b]) unzip = par (map fst, map snd) 27
La familia de funciones fold foldr :: (a -> b -> b) -> b -> [a] -> b foldr f e [] = e foldr f e (x:xs) = f x (foldr f e xs) foldl :: (b -> a -> b) -> b -> [a] -> b foldl f e [] = e foldl f e (x:xs) = foldl f (f e x) xs foldr1 :: (a -> a -> a) -> [a] -> a foldr1 f (x:xs) = if null xs then x else f x (foldr1 f xs) foldl1 :: (a -> a -> a) -> [a] -> a foldl1 f (x:xs) = foldl f x xs 28
concat :: [[a]] -> [a] concat = foldr (++) [] Definiciones con foldr reverse :: [a] -> [a] reverse = foldr snoc [] where snoc x xs = xs ++ [x] length :: [a] -> Int lenght = foldr increm 0 where increm x n = 1 + n sum :: Num a => [a] -> a sum = foldr (+) 0 and :: [Bool] -> Bool and = foldr (&&) True map :: (a -> b) -> [a] -> [b] map f = foldr (cons. f) [] where cons x xs = x:xs 29
Definiciones con foldr (II) maxlist :: (Ord a, Bounded a) => [a] -> a maxlist = foldr ( max ) minbound unzip :: [(a,b)] -> ([a],[b]) unzip = foldr conss ([],[]) where conss (x,y) (xs, ys) = (x:xs, y:ys) 30
inits & tails inits :: [a] -> [[a]] inits [] = [[]] inits (x:xs) = [] : map (x:) (inits xs) tails :: [a] -> [[a]] tails [] = [] tails (x:xs) = (x:xs) : tails xs 31
La familia de funciones scan scanl :: (a -> b -> a) -> a -> [b] -> [a] scanl f e = map (foldl f e). inits scanl :: (a -> b -> a) -> a -> [b] -> [a] -- eficiente scanl f e [] = [e] scanl f e (x:xs) = e : scanl f (f e x) xs scanr :: (a -> b -> b) -> b -> [a] -> [b] scanr f e = map (foldr f e). tails scanr :: (a -> b -> b) -> b -> [a] -> [b] -- eficiente scanr f e [] = [e] scanr f e (x:xs) = f x (heay ys) : ys where ys = scanr f a xs 32
Leyes de take & drop Leyes take m. take n = take (m min n) drop m. drop n = drop (n + m) take m. drop m = drop n. take (m + n) Leyes del map map id = id map (f. g) = map f. map g f. head = head. map f map f. tail = tail. map f map f. reverse = reverse. map f map f. concat = concat. map (map f) Leyes del filter filter p. filter q = filter (p and q) x && q x filter p. concat = concat. map (filter p) donde (p and q) x = p 33
Leyes del prod y par Leyes (II) prod(f,g) = par(f.fst, g.snd) prod(f,g).par(h,k) = par(f.h,g.k) par (f,g).h = par(f.h,g.h) Leyes del zip zipp(unzip xys) = xys prod(map f, map g).unzip = unzip.map(prod(f,g)) zipp.prod(map f, map g) = map(prod(f,g)).zipp 34
Teoremas de la dualidad Primer teorema de la dualidad foldr ( ) e xs = foldl ( ) e xs cuando : es asociativo e es su elemento neutro Segundo teorema de la dualidad foldr ( ) e xs = foldl ( ) e xs cuando para todo x, y, y z: x (y z) = (x y) z z e = e x Tercer teorema de la dualidad foldr f e xs = foldl (flip f) e (reverse xs) donde flip f x y = f y x 35
Teorema de fusión para foldr f. foldr g a = foldr h b cuando : f es estricta f a = b Teoremas de fusión f (g x y) = h x (f y) para todo x e y Teorema de fusión para foldl f. foldl g a = foldl h b cuando : f es estricta f a = b f (g x y) = h (f x) y para todo x e y 36
Teorema de fusión fold-map Teoremas de fusión(ii) foldr f a. map g = foldr (f. g) a cuando : foldr f a es estricta Teorema de fusión fold-concat foldr f a. concat = foldr (flip foldr f)) a cuando : foldr f a es estricta Ley del Contable foldr f e.concat=foldr f e.map(foldr f e) cuando : f es asociativa e es su elemento neutro 37
Ley de fusión fold-scan Teoremas de fusión(ii) foldr1 ( ).scanl ( ) e = foldr ( ) e cuando: x y = e ( x y) otimes es asociativo e es su elemento neutro es distributivo con respecto a : x (y z = (x y) (x z) 38
Mayor suma de segmentos Dada una secuencia de números, se pide calcular la mayor de las sumas de todos los segmentos de la secuencia. Por ejemplo, la secuencia: [ -1, 2, -3, 5, -2, 1, 3, -2, -2, -3, 6] tiene como mayor suma 7, que es la suma del segmento [5, -2, 1, 3]. 39
Mayor suma de segmentos(ii) mss :: [Int] -> Int mss = maxlist.map sum.segs segs :: [a] -> [[a]] segs = concat.map inits. tails La evaluación directa de mss necesita un número de pasos proporcional a n 3 para una lista de longitud n. Hay aproximadamente n 2 segmentos y sumar todos ellos necesita aproximadamente n 3 pasos. 40
Mayor suma de segmentos(iii) Sustituimos la definición de segs en mss: maxlist.map sum.concat.map inits.tails usamos la ley del map map f.concat = concat. map (map f) maxlist.concat.map (map sum).map inits.tails usamos la ley del contable foldr f e.concat=foldr f e.map(foldr f e) maxlist.map maxlist..map (map sum).map inits.tails usamos la ley map (f.g) = map f. map g) maxlist.map maxlist.map (map sum.inits).tails usamos la ley map (f.g) = map f. map g) maxlist.map (maxlist.map sum.inits).tails 41
Mayor suma de segmentos(iii) sustituimos map sum.inits = scanl (+) 0 maxlist.map (maxlist.scanl (+) 0).tails dado que maxlist = foldr1 ( max ) y (+) es distributivo con respecto a ( max ) aplicamos la lef de fusión fold-scan maxlist.map (foldr ( ) 0).tails donde x y = 0 max (x + y) sustituimos map (foldr f e). tails = scanr f e maxlist.scanr ( ) 0 El resultado es un programa que calcula la mayor suma de segmentos en tiempo lineal 42
Strings El tipo String es un sinónimo de tipo: type String = [Char] Sintaxis especial para las cadenas de caracteres: [ h, o, l, a ] "hola" "hola "++ [ m, u, n, d, o ] == "hola mundo" amayusculas :: String -> String amayusculas [] = [] amayusculas (x:xs) = toupper x : amayusculas xs 43
Enumeraciones Lista de elementos entre m y n: enumfromto :: Int -> Int -> [Int] -- predefinida enumfromto m n m > n = [] otherwise = m : enumfromto (m+1) n Forma alternativa: enumfromto m n == [m..n] [3..7] == enumfromto 3 7 == [3,4,5,6,7] Otra forma de definir una lista: [m,m + k..n] [m,m + k..n] [m,m + k,m + 2k,...,m + ck] m + ck n < m + (c + 1)k [3,5..10] == enumfromthento 3 5 7 == [3,5,7,9] También vale con Char [ a.. z ] == [ a, b,..., z ] == "abcdefghijklmnopqrstuvwxyz" 44
Comprensión de listas (List Comprehension) Construcción de listas basándonos en otras listas Forma más simple: [expr pat <- lista] pat <- lista es un generador: se recorre lista secuencialmente y se hace pattern-matching con pat. La lista resultado tiene un elemento por cada elemento generado, que se calcula según expr [ 2*x x <- [1..10]] == [2,4,6,8,10,12,14,16,18,20] Es posible más de 1 generador: [expr pat 1 <- l 1,...,pat n <- l n ] [(x,y) x <- [1..3], y <- [True,False]] == [(1,True), (1,False), (2,True), (2,False), (3,True), (3,False)] [(x,y) x <- [1..3], y <- [1..x]] == [(1,1), (2,1), (2,2), (3,1), (3,2), (3,3)] 45
Comprensión de listas (List Comprehension) (II) Guardias para limitar generación: [expr pat <- lista, guardia] [2*x x <- [1..10], x > 5] == [12,14,16,18,20] [(x,y) x <- [1..3], y <- [1..3], x /= y] == [ (1,2), (1,3), (2,1), (2,3), (3,1), (3,2) ] Simplificación de definiciones: menores_que k xs = [ x x <- xs, x < k ] filter p xs = [ x x <- xs, p x ] map f xs = [f x x <- xs] qs [] = [] qs (x:xs) = qs [y y <- xs, y < x] ++ [x] ++ qs [y y <- xs, y >= x] 46
Mónadas El grupo de las operaciones de entrada salida en Haskell se denota como IO (). Una exprosion de tipo IO () denota una acción; cuando se evalúa, la acción se realiza. Escribir un carácter: putchar :: Char -> IO () No hacer nada done :: IO () Combinar dos acciones de E/S (>>) :: IO () -> IO () -> IO () Escribir un String write :: String -> IO () write [] = done write (x:xs) = putchar x >> write xs 47
Mónadas(II) Escribir un String terminado en retorno de carro writeln :: String -> IO () writeln xs = write xs >> putchar \n Leer un carácter getchar :: IO Char Generalización de done return :: a -> IO a Implementación de done done :: IO () done = return () Generalización del tipo de (>>) (>>) :: IO a -> IO b -> IO b si p y q son acciones, entonces p >> q es una acción que, cuando se realice, primero efectúa p, ignora el valor devuelto y despus realiza q. 48
Generalización de (>>) Mónadas(II) (>>=) :: = IO a -> (a -> IO b) -> IO b Leer n caracteres readn :: Iont -> IO String readn 0 = return [] readn n = getchar >>= q where q c = read (n-1) >>= r where r cs = return (c:cs) Clase Monad class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b Notación do readn :: Iont -> IO String readn 0 = return [] readn n = c <- getchar cs <- readn (n-1) return (c:cs) 49
Leyes de las Mónadas return es un elemento neutro por la derecha de >> p >>= return == p return es un elemento neutro por la izquierda de >> en el sentido (return e) >>= q = q e >>= es asociativa en el siguiente sentido (p >>= q) >>= r = p >>= s where s x = (q x >>= r) 50