1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

50
1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3

Transcript of 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

Page 1: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

1

Algoritmos y Estructuras de Datos I

Programación funcional

Clase 3

Page 2: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

2

Tipos recursivosEl tipo definido es argumento de alguno de los constructores.Ejemplos :

data N = Z | S N

El tipo N tiene por supuesto al elemento ZConstructor sin argumentos (como False en Bool, Calor en Sensacion)

El constructor S Fabrica un N a partir de otro N, agregándole una S al principioPor ahora solamente conocemos un N que es ZPodemos formar únicamente el elemento S ZEs un nuevo NPodemos usarlo con el constructor S para fabricar otro N: S(S Z)Y así sucesivamente

Entonces, elementos del tipo N son Z, S Z, S(S Z), S(S(S Z)), S(S(....(S Z))...)

Page 3: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

3

Tipos recursivosdata BE = TT | FF | AA BE BE | NN BE

Dos constructores recursivosUno con dos parámetros del mismo tipo

Tenemos dos elementos: TT y FF (constructores sin parámetros)Podemos usarlos con NN para formar valores parecidos a los de N

NN(NN(...(NN TT))...) NN(NN(...(NN FF))...)

O podemos combinarlos con AA AA TT FFAA FF FF

O usar los BE que construimos con NN como argumentos de AA AA (NN(NN TT)) FF

También combinación inversa: aplicar NN a términos que comienzan con AANN(NN(AA TT NN(TT)))

Por último, términos que contengan AA como argumentos de AAAA (AA(NN FF)(TT)) (AA(AA(NN TT) TT) FF)

Este tipo también podría representar uno más conocido, ¿Cuál?

Page 4: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

4

Usando recursión estructural

Da una explosión combinatoria de valores posibles.

Pero también, una forma de controlarla:

Los dominios tienen muchos términos.

Todos tienen que estar creados con uno de los constructores

aplicados a cero o más argumentos.

Usando pattern matching, podemos definir funciones recursivas

sobre cualquier término mediante recursión estructural.

Page 5: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

5

Ejemplossize :: N -> Int

size Z = 0 size (S x) = 1 + size x

addN :: N -> N -> N addN Z m = maddN (S n) m = S (addN n m)

contarAes :: BE -> Int contarAes FF = 0contarAes TT = 0contarAes (NN b) = contarAes bcontarAes (AA b1 b2) = 2 + contarAes b1 + contarAes b2

Para poder aplicar la función a todas las expresiones del tipo, tenemos que poner por lo menos una ecuación para cada constructor

Para que el paso recursivo sea correcto, hay que usar, del lado derecho del igual, subexpresiones de la que aparece del izquierdo.

Page 6: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

6

Listas

Es el tipo recursivo más usadoBrinda un tratamiento de secuencias.Hay muchas funciones para listas en preludio

Son tipos algebraicos con sintaxis propia

Primero, vamos a definirlas como un tipo algebraico comúndata List a = Nil | Cons a (List a)

Después vamos a ver la notación de Haskell, que es más cómoda.

Page 7: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

7

List a

data List a = Nil | Cons a (List a)

Tipo paramétrico.Instanciémoslo: List IntTenemos Nil (sin parámetros) Lo interpretamos como secuencia vacía

Podemos usar ConsPor ahora, Cons n NilListas unitarias Cons 2 Nil

Y seguimos con ConsCons 2 (Cons 3 (Cons 2 (Cons 0 Nil)))

Page 8: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

8

Haciendo recursión estructural (List)

Sumemos todos los elementos de una lista de enteros

sumList :: List Int -> Int

sumList Nil = 0

sumList (Cons n ns) = n + sumList ns

Page 9: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

9

Sintaxis Haskell para listas (notación más cómoda)

Nil se escribe [](Cons x xs) se escribe (x:xs)

Dos contructores : []Cons 2 (Cons 3 (Cons 2 (Cons 0 Nil))) se escribe 2 : 3 : 2 : 0 : [] Notación más cómoda [2,3,2,0]

