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 [2019/03/24 10:56]
dmihai [Clase de tipuri (Type-classes)]
pp:l05 [2020/02/05 15:50] (current)
dmihai [Exerciții]
Line 3: Line 3:
 Scopul laboratorului:​ Scopul laboratorului:​
   * introducerea conceptului de clase de tipuri   * introducerea conceptului de clase de tipuri
-  * introducerea conceptului de constrângeri de clasă +  * introducerea conceptului de constrângeri de tip 
-  * clarificarea distincției ​între ​"​polimorfism parametric"​ și "​polimorfism ad-hoc"+  * clarificarea distincției ​dintre ​"​polimorfism parametric"​ și "​polimorfism ad hoc"
   * înrolarea tipurilor în clase, în Haskell   * înrolarea tipurilor în clase, în Haskell
   * definirea propriilor 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 26: 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 36: 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 46: Line 119:
 *Main> Red /= Red *Main> Red /= Red
 False False
 +*Main> Red == Blue
 +False
 +*Main> Red /= Blue
 +True
 </​code>​ </​code>​
  
-<​note>​ +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): 
-În acest cazputeam ​obține ​același comportament folosind implementarea default oferită de cuvântul cheie ''​deriving''​, i.e.\\ + 
-''​data ​Color = Red Green Blue deriving (Eq)''​. +<code haskell>​ 
-</note>+class Show a where 
 +    show :: a -> String 
 +     
 +instance Show Color where 
 +    show Red "Red
 +    show Green = "​Green"​ 
 +    show Blue = "​Blue"​ 
 +</code>
  
-===== Constrângeri de clasă =====+==== Cuvântul cheie "​deriving" ​====
  
-În laboratoarele trecuteanalizând tipurile unor expresiiați întâlnit notații asemănătoare:+Observați căînrolând tipul ''​Color''​ în clasele ''​Eq''​ și ''​Show''​definițiile oferit pentru ''​==''​ ș''​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>​ <​code>​
-Prelude> ​:t elem +Prelude> ​data Color = Red | Green | Blue deriving ​(Eq, Show) 
-elem :: (Eq a) => a -[a] -> Bool+Prelude> Red == Red 
 +True 
 +PreludeRed == Green 
 +False 
 +PreludeRed 
 +Red
 </​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ă 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 ''​=%%>​%%''​.+Mai multe informații despre ce clase pot fi derivate automat și cum se realizează această derivare găsiți aici:
  
