Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
pp:l05 [2017/03/28 11:59]
ccalin [Exerciții]
pp:l05 [2020/02/05 15:50] (current)
dmihai [Exerciții]
Line 1: Line 1:
-====== Clase de tipuri (Type-classes) ======+====== Clase de tipuri (Typeclasses) ======
  
 Scopul laboratorului:​ Scopul laboratorului:​
-  * Familiarizarea studenților cu tipuri de clase +  * introducerea conceptului de clase de tipuri 
-  * Familiarizarea studenților cu constrângeri ​de tip+  * introducerea conceptului ​de constrângeri de tip 
 +  * clarificarea distincției dintre "​polimorfism parametric"​ și "​polimorfism ad hoc" 
 +  * înrolarea tipurilor în clase, în Haskell 
 +  * definirea propriilor clase în Haskell 
 + 
 +===== Polimorfism ===== 
 + 
 +Polimorfismul constă în ideea de a oferi //o interfață comună// pentru //​entități ​de tipuri diferite//. Interfața poate fi o funcție sau un simbol (amintiți-vă de constantele polimorfice). Comportamentul din spatele acestei interfețe poate să fie același, sau poate varia în funcție de tipul pe care operează. Ne interesează două feluri importante de polimorfism:​ **polimorfism parametric** și **polimorfism ad hoc**. 
 + 
 +==== Polimorfism parametric ==== 
 + 
 +Pentru polimorfism parametric, există //un singur comportament//​ pentru toate tipurile. Polimorfismul parametric este util atunci când dorim abstractizarea unui tip, neavând nevoie de operații specifice pe acesta, sau de a-i cunoaște structura. Multe dintre funcțiile întâlnite până acum în Haskell prezintă polimorfism parametric. De exemplu, o implementare posibilă pentru lista ''​length''​ este următoarea:​ 
 + 
 +<code haskell>​ 
 +length :: [a] -> Int 
 +length [] = 0 
 +length (_:xs) = 1 + length xs 
 +</​code>​ 
 + 
 +Observați că tipul elementelor din listă este irelevant și nu aplicăm vreo funcție pe aceste elemente (nici nu le legăm la un nume). Tipurile concrete ce pot fi conținute într-o listă sunt abstractizate în semnătura de tip a lui ''​length'',​ prin variabila de tip ''​a''​. Astfel putem apela funcția ''​length''​ pe orice tip concret ca: ''​[Int]'',​ ''​[Char]'',​ ''​[Float]'',​ ''​[%%[%%Int%%]%%]''​ etc., implementarea fiind aceeași. 
 + 
 +În Java, polimorfismul parametric este realizat prin [[https://​en.wikipedia.org/​wiki/​Generics_in_Java|genericitate]]. 
 + 
 +==== Polimorfism ad hoc ==== 
 + 
 +Pentru polimorfism ad hoc, există //multiple comportamente//​. Un comportament concret este selectat pe baza tipurilor pe care se operează. În Java, acest lucru se poate obține prin "​overloading":​ definirea multiplor funcții cu același nume, dar tipuri diferite pentru parametrii:​ 
 + 
 +<code java> 
 +// File: Main.java 
 +public class Main { 
 +    static void function(int a) { 
 +        System.out.println("​int function!"​);​ 
 +    } 
 + 
 +    static void function(double a) { 
 +        System.out.println("​double function!"​);​ 
 +    } 
 + 
 +    public static void main(String[] args) { 
 +        function(3.0);​ 
 +        function(3);​ 
 +    } 
 +
 +</​code>​ 
 + 
 +<​code>​ 
 +$ javac Main.java && java Main 
 +double function! 
 +int function! 
 +</​code>​ 
 + 
 +Compilatorul poate distinge între cele două funcții pe baza tipurilor prezente în semnătura lor și poate alege în mod corect funcția apelată pe baza tipului argumentului din apelul funcției. 
 + 
 +Această formă de overloading nu este posibilă în Haskell, în mod direct; i.e. astfel de definiție nu va compila, pentru că nu putem avea două funcții distincte cu același nume: 
 + 
 +<code haskell>​ 
 +function :: Int -> String 
 +function _ = "int function!"​ 
 + 
 +function :: Double -> String 
 +function _ = "​double function!"​ 
 +</​code>​ 
 + 
 +<​code>​ 
 +Prelude> :l Main.hs 
 +[1 of 1] Compiling Main             ( Main.hs, interpreted ) 
 + 
 +test.hs:​4:​1:​ error: 
 +    Duplicate type signatures for ‘function’ 
 +[...] 
 +</​code>​ 
 + 
 +Polimorfismul ad hoc se realizează în Haskell cu ajutorul //claselor de tipuri//.
  
 ===== Typeclasses ===== ===== 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 tipuriatunci putem lucra pe tipul respectiv cu operațiile definite în clasă.+**clasă de tipuri** (**typeclass**) este o colecție ​de funcții care specifică un comportament. ​Un tip de date poate fi //​înrolat//​ într-o clasă, ​furnizând implementări ale funcțiilor, particularizate pentru acest tip
  
-<​note ​hint+<​note ​important
-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.+În ciuda numelui, clasele Haskell nu corespund claselor din limbaje ​de programare orientate ​pe obiecte ​(e.g. Java, C%%+%%%%+%%). Clasele Haskell //nu conțin date// și //nu pot fi instanțiate//​. O comparație mai pertinentă este cu interfețele din Java.
 </​note>​ </​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:+Una dintre cele mai comune clase Haskell ​este ''​Eq''​: clasa tipurilor ale căror valori pot fi comparate pentru egalitateAceasta conține doar două funcții: ''​==''​ și ''/​=''​. ​O posibilă definiție a acesteia (observați sintaxa Haskell):
  
 <code haskell> <code haskell>
Line 23: Line 95:
 </​code>​ </​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+''​a''​ este o variabilă de tip cu rol de placeholder pentru tipuri concrete care pot fi înrolate in ''​Eq''​ și are //acceași semnificație în tot scope-ul clasei//; i.e. ''​a''​-ul din semnătura funcțiilor se referă la același tip. De exemplu, pentru înrolarea tipului ''​Int''​ la clasa ''​Eq'',​ funcția ''​==''​ va avea tipul ''​Int -%%>%% Int -%%>%% Bool''​. 
-Pentru a vedea cum lucrăm cu clase, vom defini un tip de date simplu și îl vom înrola în ''​Eq'':​+ 
 +Spre deosebire de interfețele din Java ce oferă doar //​declarații//​ de funcții, clasele de tipuri pot specifica și //​definiții//​. ​Observăm că, în mod bizar, ​definițiile ​funcțiilor ​''​==''​ și ''/​=''​ depind una de cealaltă. ​Acesta este un mecanism care ne ușurează munca atunci când vrem să înrolăm un tip acestei clase: este suficient să suprascriem definiția unei singure funcțiicealaltă păstrându-și implementarea default
 + 
 +Vom defini un tip simplu și îl vom înrola în ''​Eq'':​
  
 <code haskell> <code haskell>
Line 33: Line 108:
     Green == Green = True     Green == Green = True
     Blue == Blue = True     Blue == Blue = True
 +    -- observați cum "​_"​ ne salvează de la a adăuga explicit încă 6 cazuri
     _ == _ = False     _ == _ = False
 </​code>​ </​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.+Am redefinit ''​=='',​ iar definiția lui ''/​=''​ rămâne neschimbată (''​x /= y = not (x == y)''​); acum putem folosi ​ambii operatori ​cu valori de tip ''​Color'':​
  
 <​code>​ <​code>​
Line 43: Line 119:
 *Main> Red /= Red *Main> Red /= Red
 False False
 +*Main> Red == Blue
 +False
 +*Main> Red /= Blue
 +True
 +</​code>​
 +
 +O altă clasă des întâlnită este ''​Show'',​ care ne oferă o funcție ''​show''​ pentru a obține o reprezentare sub formă de șir de caractere a unei valori (similar cu ''​toString''​ din Java):
 +
 +<code haskell>
 +class Show a where
 +    show :: a -> String
 +    ​
 +instance Show Color where
 +    show Red = "​Red"​
 +    show Green = "​Green"​
 +    show Blue = "​Blue"​
 +</​code>​
 +
 +==== Cuvântul cheie "​deriving"​ ====
 +
 +Observați că, înrolând tipul ''​Color''​ în clasele ''​Eq''​ și ''​Show'',​ definițiile oferit pentru ''​==''​ și ''​show''​ sunt simple și evidente (e.g. pentru ''​=='',​ orice constructor e egal cu el însuși și diferit de ceilalți). Astfel de implementări pot fi derivate automat, fără a le explicita; pentru asta, Haskell ne oferă cuvântul cheie ''​deriving''​. Adăugat la sfârșitul unei definiții de tip, acesta poate fi urmat de una sau mai multe clase (într-un tuplu) în care tipul de date este înrolat cu o implementare simplistă:
 +
 +<​code>​
 +Prelude> data Color = Red | Green | Blue deriving (Eq, Show)
 +Prelude> Red == Red
 +True
 +Prelude> Red == Green
 +False
 +Prelude> Red
 +Red
 </​code>​ </​code>​
  
 <​note>​ <​note>​
-În acest caz, puteam obține același comportament folosind implementarea default oferită de cuvântul cheie ''​deriving'', ​i.e.\\ +Mai multe informații despre ce clase pot fi derivate automat ​și cum se realizează această derivare găsițaici: 
-''​data Color = Red | Green | Blue deriving (Eq)''​.+ 
 +[[https://​www.haskell.org/​onlinereport/​decls.html#​derived-decls|Haskell Report - 4.3.3 Derived Instances]]\\ 
 +[[https://​www.haskell.org/​onlinereport/​derived.html|Haksell Report - 10 Specification of Derived Instances]]
 </​note>​ </​note>​
  
-===== Constrângeri de clasă ​=====+===== Constrângeri de tip =====
  
-În laboratoarele ​trecute, ​analizând tipurile unor expresii, ​ați întâlnit notații asemănătoare:+Amintiți-vă,​ din laboratorele ​trecute, ​de funcția ''​elem'':​ aceasta primea un element și o listă și întorcea o valoare booleană care ne spunea dacă elementul se găsește în listă; deducem tipul ei ca fiind ''​-%%>%% [a] -%%>%% Bool''​. 
 + 
 +O definiție posibilă a funcției ''​elem''​ este: 
 + 
 +<code haskell>​ 
 +elem _ [] = False 
 +elem e (x:xs) = e == x || elem e xs 
 +</​code>​ 
 + 
 +Observăm că, pe cel de-al doilea caz, aplicăm funcția ''​==''​ între elementul căutat ''​e''​ șcapul listei ''​x''​. Dar funcția ''​==''​ este parte a clasei ''​Eq'',​ deci valorile primite ca argumente trebuie să aparțină unui tip înrolat în ''​Eq'';​ ceea ce înseamnă că tipul ''​a''​ din semnătura funcției ''​elem'',​ trebuie să fie înrolat în ''​Eq''​. Aceasta este o **constrângere de tip** (**type-constraint**) și este exprimată în Haskell prin următoarea construcție sintactică:
  
 <​code>​ <​code>​
Line 59: Line 176:
 </​code>​ </​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ă, în semnătura de tip care urmează, variabila ​''​a'' ​nu se poate referi chiar la orice tip, ci doar la cele înrolate în ''​Eq''​.
- +
-''​(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>​ <​note>​
-Toate constrângerile de clasă ​sunt trecute într-un tuplu, ​înaintea definiției funcției, separate ​de ''​=%%>​%%''​.+Toate constrângerile de tip sunt trecute într-un tuplu, ​urmat de ''​=%%>​%%''​, apoi de tipul funcției:
  
 <​code>​ <​code>​
-*Main> :t (\x -> x == 0) +*Main> :t (\x -> x == 0 && y < 0) 
-(\x -> x == 0) :: (Eq a, Num a) => a -> Bool+(\x -> x == 0 && y == 0) :: (Eq a, Ord b, Num a, Num b) => a -> b -> Bool
 </​code>​ </​code>​
 </​note>​ </​note>​
  
-O definiție posibilă funcției ''​elem''​ este:+Odată înrolat un tip într-o clasă, putem folosi toate funcțiile care au acea clasă printre constrângeri:
  
-<​code ​haskell>+<​code>​ 
 +*Main> :t elem
 elem :: (Eq a) => a -> [a] -> Bool elem :: (Eq a) => a -> [a] -> Bool
-elem [] = False +*Main> ​elem Red [Blue, Green, Green, Red, Blue
-elem e (x:xs) = e == x || elem e xs+True 
 +*Main> ​:m +Data.List 
 +*Main Data.List>​ :t delete 
 +delete :: (Eq a) => a -> [a] -> [a] 
 +*Main Data.List>​ delete Green [Blue, Green, Green, Red, Blue] 
 +[Blue, Red, Blue]
 </​code>​ </​code>​
  
-Ceea ce înseamnă că, odată înrolat tipul nostru clasei ''​Eq'',​ putem folosi, printre altele, și functia ''​elem'':​+===== Înrolarea tipurilor polimorfice =====
  
-<​code>​ +Să ne reamintin tipul de listă polimorfică din laboratorul trecut: 
-Prelude> elem Red [Blue, Green, Green, Red, Blue] + 
-True+<​code ​haskell
 +data List a = Empty | Cons a (List a)
 </​code>​ </​code>​
  
 +Deși am putea folosi mecanismul de ''​deriving'',​ în scop pedagogic vom înrola manual lista noastră în clasa ''​Eq''​. Țineți minte că ''​List''​ nu este un tip, ci un //​constructor de tip//. Astfel, va trebui să înrolăm ''​List a''​ în clasa ''​Eq''​. Când sunt două liste egale?
  
-===== Clase de tipuri ​și tipuri de date polimorfice =====+  * două liste goale sunt egale între ele 
 +  * două liste cu cel puțin un element sunt egale dacă atât capetele lor, cât și cozile sunt egale
  
-Să considerăm tipul de date polimorfic ​''​Either'':​+<code haskell>​ 
 +eqLists Empty Empty = True 
 +eqLists (Cons a as) (Cons b bs) = (a == b) && (eqLists as bs) 
 +</​code>​ 
 + 
 +În cel de-al doilea caz, utilizăm funcția ''​==''​ pentru a compara capetele listelor, deci tipul ''​eqLists''​ trebuie să conțină o constrângere ​de tip: ''​eqLists :: (Eq a) -%%>%% List a -%%>%% List a -%%>%% Bool''​. Scopul nostru final este de a înrola ''​List a''​ în ''​Eq''​ și a folosi chiar ''​==''​ în loc de ''​eqLists'';​ pentru a face asta, avem nevoie de //o constrângere de tip la nivel de clasă//:
  
 <code haskell> <code haskell>
-data Either ​a b = Left | Right b+instance (Eq a) => Eq (List a) where 
 +    Empty == Empty = True 
 +    (Cons a as) == (Cons bs) (== b) && (as == bs) 
 +    _ == _ = False
 </​code>​ </​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.+Primul rând exprimă ideea că putem egala două liste //de același ​tip// (''​List a''​)doar dacă //​elementele lor sunt egalabile// (''​(Eq a) =%%>%%''​).
  
-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.+Observați că, în expresia celui de-al doilea caz, funcția ​''​=='' ​apare de două ori: o dată pentru a compara elementeo dată pentru a compara liste. Acesta este un exemplu de **polimorfism ad hoc**. Cele două apeluri de ''​=='' ​pot avea implementări diferite (al doilea este mereu un apel recursiv; primul poate fi, de exemplu, comparație între doi întregi, în cazul ''​List Int''​).
  
-<​code ​haskell+<​note>​ 
-show :: (Show a) => a -> String+Cuvântul cheie ''​deriving''​ se descurcă și cu înrolarea de tipuri polimorfice,​ fiind capabil să genereze această definiție cu tot cu constrângerea ca și elementele să fie parte din ''​Eq''​. 
 +</​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 -> 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>​ </​code>​
  
 +Putem observa mai multe informații utile:
  
-Pentru ​înrola tipul ''​Either''​ în clasa ''​Show'', ​vom folosi sintaxa:+  * constrângerea de tip ''​Eq ​=%%>​%%''​ arată că un tip de date trebuie să fie membru al clasei ''​Eq''​ pentru a putea fi membru al clasei ''​Ord''​. 
 +  * toate funcțiile oferite 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#​L337|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#​L394 
 + 
 +===== Legi pentru clase ===== 
 + 
 +Pe lângă nume, constrângeri,​ tipurile și implementările default ale funcțiilor,​ mai există o caracteristică a claselor de tip: legile respectate de funcții.  
 +Având cunoștiințe de matematică,​ s-ar putea să vă așteptați ca funcția ''​==''​ să aibă anumite proprietăți (e.g. reflexivitate:​ pentru orice valoare ''​x'',​ ''​x == x''​ să fie ''​True''​). Într-adevăr,​ documentația claselor vine de obicei și cu un set de proprietăți care ar trebui respectate; [[https://​hackage.haskell.org/​package/​base-4.12.0.0/​docs/​Data-Eq.html|aici]] puteți observa cele //cinci// legi ale clasei ''​Eq''​. 
 + 
 +Din păcate, aceste legi nu pot fi specificate la nivel de limbaj. Amintiți-vă de înrolarea tipului ​''​Color''​ în clasa ''​Eq''​. Având control total asupra implementăriiam putea scrie:
  
 <code haskell> <code haskell>
-instance ​(Show a, Show b) => Show (Either a b) where +instance ​Eq Color where 
-    ​show (Left x) "Left " ++ show x +    ​== _ = True 
-    ​show (Right y) "Right " ++ show y+    ​_ /= _ True
 </​code>​ </​code>​
  
-<​note ​important+Astfel, specificăm că oricare două culori sunt și egale între ele și diferite. Deși o definiție contraintuitivă și inutilă, din perspectiva Haskell acest lucru este perfect ok atât din punct de vedere sintactic cât si semantic; **nu există, la nivel de limbaj, un mecanism de a specifica ce reguli trebuiesc respectate și de a asigura respectarea acestora**. Este îndatorirea programtorului de a cunoaște aceste reguli și de a se asigura că sunt respectate de tipul înrolat; similar, când definiți propriile clase, ar trebui să vă gândiți ce legi ar trebui respectate și să le documentați. Scopul legilor este de a asigura comportamente intuitive, ferite de surprize. 
-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>​ 
 +Respectarea strictă a tuturor legilor unei clase are excepții. De exemplu, pentru operațiile în virgulă mobilă, o valoare specială [[https://​en.wikipedia.org/​wiki/​NaN|NaN ​(Not Number)]] modelează rezultatul anumitor expresii cu rezultat nedefinit. Două valori ​''​NaN'' ​nu sunt considerate egale; prin urmare, ​pentru ​tipuri ca ''​Float'' ​și ''​Double''​, operația ​''​=='' ​nu respectă proprietatea de reflexivitate pe întreg domeniul: 
 + 
 +<​code>​ 
 +Prelude> a = 0/0 :: Float 
 +Prelude> a == a 
 +False 
 +</​code>​
 </​note>​ </​note>​
  
 ===== Exerciții ===== ===== Exerciții =====
  
-1. Fie urmatorul TDA+1. Înrolați tipul ''​List a''​ în clasa ''​Show'',​ astfel încât șirul rezultat să fie identic cu cel pentru listele Haskell: 
-<​code> ​data Result a = Value a | Error </​code>​ +  ​ 
-ale carui valori pot fi folosite pentru ​modela conceptul de eroare ​(exceptie).+<​code>​ 
 +*Main> Cons 1 (Cons 2 (Cons 3 Empty)) 
 +[1,2,3] 
 +</​code>​ 
 +   
 +2. În [[pp:​l04|laboratorul anterior]], ​ați definit operații ​(''​foldrT'',​ ''​mapT''​ etc.pentru lucrul cu arbori binari polimorfici. Am vrea să înrolăm acest tip la clasele corespunzătoare.
  
-Definiti pentru tipul: +a. înrolați ''​BTree a''​ în ''​Eq''​ 
-<​code>​ data FIFO = P [a] [a] </code>+ 
 +b. înrolați ''​BTree''​ în ''​Foldable''​ și definiți ''​foldr''​ cu aceeași definiție ca ''​foldrT''​ din laboratorul trecut; veți avea apoi acces la funcții ca ''​foldl'',​ ''​sum'',​ ''​minimum''​ ('':​info Foldable''​ în ''​ghci''​ pentru ​le vedea pe toate) 
 + 
 +c. înrolați ''​BTree''​ în ''​Functor''​ și definiți ''​fmap''​ cu aceeași definiție ca ''​mapT''​ din laboratorul trecut (mai multe informații ​[[https://​wiki.haskell.org/​Functor|aici]]) 
 + 
 +<note important> 
 +Atenție la tipurile ''​foldr''​ și ''​fmap'':​
  
-operatiile: 
 <​code>​ <​code>​
-push :: a -> FIFO a -> Result (FIFO a) +Prelude> :t foldr 
-pop :: FIFO a -> Result (FIFO a) +foldr :: Foldable t => (a -> -> b-> b -> t a -> b 
-mpop :: Integer -FIFO a -> Result (FIFO a) +Prelude> ​:t fmap 
-top :: FIFO a -> Result a+fmap :: Functor f =(a -> b-> f a -> f b
 </​code>​ </​code>​
  
-care trateaza cazurile in care ''​top'',​ ''​pop'' ​si ''​mpop'' ​produc eroare. +Observați că constrângerile sunt puse pe //​constructorul de tip//, deci va trebui înrolat ​''​BTree'', ​nu ''​BTree a''​
-//Hint:// Pentru ​''​mpop'', ​folositi **case** si **guards**.+</​note>​ 
 + 
 +3. Tot î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]''​). Înrolați tipul ''​Extended''​ în clasele: 
 + 
 +a. ''​Show''​ (a.î. să afișăm numerele fără vreun nume de constructoriar infinitul ca "​Inf"​) 
 + 
 +b''​Eq''​ 
 + 
 +c. ''​Ord''​ 
 + 
 +d. ''​Num''​
  
-2Inrolati ''​FIFO a''​ in clasa ''​Show''​+4Definiți un tip de date polimorfic care să modeleze următoarea gramatică (în care o valoare poate avea orice tip):
  
-3. Definiti un tip de date asociat urmatoarei gramatici: 
 <​code>​ <​code>​
    <​expr>​ ::= <​value>​ | <​variable>​ | <​expr>​ + <​expr>​ | <​expr>​ * <​expr> ​    <​expr>​ ::= <​value>​ | <​variable>​ | <​expr>​ + <​expr>​ | <​expr>​ * <​expr> ​
 </​code>​ </​code>​
-unde o valoare poate avea orice tip. 
  
-4Consideram urmatorul constructor ​de tip+5Amintiți-vă ​de tabelele asociative din laboratorul trecut. Vom defini un "​dicționar"​ ca fiind o tabelă asociativă cu șiruri de caractere drept chei: 
-<​code>​ + 
-type Dictionary a = [(String,​a)]+<​code ​haskell
 +type Dictionary a = [(String, a)]
 </​code>​ </​code>​
-care modeleaza //​dictionare//​ - mapari de tip "​nume-variabila"​-"​valoare polimorfica"​ 
  
-Definiti functia: +Ne mai interesează și funcția ​care primește o cheie și o tabelă asociativă și întoarce ​valoarea ​asociată cheiiîmpachetată într-un ''​Maybe'':​
-<​code>​valueof :: Dictionary a -> String -> Result a</​code>​ +
-care intoarce ​valoarea ​asociata unui nume-variabiladintr-un dictionar+
  
-5. Definiti urmatoarea clasa+<code haskell>​ 
-<​code>​+getValue ​:: String -> Dictionary a -> Maybe a 
 +</​code>​ 
 + 
 +Fie următoarea clasă: 
 + 
 +<​code ​haskell>
 class Eval t a where class Eval t a where
-    eval :: Dictionary a -> t a -> Result ​a+    eval :: Dictionary a -> t a -> Maybe a
 </​code>​ </​code>​
  
-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''​.+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>​ <​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:+Acest tip de clasă reprezintă o extensie a limbajului, **Multi-parameter type-class**. ​ Cel mai probabil este nevoie de următoarele directive ​//la începutul fișierului// ​pentru a o defini și pentru a înrola tipuri la ea (directivele se găsesc deja în scheletul de laborator):
  
 <code haskell> <code haskell>
Line 171: Line 367:
  
 class Eval t a where class Eval t a where
-    eval :: Dictionary a -> t a -> Result ​a+    eval :: Dictionary a -> t a -> Maybe a
 </​code>​ </​code>​
  
Line 177: Line 373:
 </​note>​ </​note>​
  
-6Inrolati ​''​Expr'' ​si ''​Integer'' ​in clasa ''​Eval''​. Care este semnificatia evaluarii+aÎnrolați ​''​Expr'' ​și ''​Integer'' ​în clasa ''​Eval''​. Care este semnificația evaluării?\\ 
- +bînrolați ''​Expr''​ și ''​Extended''​ în clasa ''​Eval''​.\\ 
-7Inrolati ''​Expr''​ si ''​FIFO a''​ in clasa ''​Eval''​. Semnificatia inmultirii este //​concatenarea//​ a doua FIFO.  +cCe observați? Cum ați putea simplifica?
- +
-===== Alte exercitii ===== +
-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''​. +
- +
-3Implementaț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 ​=====+{{:​pp:​lab5_-_schelet.zip|Lab 5 - Schelet}}\\ 
 +===== Recommended Reading ​=====
  
-  ​* [[https://​www.haskell.org/​tutorial/​classes.html|Type Classes and Overloading - Haskell wiki]] +  * [[http://​learnyouahaskell.com/​types-and-typeclasses#​typeclasses-101|Learn you a Haskell for Great Good - Chapter 2: Types and Typeclasses#​Typeclasess ​101]]\\ 
-  * [[https://​wiki.haskell.org/​Typeclassopedia|Typeclassopedia - Haskell wiki]] +  * [[http://​learnyouahaskell.com/​making-our-own-types-and-typeclasses#​typeclasses-102|Learn you a Haskell for Great Good - Chapter 8: Making Our Own Types and Typeclasses#​Typeclasess ​102]]\\ 
-  ​* [[http://​learnyouahaskell.com/​types-and-typeclasses#​typeclasses-101|Typeclasses 101 - Learnyouahaskell]] +  * [[http://​book.realworldhaskell.org/​read/​using-typeclasses.html|Real World Haskell - Chapter 6: Using Typeclasses]]\\ 
-  * [[http://​learnyouahaskell.com/​making-our-own-types-and-typeclasses#​typeclasses-102|Typeclasses 102 - Learnyouahaskell]]+  * [[https://​www.haskell.org/​tutorial/​stdclasses.html|A Gentle Introduction to Haskell - Chapter 8: Standard Haskell Classes]]