Page 10: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

10

Haciendo recursión estructural (List a)

sum :: [Int] -> Int sum [ ] = 0sum (n:ns) = n + sum ns

length :: [a] -> Int length [ ] = 0length (x:xs) = 1 + length xs

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

Page 11: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

11

Operadores

Se llama operador a una función cuyo nombre empieza con un carácter especial.

Si nos referimos a la función sin aplicarla o en forma prefija,

se usa entre paréntesis.

Sino la usamos en forma infija entre sus operandos.

Ejemplo: La función de concatenación de listas (++)

Ejemplo: [1, 2] ++ [3, 4, 5]

Page 12: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

12

Transparencia referencial“Quiero cambiar el primer elemento de una lista de naturales por un cuatro.” No se puede hacer, los objetos no cambian de valor:

listaUno :: [Int] listaUno = [1, 2, 3, 4]“Dada una lista, quiero obtener otra que coincida con ella en todas las posiciones, excepto en la primera, que debe ser un cuatro.” Matemática: si dos listas difieren en un elemento, no son la misma

ponerCuatro :: [Int] -> [Int] ponerCuatro (x : xs) = (4 : xs)No cambia ningún elemento de la lista.Crea una lista nueva parecida a la primera.Repetimos xs en el resultado para que estuviera el resto.Si aplicamos a listaUno, obtenemos [4, 2, 3, 4] El valor de listaUno va a seguir siendo el mismo

Faltaría ecuación para explicar lista vacía!Que dice la especificación? Cambiar un elemento por otro.Alternativa a) lo dejamos así. (Si aplica a la lista vacía, da error: no hay elemento a cambiar por 4).Alternativa b) Nueva especificación para devolver [4] Alternativa c) devolver la lista vacíaEjemplo de importancia de contar con una especificación precisa.

Page 13: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

13

Árboles

Estructura formada por nodos que almacenan valores de manera jerárquica.

Vamos a trabajar con árboles binarios:

Cada nodo almacena un valor, y de cada nodo salen, o bien dos nodos, o bien ningun nodo.

Se llama Hoja a aquel nodo del que no sale ningun otro nodo.

Se llama Rama a aquel nodo del que salen otros dos nodos.

Se llama Raíz a aquel nodo que no tiene padre.

Típicamente los árboles se dibujan en forma invertida: Raíz arriba de todo y las Hojas abajo.

Page 14: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

14

Un árbol

Nodo 1

Hoja 0

Nodo 3

Hoja 2

Hoja 0

Page 15: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

15

Tipo de datos Arbol a

Definición del tipo

data Arbol a = Hoja a | Nodo a (Arbol a) (Arbol a)

El árbol del ejemplo

aej :: Arbol Int

aej = Nodo 1 (Hoja 0) (Nodo 3 (Hoja 2) (Hoja 0))

Funciones (recursión estructural)

hojas :: Arbol a -> Int

hojas (Hoja x) = 1

hojas (Nodo x i d) = hojas i + hojas d

altura :: Arbol a -> Int

altura (Hoja x) = 0

altura (Nodo x i d) = 1 +(altura i ‘max‘ altura d)

Page 16: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

16

Transparencia referencial

Función parecida a ponerCuatro.

Se puede decir que cambia los números 2 de las hojas por números 3

Pero sabemos que fabrica árboles nuevos, con los mismos datos que el recibido como argumento, excepto algunos

cambiar2 :: Arbol Int -> Arbol Int

cambiar2 (Hoja n) | n==2 = Hoja 3

cambiar2 (Hoja n) | otherwise = Hoja n

cambiar2 (Nodo n i d) = Nodo n (cambiar2 i) (cambiar2 d)

Page 17: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

17

Ejercicio: Reducir cambiar2 aej

Ejercicio: Reducir duplA aej y sumA aej

duplA :: Arbol Int -> Arbol Int

duplA (Hoja n) = Hoja (n*2)

