Tema V Clases de Tipos 1 Noción de clase de tipos en Haskell La noción de clase se utiliza para agrupar tipos, o tuplas de tipos, con arreglo a la disponibilidad de unas determinadas funciones, con el mismo nombre y tipos equivalentes, aunque posiblemente con métodos distintos para cada tipo de la clase. Desde otro punto de vista, la noción de clase es el concepto que utiliza Haskell para introducir sobrecarga en funciones. 2 Definición de una clase de tipos class <nombre> <par-tipo> {<par-tipo>}* where <declaración de función> {<declaración de función>}* {<definición de función>}* class Eq a where (==) (/=) :: a->a->Bool x /= y = not (x == y) 3 Funciones polimorfas De cada función que aparece en una definición de clase se dice que es polimorfa y de su nombre se dice que está sobrecargado (el mismo nombre sirve para designar funciones con distintos tipos concretos y distintos métodos de cálculo). Los métodos que aparecen en una definición de clase son métodos por defecto, aplicables sólo a aquellos tipos de la clase para los que no exista un método particular. 4 Declaración de instancia de una clase Para declarar un tipo como miembro de una clase se utiliza una declaración de instancia donde se concretan los métodos aplicables a cada una de las funciones características de la clase. instance <nom-clase> <tipo> {<tipo>}* where <def-método> {<def-método>} Si para alguna función no se da un método, se utiliza su método por defecto que deberá aparecer en la definición de la clase 5 Declaraciones de instancias (ejemplos) instance Eq True False _ Bool where == True = == False = == _ = True True False instance Eq Char where c==d = ord c == ord d instance Eq Int where (==) = primEqInt 6 Declaraciones de instancias (ejemplos) data Nat = Cero | Suc Nat instance Eq Cero Suc x _ Nat where == Cero = True == Suc y = x==y == _ = False 7 Contextos Con los nombres de clases se construyen predicados, relativos a parámetros de tipos, de la forma: <nom-clase> <par_tipo> {<par_tipo>} que indican que el parámetro de tipo (o secuencia de parámetros) debe ser una instancia de la clase citada Estos predicados (o conjunciones de ellos) se utilizan para limitar el rango de variación de algunos parámetros de tipo en las expresiones de tipo, constituyendo una declaración de contexto, que debe preceder a la expresión afectada, en la forma: (<nom-clase> <par_tipo> {<par_tipo>} {,<nom-clase> <par_tipo> {<par_tipo>}}*) => expresión 8 Uso de contextos (I) Los contexto se utilizan: En las declaraciones de funciones genéricas para exigir que determinados parámetros de tipo que dispongan de ciertas operaciones elemento_de :: (Eq a) => a -> [a] -> Bool x `elemento_de` [] = False x `elemento_de` (y:ys) = x==y||(x `elemento_de` ys) contenida_en :: (Eq a) => [a] -> [a] -> Bool [] `contenida_en` ys = True (x:xs)`contenida_en`ys = x`elemento_de`ys && xs`contenida_en`ys 9 Uso de contextos (II) En las declaraciones de instancia para tipos genéricos, p.e.: instance (Eq a) => [] == [] x:xs == y:ys _ == _ Eq [a] where = True = x==y && xs==ys = False Donde el contexto indica que para que un tipo lista [a] sea instancia de la clase Eq es necesario que lo sea el tipo a de las componentes. 10 Uso de contextos (III) instance (Eq a,Eq b) => Eq (a,b) where (x,y)==(u,v) = x==u && y==v data ArbolH a = H a | B (ArbolH a) (ArbolH a) instance (Eq a) => Eq (Arbol a) where H x == H y = x==y B x y == B u v = x==u && y==v 11 Uso de contextos (IV) En las definiciones de clases: class (Eq a) => Ord a where (<),(<=),(>=),(>) :: a -> a -> Bool max, min :: a -> a -> a x<y = x<=y && x/=y x>=y = y<=x x>y = y<x max x y | x>=y = x | y>=x = y min x y | x<=y = x | y<=x = y 12 Subclases La aparición de un contexto en una definición de clase indica que los tipos de dicha clase deben ser instancias de las clases del contexto. Esta circunstancia se suele indicar diciendo que la clase en cuestión se define como subclase de las clases del contexto, o también, que las clases del contexto son superclases de la clase, y que ésta hereda las operaciones de las otras. Sin embargo esta herencia es engañosa pues el mecanismo de instanciación no convierte automáticamente las instancias de una clase en instancias de sus superclases; sino que hay que realizar todas las instanciaciones necesarias explícitamente. 13 Subclases (ejemplo) data Mod2 = O | I instance Ord Mod2 where I<=O = False _<=_ = True instance Eq Mod2 where O==O = True I==I = True _==_ = False 14 Clases de tipos predefinidas en Hugs 15 Clase Eq class Eq a where (==), (/=) :: a -> a -> Bool -- Minimal complete definition: (==) or (/=) x == y = not (x/=y) x /= y = not (x==y) 16 Clase Ord 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 x x x <= < >= > y y y y max x y min x y | | | | x >= y otherwise x <= y otherwise = = = = compare compare compare compare = = = = x y x y x x x x y y y y /= == /= == GT LT LT GT 17 Clase Enum class Enum a where succ, pred toEnum fromEnum enumFrom enumFromThen enumFromTo enumFromThenTo :: :: :: :: :: :: :: a -> a Int -> a a -> Int a -> [a] a -> a -> [a] a -> a -> [a] a -> a -> a -> [a] ----- [n..] [n,m..] [n..m] [n,n'..m] -- Minimal complete definition: toEnum, fromEnum succ = toEnum . (1+) . fromEnum pred = toEnum . subtract 1 . fromEnum enumFrom x = map toEnum [ fromEnum x .. ] enumFromTo x y = map toEnum [ fromEnum x .. fromEnum y ] enumFromThen x y = map toEnum [ fromEnum x, fromEnum y ..] enumFromThenTo x y z = map toEnum [ fromEnum x, fromEnum y .. fromEnum z ] 18 Clase Show type ShowS = String -> String class Show a where show :: a -> String showsPrec :: Int -> a -> ShowS showList :: [a] -> ShowS -- Minimal complete definition: show or showsPrec show x = showsPrec 0 x "" showsPrec _ x s = show x ++ s showList [] = showString "[]" showList (x:xs) = showChar '[' . shows x . showl xs where showl [] = showChar ']' showl (x:xs)= showChar ','.shows x. showl xs 19 Clase Num class (Eq a, Show a) => Num a where (+), (-), (*) :: a -> a -> a negate :: a -> a abs, signum :: a -> a fromInteger :: Integer -> a fromInt :: Int -> a -- Minimal complete definition: All, -- except negate or (-) x - y = x + negate y fromInt = fromIntegral negate x = 0 – x 20 Ejercicios Defínase un tipo Conjunto a, basado en la representación de conjuntos mediante listas, junto con las operaciones contenido, union, interseccion y diferencia, propias de conjuntos, así como la igualdad de manera que se pueda utilizar el operador ‘==‘. Defínase un tipo de datos Racional, correspondiente a los números racionales, utilizando una representación binómica (numerador,denominador), junto con una función racNormal que simplifique la representación de un número racional a su forma irreducible, y las operaciones aritméticas de manera que se puedan utilizar los operadores infijos habituales ‘+’, ‘-’, ‘*’ y ‘/’ . 21