class: center, middle # Programació Funcional en Haskell
# Classes de tipus
## Jordi Petit Departament de Ciències de la Computació
Universitat Politècnica de Catalunya --- # Classes de tipus Una **classe de tipus** (*type class*) és una interfície que defineix un comportament. Els tipus poden **instanciar** (implementar seguint la interfície) una o més classes de tipus. La instanciació es pot fer - automàticament pel compilador per a certes classes predefinides, o - a mà. Les classes de tipus - són la forma de tenir sobrecàrrega en Haskell, i - propocionen una altra forma de polimorfisme.
⚠️ Les classes de tipus de Haskell no són classes de OOP com a C++ o Java (més aviat són com els `interface`s de Java). --- # La classe `Eq` La funció `elem` necessita comparar elements per igualtat: ```haskell elem :: (Eq a) => a -> [a] -> Bool elem x [] = False elem x (y:ys) = x == y || elem x ys ``` La declaració `(Eq a) =>` indica que els tipus `a` sobre els quals es pot aplicar la funció `elem` han de ser instàncies de la classe `Eq`. La classe predefinida `Eq` dóna operacions d'igualtat i desigualtat: ```haskell class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool ``` I fins i tot ja proporciona definicions per defecte (circulars, què hi farem!): ```haskell class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) x /= y = not (x == y) ``` --- # La classe `Eq` El nostre tipus `Jugada` (encara) no dóna suport a la classe `Eq`: ```haskell data Jugada = Pedra | Paper | Tisora λ> Paper /= Paper 💣 error: "No instance for (Eq Jugada) arising from a use of ‘/=’" λ> Pedra `elem` [Paper, Pedra, Paper] 💣 error: "No instance for (Eq Jugada) arising from a use of ‘elem’" ``` Amb `deriving (Eq)` demanem al compilador que instancïi automàticament la classe `Eq` (usant igualtat estructural): ```haskell data Jugada = Pedra | Paper | Tisora deriving (Eq) λ> Paper /= Paper 👉 False λ> Pedra `elem` [Paper, Pedra, Paper] 👉 True ``` --- # La classe `Eq` Per alguns tipus, la igualtat estructural no és suficient: ```haskell data Racional = Racional Int Int -- numerador, denominador deriving (Eq) λ> Racional 3 2 == Racional 6 4 👎 False ``` En aquests casos cal instanciar la classe a mà: ```haskell instance Eq Racional where (Racional n1 d1) == (Racional n2 d2) = n1 * d2 == n2 * d1 λ> Racional 3 2 == Racional 6 4 👍 True λ> Racional 3 2 /= Racional 6 4 👍 False ``` Només cal definir `==` perquè la definició per defecte de `/=` ja ens convé. --- # La classe `Eq` Per alguns tipus, instanciar una classe també requereix alguna altra classe: ```haskell data Arbin a = Buit | Node a (Arbin a) (Arbin a) instance Eq a => Eq (Arbin a) where Buit == Buit = True (Node x1 fe1 fd1) == (Node x2 fe2 fd2) = x1 == x2 && fe1 == fe2 && fd1 == fd2 _ == _ = False ``` --- # Informació sobre instàncies Amb la comanda `:info T` (o `:i T`) de l'intèrpret es pot veure de quines classes és instància un tipus `T`: ```haskell λ> :i Racional data Racional = Racional Int Int *instance Eq Racional λ> :i Int data Int = GHC.Types.I# GHC.Prim.Int# *instance Eq Int instance Ord Int instance Show Int instance Read Int instance Enum Int instance Num Int instance Real Int instance Bounded Int instance Integral Int ``` --- # La classe `Ord` La classe predefinida `Ord` (que requereix la classe `Eq`) dóna operacions d'ordre: ```haskell data Ordering = LT | EQ | GT -- possibles resultats d'una comparació d'ordre class (Eq a) => Ord a where compare :: a -> a -> Ordering (<), (<=), (>=), (>) :: a -> a -> Bool max, min :: a -> a -> a compare x y | x == y = EQ | x <= y = LT | otherwise = GT x < y = compare x y == LT x > y = compare x y == GT x <= y = compare x y /= GT x >= y = compare x y /= LT ``` El mínim que cal per fer la instanciació és definir el `<=` o el `compare`. Tot i que no es verifica, s'espera que les instàncies d'`Ord` compleixin aquestes lleis: - Transitivitat: si `x <= y && y <= z` llavors `x <= z`. - Reflexivitat: `x <= x`. - Antisimetria: si `x <= y && y <= x` llavors `x == y`. --- # La classe `Show` La classe predefinida `Show` dóna suport per convertir valors en textos: ```haskell class Show a where show :: a -> String ``` Amb `deriving (Show)`, el compilador la ofereix automàticament (usant sintàxi Haskell): ```haskell data Racional = Racional Int Int -- numerador, denominador deriving (Eq, Show) λ> show $ Racional 3 2 👉 "Racional 3 2" λ> show $ Racional 6 4 👉 "Racional 6 4" 💔 ``` Alternativament, per fer la instanciació a mà només cal definir el `show`: ```haskell instance Show Racional where show (Racional n d) = (show $ div n m) ++ "/" ++ (show $ div d m) where m = gcd n d λ> show $ Racional 3 2 👉 "3/2" λ> show $ Racional 6 4 👉 "3/2" 💖 ``` --- # La classe `Read` La classe predefinida `Read` dóna suport per convertir textos en valors: ```haskell class Read a where read :: String -> a ``` Amb `deriving (Read)`, el compilador la ofereix automàticament (usant sintàxi Haskell). Alternativament, per fer la instanciació a mà cal definir el `readPrec`, que forma part dels *parsers* interns de Haskell. **Compte:** Al usar `read`, sovint cal especificar el tipus de retorn, perquè el compilador sàpiga a quin de tots els `read`s sobrecarregats ens referim: ```haskell λ> read "38" 💣 "Exception: Prelude.read: no parse" λ> (read "38") :: Int 👉 38 λ> (read "38") :: Integer 👉 38 λ> (read "38") :: Float 👉 38.0 ``` --- # La classe `Num` La classe predefinida `Num` dóna suport a operadors aritmètics bàsics: ```haskell class (Eq a, Show a) => Num a where (+), (-), (*) :: a -> a -> a negate, abs, signum :: a -> a fromInteger :: Integer -> a x - y = x + negate y negate x = 0 -x ``` Per fer la instanciació cal definir totes les operacions menys `negate` o `-`. Els tipus `Int`, `Integer`, `Float` i `Double` són instàncies de la classe `Num`. --- # Altres classes predefinides .center[  .xxs[Imatge: https://wiki.haskell.org/Typeclassopedia] ] --- # Ús de classes en declaracions de tipus ```haskell suma [] = 0 suma (x:xs) = x + suma xs ``` Quin és el tipus de `suma`? -- ```haskell suma :: [Int] -> Int ``` -- .center[❌ més general!] -- ```haskell suma :: [a] -> a ``` -- .center[❌ el tipus `a` no pot ser qualsevol: ha de tenir l'operació `+`!] -- ```haskell suma :: Num a => [a] -> a ``` -- .center[✅ el tipus `a` ha de ser instància de `Num`!] -- Les condicions sobre les variables de tipus es posen davant de `=>` a la signatura. El sistema de tipus de Haskell és capaç d'inferir tipus i condicions automàticament.
⟹ més endavant veurem com. --- # Definició de classes pròpies Només cal utilitzar la mateixa sintàxi que ja hem vist. **Exemple:** Classe per a predicats. ```haskell class Pred a where sat :: a -> Bool unsat :: a -> Bool unsat = not . sat ``` Instanciació pels enters: ```haskell instance Pred Int where sat 0 = False sat _ = True ``` Instanciació pels arbres binaris: ```haskell instance Pred a => Pred (Arbin a) where sat Buit = True sat (Node x fe fd) = sat x && sat fe && sat fd ```