duplA (Nodo n i d) = Nodo (n*2) (duplA i) (duplA d)

sumA :: Arbol Int -> Int

sumA (Hoja n) = n

sumA (Nodo n i d) = n + sumA i + sumA d

.

Page 18: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

18

Recorriendo un árbol

Las funciones que vimos van “recorriendo” un árbol y operando con cada nodo.

Podrían hacerlo en cualquier orden con el mismo resultado final.

Sin embargo, a veces es importante el orden al recorrer un árbol para aplicarle alguna operación.

Por ejemplo, buscamos en qué ubicación está la primera aparición de un dato en un árbol. La interpretación de “primera” puede cambiar.

Vamos a ver dos funciones que arman una lista con los elementos de un árbol. Ambas respetan el orden de los elementos en el árbol, pero el criterio para respetar ese orden es distinto

Page 19: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

19

Órdenes de recorrido de árboles

inOrder, preOrder :: Arbol a -> [ a ]

inOrder (Hoja x) = [ x ]

inOrder (Nodo x i d) = inOrder i ++ [ x ] ++ inOrder d

preOrder (Hoja x) = [ x ]

preOrder (Nodo x i d) = x : (preOrder i ++ preOrder d)

Ejercicio: Reducir inOrder aej y preOrder aej.

Ejercicio: Definir un tercer orden: posOrder.

Page 20: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

20

Aplicaciones con Conversión de tipos

Convertimos el árbol en lista, de acuerdo a algún orden.

Podemos aprovechar funciones sobre listas para realizar operaciones

sobre los elementos del árbol.

Por ejemplo podemos redefinir sumA:

sumA a = sum (preOrder a)

Page 21: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

21

Utilidad de Polimorfismos

La mayoría de estas funciones son polimórficas (son casos más interesantes que la identidad).

Estas funciones (como preOrder) se basan solamente la estructura de sus argumentos:

No importa su valor.

Son aplicables a muchos tipos de datos, tanto existentes como por existir.

Page 22: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

22

Motivación para tipos abstractosVimos cómo crear y cómo usar tipos de datos algebraicos.

Al usar un tipo, se lo puede tratar como un tipo abstracto: Recibimos (o entregamos) un tipo de datos sin permitir acceso a representación

No hace falta más de un programador. Puedo crear un tipo (como un tipo algebraico) que voy a usar yo mismo y nadie más, pero ocultar su implementación. En otras partes del programa me está prohibido accederla

¿Para qué puedo querer ponerme límites yo mismo?Despertador lejos de la cama para evitar el reflejo de apagarloObjetos frente a la puerta de salida para no irse sin ellos

En el diseño de lenguajes de programación es un punto clave.Los lenguajes deben facilitar que el programador haga lo que quierePero también alentarlo a hacerlo bien. Y, más importante, impedirle hacerlo mal:

usar la representación interna del tipoen otros programas

Page 23: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

23

Recordemos los requisitos para tipos algebraicos

data Racional = R Int Int

Se puede usar, pero con cuidado:Nunca construir con segunda componente 0.Funciones: mismo resultado para todas las representaciones del mismo racional Función numerador que devuelve la primera componente Resultados distintos para R (-1) (-2) y R 2 4 Son el mismo número, no estamos representando las fracciones

El lenguaje puede ayudar:Recursos sintácticos para avisar que esta representación interna se usa en unas pocas funcionesMensaje de error si se intenta violar ese acuerdo.

Page 24: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

24

Mantenimiento de programas

Acceso por pattern matching limitado a pocas funcionesSi decidimos cambiar la representación interna, el cambio se va a limitar también a esas funcionesEn lo posible, muy cerca una de otra en el código (mismo archivo).

Si no pedimos al lenguaje esta protecciónRiesgo de usar (nosotros mismos u otro programador) la implementación del tipo en muchos otros programasCuando la reemplacemos, van a dejar de funcionar todos esos programas Hasta que los modifiquemos uno por uno.