-<​code>​ +[[https://​www.haskell.org/​onlinereport/​decls.html#​derived-decls|Haskell Report ​4.3.3 Derived Instances]]\\ 
-*Main> ​:t (\x -> x == 0) +[[https://​www.haskell.org/​onlinereport/​derived.html|Haksell Report - 10 Specification of Derived Instances]]
-(\x -> x == 0) :: (Eq a, Num a) => a -> Bool +
-</code>+
 </​note>​ </​note>​
 +
 +===== Constrângeri de tip =====
 +
 +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 -%%>%% [a] -%%>%% Bool''​.
  
 O definiție posibilă a funcției ''​elem''​ este: O definiție posibilă a funcției ''​elem''​ este:
  
 <code haskell> <code haskell>
-elem :: (Eq a) => a -> [a] -> Bool 
 elem _ [] = False elem _ [] = False
 elem e (x:xs) = e == x || elem e xs elem e (x:xs) = e == x || elem e xs
 </​code>​ </​code>​
  
-Ceea ce înseamnă că, odată înrolat tipul nostru ​clasei ''​Eq'', ​putem folosi, printre altele, și functia ​''​elem'':​+Observăcă, pe cel de-al doilea caz, aplicăm funcția ''​==''​ între elementul căutat ''​e''​ și 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>​
-Prelude> elem Red [Blue, Green, Green, Red, Blue] +Prelude> ​:t elem 
-True+elem :: (Eq a) => a -> [a-> Bool
 </​code>​ </​code>​
  
 +''​(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''​.
  
-===== Clase de tipuri și tipuri ​de date polimorfice =====+<​note>​ 
 +Toate constrângerile ​de tip sunt trecute într-un tuplu, urmat de ''​=%%>​%%'',​ apoi de tipul funcției:
  
-Să considerăm tipul de date polimorfic ''​Either''​:+<​code>​ 
 +*Main> ​:t (\x y -> x == 0 && y < 0) 
 +(\x y -> x == 0 && y == 0) :: (Eq a, Ord b, Num a, Num b) => a -> b -> Bool 
 +</​code>​ 
 +</​note>​
  
-<​code ​haskell+Odată înrolat un tip într-o clasă, putem folosi toate funcțiile care au acea clasă printre constrângeri:​ 
-data Either ​Left | Right b+ 
 +<​code>​ 
 +*Main> :t elem 
 +elem :: (Eq a=-> [a] -> Bool 
 +*Main> elem Red [Blue, Green, Green, Red, Blue] 
 +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>​
  
-Ț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.+===== Înrolarea tipurilor polimorfice =====
  
-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.+Să ne reamintin tipul de listă polimorfică din laboratorul trecut:
  
 <code haskell> <code haskell>
-show :: (Show a=-> String+data List a = Empty | Cons (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?
  
-Pentru a înrola tipul ''​Either''​ în clasa ''​Show''​vom folosi sintaxa:+  * două liste goale sunt egale între ele 
 +  * două liste cu cel puțin un element sunt egale dacă atât capetele lorcât și cozile sunt egale
  
 <code haskell> <code haskell>
-instance ​(Show a, Show b) => Show (Either ​a b) where +eqLists Empty Empty = True 
-    show (Left x= "Left " ++ show x +eqLists ​(Cons as) (Cons bs) = (a == b) && ​(eqLists as bs)
-    show (Right y) = "Right " ++ show y+
 </​code>​ </​code>​
  
-<note important>​ +Î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''​ șa folosi chiar ''​==''​ în loc de ''​eqLists'';​ pentru a face asta, avem nevoie de //o constrângere de tip la nivel de clasă//: 
-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.+ 
 +<code haskell>​ 
 +instance (Eq a) => Eq (List a) where 
 +    Empty == Empty = True 
 +    (Cons a as) == (Cons b bs) = (a == b) && (as == bs) 
 +    _ == _ = False 
 +</​code>​ 
 + 
 +Primul rând exprimă ideea că putem egala două liste //de același tip// (''​List a''​)doar dacă //​elementele lor sunt egalabile// (''​(Eq a=%%>%%''​). 
 + 
 +Observați că, în expresia celui de-al doilea caz, funcția ''​=='' ​apare de două ori: o dată pentru ​a compara elemente, o 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 fide exemplu, comparație între doi întregi, în cazul ''​List Int''​). 
 + 
 +<​note>​ 
 +Cuvântul cheie ''​deriving'' ​se descurcă și cu înrolarea de tipuri polimorficefiind capabil ​să genereze această definiție cu tot cu constrângerea ca și elementele să fie parte din ''​Eq''​.
 </​note>​ </​note>​
  
Line 145: Line 261:
 Putem observa mai multe informații utile: 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''​. +  * constrângerea de tip ''​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.+  * 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 ''​{-#​ 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. +  * 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#​L330+  * 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ării,​ am putea scrie: 
 + 
 +<code haskell>​ 
 +instance Eq Color where 
 +    _ == _ = True 
 +    _ /= _ = True 
 +</​code>​ 
 + 
 +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. 
 + 
 +<​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 a 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>​
  
 ===== Exerciții ===== ===== 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:+1. Înrolați tipul ''​List a''​ în clasa ''​Show'',​ astfel încât șirul rezultat să fie identic cu cel pentru listele Haskell: 
 +   
 +<​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 polimorficiAm vrea să înrolăm ​acest tip la clasele corespunzătoare.
  
-  * Show (astfel încât ​să afișăm numerele fără ​a fi precedate de vreun nume de constructor,​ iar infinitul ca "​Inf"​) +a. înrolați ''​BTree a''​ în ''​Eq''​ 
-  ​* ​Eq + 
-  ​* ​Ord +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 a le vedea pe toate) 
-  ​* ​Num+ 
 +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'':​ 
 + 
 +<​code>​ 
 +Prelude> :t foldr 
 +foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b 
 +Prelude> :t fmap 
 +fmap :: Functor f => (a -> b) -> f a -> f b 
 +</​code>​ 
 + 
 +Observați că constrângerile sunt puse pe //​constructorul de tip//, deci va trebui înrolat ''​BTree'',​ nu ''​BTree a''​. 
 +</​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 constructor,​ iar infinitul ca "​Inf"​) 
 + 
 +b. ''​Eq''​ 
 + 
 +c. ''​Ord''​ 
 + 
 +d. ''​Num''​ 
 + 
 +4. Definiți un tip de date polimorfic care să modeleze următoarea gramatică (în care o valoare poate avea orice tip):
  
-2. Definiți un tip de date asociat următoarei 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. 
  
-3Considerăm următorul 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 haskell> <code haskell>
 type Dictionary a = [(String, a)] type Dictionary a = [(String, a)]
 </​code>​ </​code>​
-care modeleaza //​dicționare//​ - mapări de tip "​nume-variabilă"​-"​valoare polimorfica"​ 
  
-Definiți funcția: +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 ​-> Maybe a</​code>​ + 
-care intoarce valoarea asociata unui nume-variabilă, dintr-un dicționar+<​code ​haskell> 
 +getValue ​:: String -> Dictionary a -> Maybe a 
 +</​code>​ 
 + 
 +Fie următoarea clasă:
  
-4. Definiți următoarea clasă: 
 <code haskell> <code haskell>
 class Eval t a where class Eval t a where
Line 185: Line 360:
  
 <​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 192: 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 198: Line 373:
 </​note>​ </​note>​
  
-5. Înrolați ''​Expr''​ și ''​Integer''​ în clasa ''​Eval''​. Care este semnificația evaluării?​ +a. Înrolați ''​Expr''​ și ''​Integer''​ în clasa ''​Eval''​. Care este semnificația evaluării?\\ 
- +bînrolați ''​Expr''​ și ''​Extended''​ în clasa ''​Eval''​.\\ 
-6Înrolați ''​Expr''​ și ''​FIFO a''​ în clasa ''​Eval''​. ​Semnificația înmulțirii este //​concatenarea//​ a două FIFO.  +cCe observați? Cum ați putea simplifica?
- +
-===== Alte exerciții ===== +
- +
-1Ați definit, în laboratorul anterior, tipurile polimorfice ''​List ​a''​ ș''​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 ​=====+{{:​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]]