Responsabili laborator:
Scopul laboratorului:
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ă.
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:
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) x /= y = not (x == y)
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
:
data Color = Red | Green | Blue instance Eq Color where Red == Red = True Green == Green = True Blue == Blue = True _ == _ = False
Am redefinit ==
, iar definiția lui /=
rămâne neschimbată (x /= y = not (x == y)
), ceea ce e suficient ca să folosim ambii operatori.
*Main> Red == Red True *Main> Red /= Red False
deriving
, i.e.data Color = Red | Green | Blue deriving (Eq)
.
În laboratoarele trecute, analizând tipurile unor expresii, ați întâlnit notații asemănătoare:
Prelude> :t elem elem :: (Eq a) => a -> [a] -> Bool
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).
=>
.
*Main> :t (\x -> x == 0) (\x -> x == 0) :: (Eq a, Num a) => a -> Bool
O definiție posibilă a funcției elem
este:
elem :: (Eq a) => a -> [a] -> Bool elem _ [] = False elem e (x:xs) = e == x || elem e xs
Ceea ce înseamnă că, odată înrolat tipul nostru clasei Eq
, putem folosi, printre altele, și functia elem
:
Prelude> elem Red [Blue, Green, Green, Red, Blue] True
Să considerăm tipul de date polimorfic Either
:
data Either a b = Left a | Right b
Ț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.
show :: (Show a) => a -> String
Pentru a înrola tipul Either
în clasa Show
, vom folosi sintaxa:
instance (Show a, Show b) => Show (Either a b) where show (Left x) = "Left " ++ show x show (Right y) = "Right " ++ show y
(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.
1. Fie urmatorul TDA:
data Result a = Value a | Error
ale carui valori pot fi folosite pentru a modela conceptul de eroare (exceptie).
Definiti pentru tipul:
data FIFO a = P [a] [a]
operatiile:
push :: a -> FIFO a -> Result (FIFO a) pop :: FIFO a -> Result (FIFO a) mpop :: Integer -> FIFO a -> Result (FIFO a) top :: FIFO a -> Result a
care trateaza cazurile in care top
, pop
si mpop
produc eroare.
Hint: Pentru mpop
, folositi case si guards.
2. Inrolati FIFO a
in clasa Show
3. Definiti un tip de date asociat urmatoarei gramatici:
<expr> ::= <value> | <variable> | <expr> + <expr> | <expr> * <expr>
unde o valoare poate avea orice tip.
4. Consideram urmatorul constructor de tip:
type Dictionary a = [(String,a)]
care modeleaza dictionare - mapari de tip “nume-variabila”-“valoare polimorfica”
Definiti functia:
valueof :: Dictionary a -> String -> Result a
care intoarce valoarea asociata unui nume-variabila, dintr-un dictionar
5. Definiti urmatoarea clasa:
class Eval t a where eval :: Dictionary a -> t a -> Result a
Spre deosebire de clasele prezentate in exemplele anterioare, care desemneaza o proprietate a unui tip sau constructor de tip, Eval
stabileste o relatie intre un constructor de tip t
si un tip a
. Relatia spune ca orice container de tip t a
poate fi evaluat in prezenta unui dictionar cu valori de tip a
, la o valoare de tip Result a
.
6. Inrolati Expr
si Integer
in clasa Eval
. Care este semnificatia evaluarii?
7. Inrolati Expr
si FIFO a
in clasa Eval
. Semnificatia inmultirii este concatenarea a doua FIFO.
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:
Prelude> show (Cons 1 (Cons 2 (Cons 3 Nil))) [1,2,3]
(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
.