Es bueno que el lenguaje brinde una manera de abstraernos de la representación interna de los tipos.

Vamos a ver:

Cómo usar los tipos abstractos que recibimos ya implementados.

Cómo hacer para presentar como abstractos los tipos que creamos.

Page 25: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

25

Uso de tipos abstractos

Recibimos un tipo de datos abstractonombre del tiponombres de sus operaciones básicastipos de las operacionesespecificación de su funcionamiento

Puede ser más o menos formalEn Haskell no es chequeada por el lenguaje

¿Cómo se utiliza el tipo?A través de sus operacionesY únicamente asíNo solo por convicción: no tenemos otro remedio

Page 26: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

26

Ejemplo: racionales

Nos dan un tipo de datos abstracto que representa los racionalesNo nos importa cómo fue definido. Quizás, con un tipo algebraico como vimos antes, pero no podemos aplicar pattern matching.

Recibimos operaciones:crearR :: Int -> Int -> RacionalnumerR :: Racional -> IntdenomR :: Racional -> Int

Especificación Numerador y denominador: forma normalizada (máxima simplificación) con signo en el numerador.No sabemos cuándo se produce la simplificación para normalizar

al construir el número en crearR al evaluarlo, en numerR y denomR

numerR (crearR (-1) (-2)) == numerR (crearR 2 4)

También aclara que crearR n 0 da error

Page 27: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

27

Operaciones sobre racionales

Tal vez nos den operaciones básicas (suma y multiplicación)

También podríamos definirlas nosotros:

sumaR, multR, divR :: Racional -> Racional -> Racional

r1 ‘sumaR‘ r2 = crearR

(denomR r2 * numerR r1 + denomR r1 * numerR r2)

(denomR r1 * denomR r2)

r1 ‘multR‘ r2 = crearR

(numerR r1 * numerR r2)

(denomR r1 * denomR r2)

r1 ‘divR‘ r2 = crearR

(denomR r2 * numerR r1)

(denomR r1 * numerR r2)

Page 28: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

28

Otro ejemplo: diccionario

Guarda definiciones para ciertas palabras (como diccionario de la lengua)

Visto en forma genérica (paramétrico), se usa mucho en los programas de computación.Por ejemplo, en lugar de relacionar palabras con definiciones, relaciona números de teléfonos con abonadosCombinado con un dispositivo de identificación de llamadas: averiguar el nombre de quien me llama.

Almacenamiento:Para el implementador, ambos diccionarios (palabras y teléfonos) estarían fuera de la memoria Medio externo con más capacidad y tolerancia a fallasEn esta materia no van a aprender cómo comunicarse con dispositivos así desde Haskell. Como programadores-usuarios no nos interesa.

Page 29: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

29

Operaciones del diccionario

Buscar:: Diccionario -> Palabra -> Maybe Definicionagregar::(Palabra,Definicion)-> Diccionario-> Diccionarioeliminar:: Palabra -> Diccionario -> Diccionariocastellano:: Diccionario

Transparencia referencialagregar y eliminar no modifican, no cambian un diccionarioCrean un diccionario nuevo a partir de uno existente y unos datos más.

agregar (“eldiego", “jugador de futbol") castellano Construye diccionario nuevoPuedo preguntarle qué quiere decir “eldiego” castellano sigue siendo el mismo diccionario¿Cómo se llama el nuevo diccionario?

No tiene un nombreLa forma de hablar de él es con la expresión enteraSi queremos hacer la pregunta:

buscar(agregar(“eldiego",“jugador de futbol") castellano) “eldiego"

Devuelve Just “jugador de futbol"

Page 30: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

30

Sobre el diccionario

Además de las operaciones y sus tipos, el diseñador nos da una especificación

Agregar dos veces una palabra a un diccionario es lo mismo que agregarla una vez.No importa en qué orden se agreguen dos palabras. Si se saca una palabra, su definición desaparece.

Page 31: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

31

Otro ejemplo: conjuntos

