Tema 9: Declaraciones de tipos y clases
Índice
- 1. Declaraciones de tipos
- 2. Definiciones de tipos de datos
- 3. Definición de tipos recursivos
- 3.1. Definición de tipos recursivos: Los naturales
- 3.2. Definiciones con tipos recursivos
- 3.3. Tipo recursivo con parámetro: Las listas
- 3.4. Definición de tipos recursivos: Los árboles binarios
- 3.5. Definiciones sobre árboles binarios
- 3.6. Definiciones sobre árboles binarios
- 3.7. Definiciones de distintos tipos de árboles
- 4. Sistema de decisión de tautologías
- 5. Máquina abstracta de cálculo aritmético
- 6. Declaraciones de clases y de instancias
- 7. Material complementario
- 8. Bibliografía
1. Declaraciones de tipos
1.1. Declaraciones de tipos como sinónimos
- Se puede definir un nuevo nombre para un tipo existente mediante una declaración de tipo.
Ejemplo: Las cadenas son listas de caracteres.
type String = [Char]
- El nombre del tipo tiene que empezar por mayúscula.
1.2. Declaraciones de tipos nuevos
- Las declaraciones de tipos pueden usarse para facilitar la lectura de tipos.
Las posiciones son pares de enteros.
type Pos = (Int,Int)
origen
es la posición (0,0).origen :: Pos origen = (0,0)
(izquierda p)
es la posición a la izquierda de la posiciónp
. Por ejemplo,izquierda (3,5) == (2,5)
Su definición es
izquierda :: Pos -> Pos izquierda (x,y) = (x-1,y)
1.3. Declaraciones de tipos parametrizadas
Las declaraciones de tipos pueden tener parámetros. Por ejemplo,
Par a
es el tipo de pares de elementos de tipoa
type Par a = (a,a)
(multiplica p)
es el producto del par de enterosp
. Por ejemplo,multiplica (2,5) == 10
Su definición es
multiplica :: Par Int -> Int multiplica (x,y) = x*y
(copia x)
es el par formado con dos copias dex
. Por ejemplo,copia 5 == (5,5)
Su definición es
copia :: a -> Par a copia x = (x,x)
1.4. Declaraciones anidadas de tipos
- Las declaraciones de tipos pueden anidarse. Por ejemplo,
Las posiciones son pares de enteros.
type Pos = (Int,Int)
Los movimientos son funciones que va de una posición a otra.
type Movimiento = Pos -> Pos
Las declaraciones de tipo no pueden ser recursivas. Por ejemplo, el siguiente código es erróneo.
type Arbol = (Int,[Arbol])
Al intentar cargarlo da el mensaje de error
Cycle in type synonym declarations
2. Definiciones de tipos de datos
2.1. Definición de tipos con data
- En Haskell pueden definirse nuevos tipos mediante
data
. El tipo de los booleanos está formado por dos valores para representar lo falso y lo verdadero.
data Bool = False | True
- El símbolo
|
se lee como "o". - Los valores
False
yTrue
se llaman los constructores del tipoBool
. - Los nombres de los constructores tienen que empezar por mayúscula.
2.2. Uso de los valores de los tipos definidos
- Los valores de los tipos definidos pueden usarse como los de los predefinidos.
Definición del tipo de movimientos:
data Mov = Izquierda | Derecha | Arriba | Abajo
Uso como argumento:
(movimiento m p)
es la posición obtenida aplicando el movimientom
a la posiciónp
. Por ejemplo,movimiento Arriba (2,5) == (2,6)
Su definición es
movimiento :: Mov -> Pos -> Pos movimiento Izquierda (x,y) = (x-1,y) movimiento Derecha (x,y) = (x+1,y) movimiento Arriba (x,y) = (x,y+1) movimiento Abajo (x,y) = (x,y-1)
Uso en listas:
(movimientos ms p)
es la posición obtenida aplicando la lista de movimientosms
a la posiciónp
. Por ejemplo,movimientos [Arriba, Izquierda] (2,5) == (1,6)
Su definición es
movimientos :: [Mov] -> Pos -> Pos movimientos [] p = p movimientos (m:ms) p = movimientos ms (movimiento m p)
Uso como valor:
(opuesto m)
es el movimiento opuesto dem
.movimiento (opuesto Arriba) (2,5) == (2,4)
Su definición es
opuesto :: Mov -> Mov opuesto Izquierda = Derecha opuesto Derecha = Izquierda opuesto Arriba = Abajo opuesto Abajo = Arriba
2.3. Definición de tipo con constructores con parámetros
- Los constructores en las definiciones de tipos pueden tener parámetros.
Ejemplo de definición
data Figura = Circulo Float | Rect Float Float
Tipos de los constructores:
λ> :type Circulo Circulo :: Float -> Figura λ> :type Rect Rect :: Float -> Float -> Figura
Uso del tipo como valor:
(cuadrado n)
es el cuadrado de ladon
.cuadrado :: Float -> Figura cuadrado n = Rect n n
Uso del tipo como argumento:
(area f)
es el área de la figuraf
. Por ejemplo,area (Circulo 1) == 3.1415927 area (Circulo 2) == 12.566371 area (Rect 2 5) == 10.0 area (cuadrado 3) == 9.0
Su definición es
area :: Figura -> Float area (Circulo r) = pi*r^2 area (Rect x y) = x*y
2.4. Definición de tipos con parámetros
- Los tipos definidos pueden tener parámetros.
Ejemplo de tipo con parámetro
data Maybe a = Nothing | Just a
(divisionSegura m n)
es la división dem
entren
sin
no es cero y nada en caso contrario. Por ejemplo,divisionSegura 6 3 == Just 2 divisionSegura 6 0 == Nothing
Su definición es
divisionSegura :: Int -> Int -> Maybe Int divisionSegura _ 0 = Nothing divisionSegura m n = Just (m ~div~ n)
(headSegura xs)
es la cabeza dexs
sixs
es no vacía y nada en caso contrario. Por ejemplo,headSegura [2,3,5] == Just 2 headSegura [] == Nothing
Su definición es
headSegura :: [a] -> Maybe a headSegura [] = Nothing headSegura xs = Just (head xs)
3. Definición de tipos recursivos
3.1. Definición de tipos recursivos: Los naturales
- Los tipos definidos con
data
pueden ser recursivos. Los naturales se construyen con el cero y la función sucesor.
data Nat = Cero | Suc Nat deriving Show
Tipos de los constructores:
λ> :type Cero Cero :: Nat λ> :type Suc Suc :: Nat -> Nat
Ejemplos de naturales:
Cero Suc Cero Suc (Suc Cero) Suc (Suc (Suc Cero))
3.2. Definiciones con tipos recursivos
(nat2int n)
es el número entero correspondiente al número naturaln
. Por ejemplo,nat2int (Suc (Suc (Suc Cero))) == 3
Su definición es
nat2int :: Nat -> Int nat2int Cero = 0 nat2int (Suc n) = 1 + nat2int n
(int2nat n)
es el número natural correspondiente al número enteron
. Por ejemplo,int2nat 3 == Suc (Suc (Suc Cero))
Su definición es
int2nat :: Int -> Nat int2nat 0 = Cero int2nat n = Suc (int2nat (n-1))
(suma m n)
es la suma de los número naturalesm
yn
. Por ejemplo,λ> suma (Suc (Suc Cero)) (Suc Cero) Suc (Suc (Suc Cero))
Su definición es
suma :: Nat -> Nat -> Nat suma Cero n = n suma (Suc m) n = Suc (suma m n)
Ejemplo de cálculo:
suma (Suc (Suc Cero)) (Suc Cero) = Suc (suma (Suc Cero) (Suc Cero)) = Suc (Suc (suma Cero (Suc Cero))) = Suc (Suc (Suc Cero))
3.3. Tipo recursivo con parámetro: Las listas
Definición del tipo lista:
data Lista a = Nil | Cons a (Lista a)
(longitud xs)
es la longitud de la listaxs
. Por ejemplo,longitud (Cons 2 (Cons 3 (Cons 5 Nil))) == 3
Su definición es
longitud :: Lista a -> Int longitud Nil = 0 longitud (Cons _ xs) = 1 + longitud xs
3.4. Definición de tipos recursivos: Los árboles binarios
Ejemplo de árbol binario:
5 / \ / \ 3 7 / \ / \ 1 4 6 9
Definición del tipo de árboles binarios:
data Arbol = Hoja Int | Nodo Arbol Int Arbol
Representación del ejemplo
ejArbol = Nodo (Nodo (Hoja 1) 3 (Hoja 4)) 5 (Nodo (Hoja 6) 7 (Hoja 9))
3.5. Definiciones sobre árboles binarios
(ocurre m a)
se verifica sim
ocurre en el árbola
. Por ejemplo,ocurre 4 ejArbol == True ocurre 10 ejArbol == False
Su definición es
ocurre :: Int -> Arbol -> Bool ocurre m (Hoja n) = m == n ocurre m (Nodo i n d) = m == n || ocurre m i || ocurre m d
(aplana a)
es la lista obtenida aplanando el árbola
. Por ejemplo,aplana ejArbol == [1,3,4,5,6,7,9]
Su definición es
aplana :: Arbol -> [Int] aplana (Hoja n) = [n] aplana (Nodo i n d) = aplana i ++ [n] ++ aplana d
3.6. Definiciones sobre árboles binarios
- Un árbol es ordenado si el valor de cada nodo es mayor que los de su subárbol izquierdo y menor que los de su subárbol derecho.
- El árbol del ejemplo es ordenado.
(ocurreEnArbolOrdenado m a)
se verifica sim
ocurre en el árbol ordenadoa
. Por ejemplo,ocurreEnArbolOrdenado 4 ejArbol == True ocurreEnArbolOrdenado 10 ejArbol == False
Su definición es
ocurreEnArbolOrdenado :: Int -> Arbol -> Bool ocurreEnArbolOrdenado m (Hoja n) = m == n ocurreEnArbolOrdenado m (Nodo i n d) | m == n = True | m < n = ocurreEnArbolOrdenado m i | otherwise = ocurreEnArbolOrdenado m d
3.7. Definiciones de distintos tipos de árboles
Árboles binarios con valores en las hojas:
data Arbol a = Hoja a | Nodo (Arbol a) (Arbol a)
Árboles binarios con valores en los nodos:
data Arbol a = Hoja | Nodo (Arbol a) a (Arbol a)
Árboles binarios con valores en las hojas y en los nodos:
data Arbol a b = Hoja a | Nodo (Arbol a b) b (Arbol a b)
Árboles con un número variable de sucesores:
data Arbol a = Nodo a [Arbol a]
4. Sistema de decisión de tautologías
4.1. Sintaxis de la lógica proposicional
- Definición de fórmula proposicional:
- Las variables proposicionales son fórmulas.
- Si F es una fórmula, entonces ¬F también lo es.
- Si F y G son fórmulas, entonces (F ∧ G) y (F ∨ G) también lo son.
Tipo de dato de fórmulas proposicionales:
data FProp = Const Bool | Var Char | Neg FProp | Conj FProp FProp | Impl FProp FProp deriving Show
Ejemplos de fórmulas proposicionales:
- A ∧ ¬A
- (A ∧ B) → A
- A → (A ∧ B)
- (A → (A → B)) → B
Su definición es
p1, p2, p3, p4 :: FProp p1 = Conj (Var 'A') (Neg (Var 'A')) p2 = Impl (Conj (Var 'A') (Var 'B')) (Var 'A') p3 = Impl (Var 'A') (Conj (Var 'A') (Var 'B')) p4 = Impl (Conj (Var 'A') (Impl (Var 'A') (Var 'B'))) (Var 'B')
4.2. Semántica de la lógica proposicional
Tabla de verdad de la negación:
i ¬i T F F T Tablas de verdad de la conjunción y el condicional:
i j i ∧ j i → j T T T T T F F F F T F T F F F T Tabla de verdad para (A → B) ∨ (B → A):
A B A → B B → A (A → B) ∨ (B → A) T T T T T T F F T T F T T F T F F T T T Las interpretaciones son listas formadas por el nombre de una variable proposicional y un valor de verdad.
type Interpretacion = [(Char, Bool)]
(valor i p)
es el valor de la fórmulap
en la interpretacióni
. Por ejemplo,valor [('A',False),('B',True)] p3 == True valor [('A',True),('B',False)] p3 == False
Su definición es
valor :: Interpretacion -> FProp -> Bool valor _ (Const b) = b valor i (Var x) = busca x i valor i (Neg p) = not (valor i p) valor i (Conj p q) = valor i p && valor i q valor i (Impl p q) = valor i p <= valor i q
(busca c t)
es el valor del primer elemento de la lista de asociaciónt
cuya clave esc
. Por ejemplo,busca 2 [(1,'a'),(3,'d'),(2,'c')] == 'c'
Su definición es
busca :: Eq c => c -> [(c,v)] -> v busca c t = head [v | (c',v) <- t, c == c']
(variables p)
es la lista de los nombres de las variables dep
.variables p3 == "AAB"
Su definición es
variables :: FProp -> [Char] variables (Const _) = [] variables (Var x) = [x] variables (Neg p) = variables p variables (Conj p q) = variables p ++ variables q variables (Impl p q) = variables p ++ variables q
(interpretacionesVar n)
es la lista de las interpretaciones conn
variables. Por ejemplo,λ> interpretacionesVar 2 [[False,False], [False,True], [True,False], [True,True]]
Su definición es
interpretacionesVar :: Int -> [[Bool]] interpretacionesVar 0 = [[]] interpretacionesVar n = map (False:) bss ++ map (True:) bss where bss = interpretacionesVar (n-1)
(interpretaciones p)
es la lista de las interpretaciones de la fórmulap
. Por ejemplo,λ> interpretaciones p3 [[('A',False),('B',False)], [('A',False),('B',True)], [('A',True),('B',False)], [('A',True),('B',True)]]
Su definición es
interpretaciones :: FProp -> [Interpretacion] interpretaciones p = [zip vs i | i <- interpretacionesVar (length vs)] where vs = nub (variables p)
Decisión de tautología
(esTautologia p)
se verifica si la fórmulap
es una tautología. Por ejemplo,esTautologia p1 == False esTautologia p2 == True esTautologia p3 == False esTautologia p4 == True
Su definición es
esTautologia :: FProp -> Bool esTautologia p = and [valor i p | i <- interpretaciones p]
5. Máquina abstracta de cálculo aritmético
5.1. Evaluación de expresiones aritméticas
Una expresión aritmética es un número entero o la suma de dos expresiones.
data Expr = Num Int | Suma Expr Expr
(valorEA x)
es el valor de la expresión aritméticax
.valorEA (Suma (Suma (Num 2) (Num 3)) (Num 4)) == 9
Su definición es
valorEA :: Expr -> Int valorEA (Num n) = n valorEA (Suma x y) = valorEA x + valorEA y
Cálculo:
valorEA (Suma (Suma (Num 2) (Num 3)) (Num 4)) = (valorEA (Suma (Num 2) (Num 3))) + (valorEA (Num 4)) = (valorEA (Suma (Num 2) (Num 3))) + 4 = (valorEA (Num 2) + (valorEA (Num 3))) + 4 = (2 + 3) + 4 = 9
5.2. Máquina de cálculo aritmético
La pila de control de la máquina abstracta es una lista de operaciones.
type PControl = [Op]
Las operaciones son meter una expresión en la pila o sumar un número con el primero de la pila.
data Op = METE Expr | SUMA Int
(eval x p)
evalúa la expresiónx
con la pila de controlp
. Por ejemplo,eval (Suma (Suma (Num 2) (Num 3)) (Num 4)) [] == 9 eval (Suma (Num 2) (Num 3)) [METE (Num 4)] == 9 eval (Num 3) [SUMA 2, METE (Num 4)] == 9 eval (Num 4) [SUMA 5] == 9
Su definición es
eval :: Expr -> PControl -> Int eval (Num n) p = ejec p n eval (Suma x y) p = eval x (METE y : p)
(ejec p n)
ejecuta la lista de controlp
sobre el enteron
. Por ejemplo,ejec [METE (Num 3), METE (Num 4)] 2 == 9 ejec [SUMA 2, METE (Num 4)] 3 == 9 ejec [METE (Num 4)] 5 == 9 ejec [SUMA 5] 4 == 9 ejec [] 9 == 9
Su definición es
ejec :: PControl -> Int -> Int ejec [] n = n ejec (METE y : p) n = eval y (SUMA n : p) ejec (SUMA n : p) m = ejec p (n+m)
(evalua e)
evalúa la expresión aritméticae
con la máquina abstracta. Por ejemplo,evalua (Suma (Suma (Num 2) (Num 3)) (Num 4)) == 9
Su definición es
evalua :: Expr -> Int evalua e = eval e []
Evaluación:
eval (Suma (Suma (Num 2) (Num 3)) (Num 4)) [] = eval (Suma (Num 2) (Num 3)) [METE (Num 4)] = eval (Num 2) [METE (Num 3), METE (Num 4)] = ejec [METE (Num 3), METE (Num 4)] 2 = eval (Num 3) [SUMA 2, METE (Num 4)] = ejec [SUMA 2, METE (Num 4)] 3 = ejec [METE (Num 4)] (2+3) = ejec [METE (Num 4)] 5 = eval (Num 4) [SUMA 5] = ejec [SUMA 5] 4 = ejec [] (5+4) = ejec [] 9 = 9
6. Declaraciones de clases y de instancias
6.1. Declaraciones de clases
- Las clases se declaran mediante el mecanismo
class
. Ejemplo de declaración de clases:
class Eq a where (==), (/=) :: a -> a -> Bool -- Minimal complete definition: (==) or (/=) x == y = not (x/=y) x /= y = not (x==y)
6.2. Declaraciones de instancias
- Las instancias se declaran mediante el mecanismo
instance
. Ejemplo de declaración de instancia:
instance Eq Bool where False == False = True True == True = True _ == _ = False
6.3. Extensiones de clases
- Las clases pueden extenderse mediante el mecanismo
class
. Ejemplo de extensión de clases:
class (Eq a) => Ord a where compare :: a -> a -> Ordering (<), (<=), (>=), (>) :: a -> a -> Bool max, min :: a -> a -> a -- Minimal complete definition: (<=) or compare -- using compare can be more efficient for complex types compare x y | x==y = EQ | x<=y = LT | otherwise = GT x <= y = compare x y /= GT x < y = compare x y == LT x >= y = compare x y /= LT x > y = compare x y == GT max x y | x <= y = y | otherwise = x min x y | x <= y = x | otherwise = y
6.4. Instancias de clases extendidas
- Las instancias de las clases extendidas pueden declararse mediante el
mecanismo
instance
. Ejemplo de declaración de instancia:
instance Ord Bool where False <= _ = True True <= True = True True <= False = False
6.5. Clases derivadas
- Al definir un nuevo tipo con
data
puede declarse como instancia de clases mediante el mecanismoderiving
. Ejemplo de clases derivadas:
data Bool = False | True deriving (Eq, Ord, Read, Show)
Comprobación:
False == False == True False < True == True show False == "False" read "False" :: Bool == False
- Para derivar un tipo cuyos constructores tienen argumentos como derivado, los tipos de los argumentos tienen que ser instancias de las clases derivadas.
Ejemplo:
data Figura = Circulo Float | Rect Float Float deriving (Eq, Ord, Show)
Se cumple que
Float
es instancia deEq
,Ord
yShow
.λ> :info Float ... instance Eq Float instance Ord Float instance Show Float ...
7. Material complementario
El código del tema se encuentra en este enlace.
Este tema también se encuentra en los siguientes formatos:
- Como transparencias en PDF.
- Como libro interactivo en IHaskell sobre Jupyter.
- Como vídeos de clase: vídeo 1 y vídeo 2.
8. Bibliografía
- J. Burget The algebra (and calculus!) of algebraic data types.
- G. Hutton. Programming in Haskell. Cambridge University Press, 2007.
- Cap. 10: Declaring types and classes.
- B.C. Ruiz, F. Gutiérrez, P. Guerrero y J.E. Gallardo. Razonando con
Haskell. Thompson, 2004.
- Cap. 4: Definición de tipos.
- Cap. 5: El sistema de clases de Haskell.
- S. Thompson. Haskell: The Craft of Functional Programming, Second Edition.
Addison-Wesley, 1999.
- Cap. 12: Overloading and type classes.
- Cap. 13: Checking types.
- Cap. 14: Algebraic types.
- Wikibook. Haskell.
- Algebraic data types.