Logo Studenta

informática (27)

¡Este material tiene más páginas!

Vista previa del material en texto

Programación Funcional con Haskell
Mauro Jaskelioff
22/03/2016
Hay dos maneras de construir un diseño de software:
Una manera es hacerlo de manera tan simple que sea
obvio que no hay deficiencias.
La otra es hacerlo de manera tan complicada que no
haya deficiencias obvias.
Tony Hoare, 1980
Desaf́ıos del Software
I Sistemas de software gigantes, cores múltiples, sistemas
distribuidos.
I Costo y tiempo de desarrollo.
I Confianza en los programas desarrollados
I ¿Qué tecnoloǵıa nos permitirá lidiar con los desaf́ıos?
La arquitectura de Von Neumann
I Una idea en los 1940’ que simplificó en forma práctica y
elegante varios problemas de ingenieŕıa y programación.
I Las condiciones han cambiado, pero, la máquina de Von
Neumann es, esencialmente, lo que ahora conocemos como
computadoras.
I En su forma más simple, consiste de un CPU, una memoria y
un tubito que los une.
I La tarea de los programas es modificar los contenidos de la
memoria en forma significativa.
I Todas las modificaciones se hacen a través del tubito!
Lenguajes de Von Neumann
I Los lenguajes imperativos fueron creados para programar esta
computadora.
I Son versiones complejas, de alto nivel, de la máquina de Von
Neumann
I ¡Se definen por su traducción a la máquina!
I La caracteŕıstica de estos lenguajes es:
I Las variables representan celdas de memoria.
I Los comandos de control representan saltos y comparaciones.
I La asignación imita la obtención y almacenamiento de datos.
I Separación entre datos y programas.
I Dada la popularidad de los lenguajes de Von Neumann, otras
arquitecturas no fueron desarrolladas.
Algunas problemas de los programas imperativos
I Mantienen un estado invisible.
I Uno debe ejecutar los programas mentalmente para
entenderlos.
I No son composicionales.
I Es dif́ıcil abstraer patrones comunes.
I Fuerzan un orden de ejecución.
I Semántica compleja (o directamente, dada por la
implementación).
I Ausencia de propiedades matemáticas útiles.
Ejemplo
Cálculo del producto escalar de dos vectores:
c := 0
for i := 1 step 1 until n do
c := c + a [ i ] ∗ b [ i ]
Comparemos con:
prod = sum ◦ zipWith (∗)
Requerimientos para un Lenguaje de Programación
Moderno
I Los programas deben poder ser escritos en forma clara,
concisa, y a un alto nivel de abstracción.
I Se debe poder desarrollar componentes de software generales,
reusables y modulares.
I La semántica debe ser clara para facilitar la verificación formal.
I Debe permitir el prototipado rápido y proveer herramientas
potentes.
I Debe facilitar la programación paralela.
I Debe tener propiedades matemáticas útiles.
Programación funcional
I La programación funcional cumple en buena medida con estos
requerimientos.
I No usa un modelo de computación basado en máquinas sino
en un lenguaje simple y elegante (el λ-cálculo).
I Su simpleza y versatilidad lo hacen ideal para aprender
conceptos de
I programación,
I lenguajes de programación,
I computabilidad,
I semántica,
I verificación, derivación, testing.
¿Qué es la programación funcional?
Hay diferentes opiniones.
I Es un estilo de programación en el cual el método básico de
computar es aplicar funciones a argumentos
I Un lenguaje de programación funcional es un lenguaje que
permite y alienta el uso de un estilo funcional.
Haskell
I Haskell posee varias ventajas.
I Programas Concisos
I Sistemas de Tipos Poderoso
I Funciones Recursivas
I Fácilidad para probar propiedades de programas.
I Funciones de Alto Orden
I Evaluación Perezosa
I Facilidad para definir DSLs
I Efectos Monádicos
I Haremos un “repaso” por la sintaxis básica de Haskell.
I También introduciremos algunos conceptos nuevos.
Preludio
I Muchas funciones de uso común están definidas en el Preludio
(Prelude.hs)
I Además de las operaciones aritméticas usuales se definen
muchas funciones que operan sobre listas.
I head , tail , (!!), take, drop, length, sum, product, (++), reverse
I Leer el código del preludio puede ser muy instructivo (y
sorprendente!).
GHC
I Usaremos GHC, un compilador (ghc) e intérprete (ghci) de
Haskell, que también soporta varias extensiones del mismo.
I URL: http://www.haskell.org/ghc
I Es conveniente instalar la Haskell Platform, que consiste del
GHC junto con una colección de bibliotecas frecuentemente
usadas.
http://www.haskell.org/ghc
La aplicación de funciones
I en Haskell la aplicación se denota con un espacio y asocia a la
izquierda.
Matemáticas Haskell
f (x) f x
f (x , y) f x y
f (g (x)) f (g x)
f (x , (g (y)) f x (g y)
f (x) g (y) f x ∗ g y
I La aplicación tiene mayor precedencia que cualquier otro
operador.
f x + y = (f x) + y
Nombres y comentarios
I Las funciones y sus argumentos deben empezar con minúscula,
y pueden ser seguidos por cero o más letras (mayúsculas o
minúsculas), d́ıgitos, guiones bajos, y apóstrofes.
I Las palabras reservadas son:
case class data default deriving do else if import in infix
infixl infixr instance let module newtype of then type where
I Los comentarios se escriben
-- comentario que finaliza junto con la ĺınea
{- bloque de comentarios, útil para comentarios
que no entran en una sola ĺınea. -}
Offside Rule
I En una serie de definiciones, cada definición debe empezar en
la misma columna.
a = b + c
where
b = 1
c = 2
d = a + 2
I Gracias a esta regla no hace falta un sintaxis expĺıcita para
agrupar definiciones.
Operadores infijos
I Los operadores infijos son funciones como cualquier otra.
I Una función se puede hacer infija con backquotes
10 ‘div ‘ 4 = div 10 4
I Se pueden definir operadores infijos usando alguno de los
śımbolos disponibles
a ∗∗ b = (a ∗ b) + (a + 1) ∗ (b − 1)
I La asociatividad y precedencia se indica usando infixr
(asociativad der.), infixl (asociatividad izq.), o infix (si los
paréntesis deben ser obligatorios)
infixr 6 (∗∗)
Tipos
I Un tipo es un nombre para una colección de valores
I Ej: Bool contiene los valores True y False.
I Escribimos True :: Bool y False :: Bool .
I En general, si una expresión e tiene tipo t escribimos
e :: t
I En Haskell, toda expresión válida tiene un tipo
I El tipo de cada expresión es calculado previo a su evaluación
mediante la inferencia de tipos.
I Si no es posible encontrar un tipo (por ejemplo (True + 4)) el
compilador/intérprete protestará con un error de tipo.
Tipos básicos
Alguno de los tipos básicos de Haskell son:
I Bool , booleanos
I Char , caracteres
I Int, enteros de precisión fija.
I Integer , enteros de precisión arbitraria.
I Float, números de punto flotante de precisión simple.
Listas
I Una lista es una secuencia de valores del mismo tipo
I [True,True,False,True ] :: [Bool ]
I [’h’, ’o’, ’l’, ’a’] :: [Char ]
I En general, [t ] es una lista con elementos de tipo t.
I t, puede ser cualquier tipo válido.
I [[’a’], [’b’, ’c’], [ ]] :: [[Char ]]
I No hay restricción con respecto a la longitud de las listas.
Tuplas
I Una tupla es una secuencia finita de valores de tipos
(posiblemente) distintos.
I (True,True) :: (Bool ,Bool)
I (True, ’a’, ’b’) :: (Bool ,Char ,Char)
I En general, (t1, t2, ..., tn) es el tipo de una n-tupla cuyas
componente i tiene tipo ti , para i en 1 . . n.
I A diferencia de las listas, las tuplas tienen explicitado en su
tipo la cantidad de elementos que almacenan.
I Los tipos de las tuplas no tiene restricciones
I (’a’, (True, ’c’)):: (Char , (Bool ,Char))
I (([’a’, ’b’], ’a’), ’b’)::(([Char ],Char),Char)
Funciones
I Una función mapea valores de un tipo en valores de otro
I not :: Bool → Bool
I isDigit :: Char → Bool
I En general, Un tipo t1 → t2 mapea valores de t1 en valores de
t2.
I Se pueden escribir funciones con múltiples argumentos o
resultados usando tuplas y listas.
add :: (Int, Int)→ Int
add (x , y) = x + y
deceroa :: Int → [ Int ]
deceroa n = [0 . . n ]
Currificación y aplicación parcial
I Otra manera de tomar múltiples argumentos es devolver una
función comoresultado
add ′ :: Int → (Int → Int)
add ′ x y = x + y
I A diferencia de add , add ′ toma los argumentos de a uno por
vez. Se dice que add ′ está currificada.
I La ventaja de la versión currificada es que permite la
aplicación parcial:
suma3 :: Int → Int
suma3 = add ′ 3
Currificación
I Si queremos expresar una función que tome 3 argumentos
devolvemos una función que devuelve una función:
mult :: Int → (Int → (Int → Int))
mult x y z = x ∗ y ∗ z
I Para evitar escribir muchos paréntesis, por convención el
constructor de tipos → asocia a la derecha.
mult :: Int → Int → Int → Int
I Notar que esta convención es consistente con la aplicación
asociando a la izquierda.
I A menos que la función lo requiera, en Haskell todas las
funciones están currificadas.
Nombres de los tipos
I A excepción de listas, tuplas y funciones, los nombres de los
tipos concretos comienzan con mayúsculas.
I El espacio de nombres de los tipos está completamente
separado del espacio de nombres de las expresiones.
Polimorfismo
I Una función es polimórfica si su tipo contiene variables de
tipo.
length :: [a ]→ Int
Para cualquier tipo a la función length es la misma.
I Las variables de tipo se escriben con minúscula.
I Las variables de tipo pueden ser instanciadas a otros tipos
length [False,True ] ← a = Bool
length [’a’, ’b’] ← a = Char
I A veces se llama polimorfismo paramétrico a este tipo de
polimorfismo.
Sobrecarga de funciones
I ¿Cuál es el tipo de 3 + 2?
I En Haskell, los números y las operaciones aritméticas están
sobrecargadas
I Esta sobrecarga se realiza mediante clases de tipo.
I El operador (+) tiene tipo:
(+) :: Num a⇒ a→ a→ a
I (+) está definido para cualquier tipo que sea una instancia de
la clase Num.
I A diferencia del polimorfismo paramétrico, hay una definición
distinta de (+) para cada instancia.
Clases de tipo
I Haskell provee una serie de clases básicas:
I Eq son los tipos cuyos valores pueden ser comparados para ver
si son iguales o no.
(==) :: Eq a⇒ a→ a→ Bool
(/=) :: Eq a⇒ a→ a→ Bool
¿Qué tipo no puede ser instancia de esta clase?
I Ord son los tipos que además de ser instancias de Eq poseen
un orden total.
(<), (6), (>), (>) :: Ord a⇒ a→ a→ Bool
min,max :: Ord a⇒ a→ a→ a
Clases de tipo
I Show son los tipos cuyos valores pueden ser convertidos en
una cadena de caracteres.
show :: Show a⇒ a→ String
I Read es la clase dual. Son los tipos que se pueden obtener de
una cadena de caracteres.
read :: Read a⇒ String → a
I ¿A qué tipo corresponde la instancia que leerá
I not (read "False")?
I read "3"?
I read "3" :: Int?
Clases de Tipo
I Num son los tipos numéricos
I Sus instancias deben implementar
(+), (−), (∗) :: Num a⇒ a→ a→ a
negate, abs, signum :: Num a⇒ a→ a
¿Y la división?
I Integral son los tipos que son Num e implementan
div ,mod :: Integral a⇒ a→ a→ a
I Fractional son los tipos que son Num e implementan
(/) :: Fractional a⇒ a→ a→ a
recip :: Fractional a⇒ a→ a
Expresiones Condicionales
I Las funciones pueden ser definidas usando expresiones
condicionales
abs :: Int → Int
abs n = if n > 0 then n else− n
I Para que la expresión condicional tenga sentido, ambas ramas
de la misma deben tener el mismo tipo.
I Las expresiones condicionales siempre deben tener la rama
“else”
I Por lo tanto no hay ambigüedades en caso de anidamiento:
signum :: Int → Int
signum n = if n < 0 then− 1 else
if n == 0 then 0 else 1
Ecuaciones con Guardas
I Una alternativa a los condicionales es el uso de ecuaciones con
guardas.
abs n | n > 0 = n
| otherwise = −n
I Se usan para hacer ciertas definiciones más fáciles de leer.
signum n | n < 0 = −1
| n == 0 = 0
| otherwise = 1
I La condición otherwise se define en el preludio como
otherwise = True
Pattern Matching
I Muchas funciones se definen más claramente usando pattern
matching.
not :: Bool → Bool
not False = True
not True = False
I Los patrones se componen de constructores de datos y
variables (salvo los patrones 0 y n + 1) .
I Una variable es un patrón que nunca falla.
succ :: Int → Int
succ n = n + 1
Pattern Matching
I Usando el ingenio se pueden obtener definiciones concisas.
(∧) :: Bool → Bool → Bool
True ∧ True = True
True ∧ False = False
False ∧ True = False
False ∧ False = False
puede ser escrita en forma compacta como
(∧) :: Bool → Bool → Bool
True ∧ True = True
∧ = False
I Notar la importancia del orden de las ecuaciones.
Patrones de tuplas
I Una tupla de patrones es un patrón.
fst :: (a, b)→ a
fst (x , ) = x
snd :: (a, b)→ b
snd ( , y) = y
I ¿Qué hace la siguiente función?
f (x , (y , z)) = ((x , y), z)
I En general, los patrones pueden anidarse
Patrones de Listas
I Toda lista (no vaćıa) se contruye usando el operador (:)
(llamado cons) que agrega un elemento al principio de la lista
[1, 2, 3, 4] 7→ 1 : (2 : (3 : (4 : [ ])))
I Por lo tanto, puedo definir funciones usando el patrón (x : xs)
head :: [a ]→ a
head (x : ) = x
tail :: [a ]→ [a ]
tail ( : xs) = xs
I (x : xs) sólo matchea el caso de listas no vaćıas >head [ ]
Error !
Expresiones Lambda
I Se pueden construir funciones sin darles nombres usando
expresiones lambda.
λx → x + x
I Estas funciones se llaman funciones anónimas.
I En Haskell escribimos \ en lugar de la letra griega lambda λ
I La notación proviene del cálculo lambda, en el cual se basa
Haskell, y que estudiaremos en detalle más adelante.
Utilidad de las expresiones lambda
I Usando lambdas puedo explicitar que las funciones son
simplemente valores:
add x y = x + y
add = λx → (λy → x + y)
I Evito tener que darle un nombre a una función que se usa una
sola vez.
impares n = map f [0 . . n − 1]
where f x = x ∗ 2 + 1
odds n = map (λx → x ∗ 2 + 1) [0 . . n − 1]
Secciones
I Un operador infijo, puede ser escrito en forma prefija usando
paréntesis:
> (+) 1 2
3
I También uno de los argumentos puede ser inclúıdo en los
paréntesis
> (1+) 2
3
> (+2) 1
3
I En general, dado un operador ⊕, entonces las funciones de la
forma (⊕), (x⊕), (⊕y) son llamadas secciones.
Conjuntos por comprensión
I En matemáticas, una manera de construir conjuntos a partir
de conjuntos existentes es con la notación por comprensión
{x2|x ∈ {1 . . . 5}}
describe el conjunto {1, 4, 9, 16, 25} o (lo que es lo mismo) el
conjunto de todos los números x2 tal que x sea un elemento
del conjunto {1 . . . 5}
Listas por comprensión
I En Haskell, una manera de construir listas a partir de listas
existentes es con la notación por comprensión
[x ↑ 2 | x ← [1 . . 5]]
describe la lista [1, 4, 9, 16, 25] o (lo que es lo mismo) la lista
de todos los números x ↑ 2 tal que x sea un elemento de la
lista [1 . . 5]
I La expresión x ← [1 . . 5] es un generador, ya que dice como
se generan los valores de x .
I Una lista por comprensión puede tener varios generadores,
separados por coma.
> [(x , y) | x ← [1, 2, 3], y ← [4, 5]]
[(1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)]
I ¿Qué pasa cuando cambiamos el orden de los generadores?
> [(x , y) | y ← [4, 5], x ← [1, 2, 3]]
I Los generadores posteriores cambian más rápidamente.
Generadores dependientes
I Un generador puede depender de un generador anterior
[(x , y) | x ← [1 . . 3], y ← [x . . 3]]
Esto es la lista de todos los pares (x , y) tal que x , y están en
[1 . . 3] e y > x .
I ¿Qué hace la siguiente función?
concat :: [[a ]]→ [a ]
concat xss = [x | xs ← xss, x ← xs ]
> concat [[1, 2, 3], [4, 5], [6]]
[1, 2, 3, 4, 5, 6]
Guardas
I Las listas por comprensión pueden usar guardas para restringir
los valores producidos por generadores anteriores
[x | x ← [1 . . 10], even x ]
I ¿Qué hace la siguiente función?
factors :: Int → [ Int ]
factors n = [x | x ← [1 . . n ], n ‘mod ‘ x == 0]
I Como un número n es primo iff sus únicos factores son 1 y n,
podemos definir
prime :: Int → Bool
prime n = factors n == [1, n ]
primes :: Int → [ Int ]
primes n = [x | x ← [2 . . n ], prime x ]
Cadenas
I Una String es una lista de caracteres.I "Hola" :: String
I "Hola" = [’H’, ’o’, ’l’, ’a’]
I Todas las funciones sobre listas son aplicables a String , y las
listas por comprensión pueden ser aplicadas a Strings.
cantminusc :: String → Int
cantminusc xs = length [x | x ← xs, isLower x ]
Zip
I La función zip, mapea dos listas a una lista con los pares de
elementos correspondientes
zip :: [a ]→ [b ]→ [(a, b)]
> zip [’a’, ’b’, ’c’] [1, 2, 3, 4]
[(’a’, 1), (’b’, 2), (’c’, 3)]
I Ejemplo: Lista de pares de elementos adyacentes:
pairs :: [a ]→ [(a, a)]
pairs xs = zip xs (tail xs)
I ¿Está una lista ordenada?
sorted :: Ord a⇒ [a ]→ Bool
sorted xs = and [x 6 y | (x , y)← pairs xs ]
Ejemplo zip: pares ı́ndice/valor
I Podemos usar zip para generar ı́ndices
rangeof :: Int → Int → [a ]→ [a ]
rangeof low hi xs = [x | (x , i)← zip xs [0 . .],
i > low ,
i 6 hi ]
> [x ↑ 2 | x ← [1 . . 10]]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
> rangeof 3 7 [x ↑ 2 | x ← [1 . . 10]]
[16, 25, 36, 49, 64]
Recursión
I En los lenguajes funcionales, la recursión es uno de los
principales recursos para definir funciones.
factorial :: Int → Int
factorial 0 = 1
factorial n = n ∗ factorial (n − 1)
I ¿Qué sucede con factorial n cuando n < 0?
I Recursión sobre listas
length :: [a ]→ Int
length [ ] = 0
length (x : xs) = 1 + length xs
Recursión Mutua
I No hace falta ninguna sintaxis especial para la recursión
mutua.
zigzag :: [(a, a)]→ [a ]
zigzag = zig
zig [ ] = [ ]
zig ((x , ) : xs) = x : zag xs
zag [ ] = [ ]
zag (( , y) : xs) = y : zig xs
I > zigzag [(1, 2), (3, 4), (5, 6), (7, 8)]
[1, 4, 5, 8]
Quicksort
I El algoritmo de ordenación Quicksort:
I La lista vaćıa está ordenada
I Las listas no vaćıas pueden ser ordenadas, ordenando los
valores de la cola 6 que la cabeza, ordenando los valores >
que la cabeza y rearmando el resultado con las listas
resultantes a ambos lados de la cabeza.
I Su implementación:
qsort :: Ord a⇒ [a ]→ [a ]
qsort [ ] = [ ]
qsort (x : xs) = qsort chicos ++ [x ] ++ qsort grandes
where chicos = [a | a← xs, a 6 x ]
grandes = [b | b ← xs, b > x ]
Referencias
I “Can Programming Be Liberated from the von Neumann
Style?”. John Backus, Turing Award Lecture. 1977
I Why Functional Programming Matters. John Hughes,
Computing Journal. Vol 32. 1989.
I Programming in Haskell. Graham Hutton, CUP 2007.
I Introducción a la Programación Funcional con Haskell.
Richard Bird, Prentice Hall 1997.
	Programación Funcional
	Programación Funcional
	Haskell
	Sintaxis
	Tipos
	Expresividad
	Listas
	Listas Por comprensión
	Cadenas
	Zip
	Recursión

Continuar navegando

Materiales relacionados

80 pag.
Lenguaje-C

UBAM

User badge image

Contenidos Muy Locos

74 pag.
teoricasAlgo1

SIN SIGLA

User badge image

Karen Marlene Valdez

17 pag.
Tipo_Lenguajes_programacion

SIN SIGLA

User badge image

juliangelabert