El tipo de datos preferido para representar colecciones en programación funcional no es conjuntos, sino listas, porque los conjuntos no pueden representarse con un tipo algebraico que cumpla las condiciones que pusimos (los constructores permiten armar de varias formas el mismo conjunto, o expresiones que no son conjuntos).

La solución es crear un tipo abstracto para los conjuntos.Las operaciones disponibles para el programador-usuario van a limitar su uso como pasó con los racionales

Page 32: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

32

Operaciones de conjuntos

El conjunto vacíoempty :: IntSet

¿El conjunto dado es vacío?isEmpty :: IntSet -> Bool

¿Un elemento pertenece al conjunto?belongs :: Int -> IntSet -> Bool

Agregar un elemento al conjunto, si no estaba; si estaba, dejarlo igualinsert :: Int -> IntSet -> IntSet

Elegir el menor número y quitarlo del conjuntochoose :: IntSet -> (Int, IntSet)

Page 33: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

33

Transparencia referencial

Las operaciones no modifican los conjuntos, construyen conjuntos nuevosPero es fácil explicar las cosas en estos términos

choose no quita nadaEl original, sigue igual que antesDevuelve un entero y otro conjunto, con todos los elementos del primero menos eseNo queda claro qué pasa si el conjunto es vacíoSe puede suponer que da error

Page 34: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

34

Nuevas operaciones sobre conjuntos

Como programadores-usuarios, definamos operación nueva: uniónunion :: IntSet -> IntSet -> IntSet

union p q | isEmpty p = qunion p q | otherwise = unionAux (choose p) q

unionAux (x,p') q = insert x (union p' q)

Pudimos hacerlo sin conocer la representación de los conjuntos.Usamos las operaciones provistas. Si cambia la representación el implementador tiene que rescribir las ecuaciones para las operaciones del tipo abstracto (empty, isEmpty, belongs, insert, choose)union y cualquier otra definida en otros programas quedan intactas.

Observemos que la recursión no es privativa del pattern matchingunion está definida en forma recursiva (a través de unionAux)Pero no usa recursión estructural ¡ni siquiera conocemos la estructura!

Page 35: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

35

Creación de tipos abstractos

Pasemos al rol de implementadores. Escribir un tipo abstracto que puedan usar otros programadores. En Haskell se hace con módulos. Son archivos de texto que contienen parte de un programa. Por ejemplo: uno o más tipos de datos con sus funciones. O, simplemente, un grupo de funciones. Dos características importantes en cualquier lenguaje

EncapsulamientoAgrupar estructura de datos con funciones básicasPrograma no es lista de definiciones y tipos, son “cápsulas” Cada una con un (tal vez más) tipo de datos y sus funciones específicas

Ocultamiento Cuando escribo un módulo indico qué nombres exportaCuáles van a poder usarse desde afuera y cuáles noAplicación: ocultar funciones auxiliares (como unionAux)También para crear tipos de datos abstractos debemos ocultar la representación interna

Page 36: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

36

Ejemplo de módulo

El primer ejemplo no es un tipo abstracto, sino algebraico.Vamos a agrupar la funcionalidad de los complejos y exportar el tipo completamente

module Complejos (Complejo(..), parteReal, parteIm) wheredata Complejo = C Float FloatparteReal, parteIm:: Complejo -> FloatparteReal(C r i) = rparteIm (C r i) = i

module introduce el módulo: nombre, qué exportaNombre (..) exporta el nombre del tipo y sus constructoresUn programador que lo use puede hacer pattern matchingDespués, where y definicionesSi no se pone la lista de exportación (Module Complejos where...), se exportan todos los nombres definidos

Page 37: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

37

Ejemplo de módulo de tipo abstracto

module Racionales (Racional, crearR, numerR, denomR) where

data Racional = R Int Int

crearR :: Int -> Int -> Racional

crearR n d = reduce (n*signum d) (abs d)

reduce :: Int -> Int -> Racional

reduce x 0 = error "Racional con denom. 0"

