Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Clase de tipuri (Type-classes) ====== Scopul laboratorului: * Familiarizarea studenților cu tipuri de clase * Familiarizarea studenților cu constrângeri de tip ===== Typeclasses ===== O clasă de tipuri (typeclass) este un fel de interfață care definește un comportament. Dacă un tip de date face parte dintr-o anume clasă de tipuri, atunci putem lucra pe tipul respectiv cu operațiile definite în clasă. <note hint> Conceptul de clasă de tipuri este diferit de conceptul de clasă din programarea orientată pe obiecte. O comparație mai pertinentă este între clasele de tip din Haskell și interfețele din Java. </note> O clasă des întâlnită este clasa ''Eq''. Orice tip care este o instanță a acestei clase poate fi comparat, utilizând funcțiile ''=='' și ''/=''. Clasa ''Eq'' este definită mai jos. Observați sintaxa Haskell: <code haskell> class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) x /= y = not (x == y) </code> Observăm că definițiile pentru ''=='' și ''/='' depind una de cealaltă. Acest lucru ne ușurează munca atunci când vrem să înrolăm un tip acestei clase, pentru că trebuie să redefinim doar unul dintre cei doi operatori. Pentru a vedea cum lucrăm cu clase, vom defini un tip de date simplu și îl vom înrola în ''Eq'': <code haskell> data Color = Red | Green | Blue instance Eq Color where Red == Red = True Green == Green = True Blue == Blue = True _ == _ = False </code> Am redefinit ''=='', iar definiția lui ''/='' rămâne neschimbată (''x /= y = not (x == y)''), ceea ce e suficient ca să folosim ambii operatori. <code> *Main> Red == Red True *Main> Red /= Red False </code> <note> În acest caz, puteam obține același comportament folosind implementarea default oferită de cuvântul cheie ''deriving'', i.e.\\ ''data Color = Red | Green | Blue deriving (Eq)''. </note> ===== Constrângeri de clasă ===== În laboratoarele trecute, analizând tipurile unor expresii, ați întâlnit notații asemănătoare: <code> Prelude> :t elem elem :: (Eq a) => a -> [a] -> Bool </code> Partea de după ''=%%>%%'' ne spune că funcția ''elem'' primește un element de un tip oarecare, ''a'' și o listă cu elemente de același tip și întoarce o valoare booleană. ''(Eq a)'' precizează că tipul de date ''a'' trebuie să fie o instanță a clasei ''Eq'' și nu orice tip. De aceea se numește o **constrângere de clasă** (class constraint). <note> Toate constrângerile de clasă sunt trecute într-un tuplu, înaintea definiției funcției, separate de ''=%%>%%''. <code> *Main> :t (\x -> x == 0) (\x -> x == 0) :: (Eq a, Num a) => a -> Bool </code> </note> O definiție posibilă a funcției ''elem'' este: <code haskell> elem :: (Eq a) => a -> [a] -> Bool elem _ [] = False elem e (x:xs) = e == x || elem e xs </code> Ceea ce înseamnă că, odată înrolat tipul nostru clasei ''Eq'', putem folosi, printre altele, și functia ''elem'': <code> Prelude> elem Red [Blue, Green, Green, Red, Blue] True </code> ===== Clase de tipuri și tipuri de date polimorfice ===== Să considerăm tipul de date polimorfic ''Either'': <code haskell> data Either a b = Left a | Right b </code> Țineți minte că ''Either'' nu este un tip, ci un //constructor de tip//. ''Either Int String'', ''Either Char Bool'' etc. sunt tipuri propriu-zise. O clasă utilă de tipuri este clasa ''Show'', care oferă funcția ''show'' care primește un parametru și întoarce reprezentarea acestuia sub formă de șir de caractere. <code haskell> show :: (Show a) => a -> String </code> Pentru a înrola tipul ''Either'' în clasa ''Show'', vom folosi sintaxa: <code haskell> instance (Show a, Show b) => Show (Either a b) where show (Left x) = "Left " ++ show x show (Right y) = "Right " ++ show y </code> <note important> Observați constrângerile de clasă ''(Show a, Show b)''! În implementarea funcției ''show'' pentru tipul ''Either a b'', folosim aplicațiile ''show x'' și ''show y'', deci aceste elemente trebuie să aibă, la rândul lor, un tip înrolat în clasa Show. </note> ===== Informații despre clase în ghci ===== Din cadrul ghci, puteți obține informații despre o clasă anume folosind comanda '':info <typeclass>'' ('':i <typeclass>''): <code> Prelude> :info Ord class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool max :: a -> a -> a min :: a -> a -> a {-# MINIMAL compare | (<=) #-} -- Defined in ‘GHC.Classes’ instance Ord a => Ord [a] -- Defined in ‘GHC.Classes’ instance Ord Word -- Defined in ‘GHC.Classes’ instance Ord Ordering -- Defined in ‘GHC.Classes’ instance Ord Int -- Defined in ‘GHC.Classes’ ... </code> Putem observa mai multe informații utile: * constrângerea de clasă ''Eq a =%%>%%'' arată că un tip de date trebuie să fie membru al clasei ''Eq'' pentru a putea fi membru al clasei ''Ord''. * putem observa toate funcțiile și operatorii oferiți de clasa ''Ord'': ''compare'', ''<'', ''%%<%%='' etc. * linia ''{-# MINIMAL compare | (%%<%%= ) #-}'' ne informează că e suficient să implementăm fie funcția ''compare'', fie operatorul ''%%<%%='', pentru a putea utiliza toate funcțiile puse la dispoziție de clasa ''Ord'' (amintiți-vă de clasa ''Eq'' și cum ''/='' rămânea definit în funcție de ''==''). * linia ''-- Defined in ‘GHC.Classes’'' indică locul în care această clasă e definită. O căutare a numelui ne duce [[https://github.com/ghc/ghc/blob/master/libraries/ghc-prim/GHC/Classes.hs#L273|aici]], unde putem observa implementarea clasei, exact așa cum este folosită în ghc. * următoarele linii reprezintă o înșirare a tuturor tipurilor de date despre care ghci știe că sunt înrolate în clasa ''Ord'', precum și unde se găsește această înrolare. E.g. prima linie arată că listele ce conțin elemente ordonabile sunt și ele ordonabile, comportament definit în ''GHC.Classes'': https://github.com/ghc/ghc/blob/master/libraries/ghc-prim/GHC/Classes.hs#L330 ===== Exerciții ===== 1. În [[pp:l04|laboratorul anterior]], ați definit un tip pentru a modela numerele naturale extinse cu un punct la infinit, precum și niște operații pe acestea. Dorim să facem implementarea elegantă, pentru a putea folosi operatori deja existenți (e.g. ''=='' pentru comparare) și pentru a putea folosi alte funcții existente care impun constrângeri de tip (e.g. ''Data.List.sort :: Ord a =%%>%% [a] -%%>%% [a]''). Astfel, ne dorim să înrolăm tipul de date în următoarele clase: * Show (astfel încât să afișăm numerele fără a fi precedate de vreun nume de constructor, iar infinitul ca "Inf") * Eq * Ord * Num 2. Definiți un tip de date asociat următoarei gramatici: <code> <expr> ::= <value> | <variable> | <expr> + <expr> | <expr> * <expr> </code> unde o valoare poate avea orice tip. 3. Considerăm următorul constructor de tip: <code haskell> type Dictionary a = [(String, a)] </code> care modeleaza //dicționare// - mapări de tip "nume-variabilă"-"valoare polimorfica" Definiți funcția: <code>valueof :: Dictionary a -> String -> Maybe a</code> care intoarce valoarea asociata unui nume-variabilă, dintr-un dicționar 4. Definiți următoarea clasă: <code haskell> class Eval t a where eval :: Dictionary a -> t a -> Maybe a </code> Spre deosebire de clasele prezentate în exemplele anterioare, care desemnează o //proprietate// a unui tip sau constructor de tip, ''Eval'' stabilește o **relație** între un constructor de tip ''t'' și un tip ''a''. Relația spune că orice container de tip ''t a'' poate fi evaluat in prezența unui dicționar cu valori de tip ''a'', la o valoare de tip ''Maybe a''. <note> Acest tip de clasă reprezintă o extensie a limbajului, **Multi-parameter type-class**. Cel mai probabil este nevoie de următoarele directive pentru a o defini și pentru a înrola tipuri la ea: <code haskell> {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} class Eval t a where eval :: Dictionary a -> t a -> Result a </code> Mai multe despre acestea, precum și despre **Functional Dependencies**, un alt feature care este strâns legat de clasele de tipuri cu mai mulți parametrii puteți găsi [[https://en.wikibooks.org/wiki/Haskell/Advanced_type_classes|aici]]. </note> 5. Înrolați ''Expr'' și ''Integer'' în clasa ''Eval''. Care este semnificația evaluării? 6. Înrolați ''Expr'' și ''FIFO a'' în clasa ''Eval''. Semnificația înmulțirii este //concatenarea// a două FIFO. ===== Alte exerciții ===== 1. Ați definit, în laboratorul anterior, tipurile polimorfice ''List a'' și ''Tree a''. Pentru le putea reprezenta, ați folosit implementarea implicită a funcției ''show'', oferită de ''deriving (Show)''. Aceasta nu era însă o reprezentare citibilă. Ne dorim să reprezentăm tipul listă, la fel ca cel existent în Haskell: <code> Prelude> show (Cons 1 (Cons 2 (Cons 3 Nil))) [1,2,3] </code> (Pentru arbori există multe reprezentări posibile, puteți alege orice reprezentare preferați). 2. Înrolați aceleași tipuri în clasa ''Eq''. 3. Implementați sortarea pentru ''List a'', unde ''a'' e un tip oarecare înrolat în clasa ''Ord''. 4. Implementați căutarea binară pentru ''Tree a'', unde ''a'' e un tip oarecare înrolat în clasa ''Ord''. 5. Înrolați tipurile de date ''List'' și ''Tree'' în clasa ''Functor''. ===== Resurse ===== * [[https://www.haskell.org/tutorial/classes.html|Type Classes and Overloading - Haskell wiki]] * [[https://wiki.haskell.org/Typeclassopedia|Typeclassopedia - Haskell wiki]] * [[http://learnyouahaskell.com/types-and-typeclasses#typeclasses-101|Typeclasses 101 - Learnyouahaskell]] * [[http://learnyouahaskell.com/making-our-own-types-and-typeclasses#typeclasses-102|Typeclasses 102 - Learnyouahaskell]]