reduce x y = R (x ‘quot‘ d) (y ‘quot‘ d)

where d = gcd x y

numerR, denomR :: Racional -> Int

numerR (R n d) = n

denomR (R n d) = d

Page 38: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

38

Aclaraciones

signum (signo), abs (valor absoluto), quot (división entera) y

gcd (gratest common divisor (MCD)) están en el preludio.

En la lista de exportación no dice (..) después de Racional

Se exporta solamente el nombre del tipo

NO sus constructores

Convierte el tipo en abstracto para los programadores que lo usen

Tampoco exportamos la función auxiliar reduce.

Page 39: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

39

Ahora usemos el tipo

Volvemos al rol de programador-usuario . Indicar (en otro módulo) que se quiere incorporar este tipo de datos. Se usa la cláusula importEs posible importar todos los nombres exportados por un módulo (importando el nombre del módulo)O solamente algunos de ellos (aclarando entre paréntesis cuáles)

module Main whereimport Complejosimport Racionales (Racional, crearR)miPar :: (Complejo, Racional)miPar = (C 1 0, crearR 4 2)

Si se pone algunas de estas expresiones: va a dar errornumerR (snd miPar)reduce (crearR 4 2)R 4 2 + R 2 1

Page 40: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

40

Sinónimos de tipo

Nombre nuevo a un tipo de Haskell: se usa la cláusula type.No se crea un nuevo tipo, sino un sinónimo de tipo.Los dos nombres son equivalentes.Ejemplo: nombrar una instancia particular de un tipo paramétrico:

type String = [Char]type IntOChar = Either Int Char

O renombrar tipo existentes con nombre más significativo:type Nombre = Stringtype Sueldo = Inttype Empleado = (Nombre, Sueldo)type Direccion = Stringtype Persona = (Nombre, Direccion)Persona es par String, pero si usamos (String, String), es difícil

entenderTambién sinónimos de tipos paramétricos:

type Lista a = [a]type IntY a = (Int, a)

No es una forma de definir tipos, no admite recursión

Page 41: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

41

Otra forma de crear tipos

Vimos cómo crear sinónimos de tipos (es un nombre adicional para un tipo existente, y resulta intercambiable)

A veces queremos un tipo nuevo con la representación de uno existente, pero que NO puedan intercambiarse.

Si la representación de un tipo abstracto es uno que ya existe, no usamos sinónimo: Un programador-usuario podría aprovecharlo y tomar elementos iguales como diferentes , crear valores inválidos, atar el programa a esta representación.

Usamos la cláusula newtype

Page 42: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

42

newtype

Ejemplo, conjuntos de enteros. Los representamos internamente con listas y encerramos la representación en un tipo abstracto

[1, 3, 2, 4, 3] = [1, 2, 3, 4]newtype IntSet = Set [Int]

Muy similar a data, peroLlama la atención sobre renombre

A otro implementador encargado de modificarla,A alguien que tenga que analizarAl mismo implementador dentro de un tiempo

Admite un solo constructor con un parámetrono crea nuevos elementosrenombra elementos existentes (no intercambiable)

Mejor rendimiento que data

Page 43: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

43

Implementación de conjuntosRepresentación: listas ordenadas sin elementos repetidosmodule ConjuntoInt

(IntSet, empty, isEmpty, belongs, insert, choose) where

import qualified List (insert)newtype IntSet = Set [Int]empty :: IntSetempty = Set []isEmpty :: IntSet -> BoolisEmpty (Set xs) = null xs

belongs :: Int -> IntSet -> Boolbelongs x (Set xs) = x ‘elem‘ xs

insert :: Int -> IntSet -> IntSetinsert x (Set xs) | x ‘elem‘ xs = Set xsinsert x (Set xs) | otherwise = Set (List.insert x xs)

choose :: IntSet -> (Int, IntSet)choose (Set (x:xs)) = (x, Set xs)

Page 44: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

44

Aclaraciones

Función insert del modulo List Es útil para implementar los conjuntosInserta un elemento en su lugar correspondiente de una lista ordenadaPero estaba definiendo una función insert para conjuntos, y no puedo tener en el mismo programa las dos funciones

Usé qualified al importar insert del módulo ListPara referirme a ese nombre tengo que calificarlo con el del módulo. Tengo que escribir List.insertEsto me permitió distinguir los dos insert

Las funciones null (ver si una lista es vacía) y elem (ver si un elemento pertenece a una lista) están en el preludio.

Page 45: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

45

FinTerminamos el tema de definición de tipos en Haskell hasta el nivel con el que vamos a usarlos en la materia.Dejamos afuera:

chiches de notación (como definir campos con nombre para los tipos algebraicos) un tema conceptualmente complejo: clases de tipos. Son extensión sobre el sistema de tipos de Hindley-Milner y agrega mucho poder más allá del polimorfismo (overloading).

Pero con las herramientas que les dimos van a poder crear programas interesantes como el del proyecto de taller.

Page 46: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

46

Page 47: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

47

Polimorfismo

Dijimos que el tipado en Haskell es fuertePero es común querer escribir funciones que puedan usarse, sin

redefinirlas, para distintos tipos de datosSe llama polimorfismoEl comportamiento de la función no depende del valor de sus parámetrosEjemplo: id (identidad): id x = x

¿De qué tipo es id?id 3, entonces id :: Int -> Intid True, entonces id :: Bool -> Boolid doble, entonces id :: (Int -> Int) -> (Int -> Int)

Respuesta: id :: a -> a No importa qué tipo sea aUsamos variables de tipoid es un tipo parámetrico (depende de un parámetro) Es una función polimórficaSe lee “id es una función que dado un elemento de algún tipo a devuelve otro

elemento de ese mismo tipo.”

Page 48: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

48

Currificación

promedio1 :: (Float,Float) -> Floatpromedio1 (x,y) = (x+y)/2

promedio2 :: Float -> Float -> Floatpromedio2 x y = (x+y)/2

promedio1 recibe como único argumento un par ordenadopromedio2 recibe dos argumentos de tipo Float

Separamos los tipos de los argumentos por una flechaIgual a la que separa los tipos de los argumentos del tipo del resultadoNo es caprichoso, tiene implicancias teóricas y prácticas (no vamos a verlas)

La notación (una operación que permite) se llama currificaciónPor Haskell B. Curry

Matemático estadounidenseDe su nombre también se tomó el del lenguaje que están aprendiendo

Alcanza con ver que evita varios signos de puntuación (paréntesis y comas):promedio1 (promedio1 (2, 3), promedio1 (1, 2))promedio2 (promedio2 2 3) (promedio2 1 2)

Page 49: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

49

Roles

• Personas que trabajan en un desarrollo con tipos abstractos– Una persona puede ocupar más de uno

• En cada momento debe tener claro en cuál está– Un rol puede ser ejercido por un equipo

• Diseñador– Establece operaciones – Escribe las especificaciones– Puede decidir o sugerir representación interna

• Implementador– Escribe el código para definir el tipo, representación y funciones básicas

• QUÉ hacer (decisiones del diseñador) • CÓMO hacerlo (código escrito por el implementador)

• Programador-usuario– Utiliza las operaciones

• No sabe cómo están implementadas• O sabe, pero no puede aprovecharlo

– Se desentiende de la implementación y usa el tipo como primitivo– Ya usamos tipos abstractos (Int, Float)

Page 50: 1 Algoritmos y Estructuras de Datos I Programación funcional Clase 3.

50

Rol de los estudiantes en Algo I

• Prácticas y parciales – Pogramadores-usuarios

• Usar tipos de datos que se les van a proveer

• Taller– Además, implementadores– Implementar tipos de datos abstractos – Ahora veremos cómo

• Diseñadores– Siempre los docentes de la materia– Escapa al alcance de la misma – Incluso, en algunos casos, al del área de Programación.