Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:l05 [2019/03/24 15:09] dmihai |
pp:l05 [2020/02/05 15:50] (current) dmihai [Exerciții] |
||
---|---|---|---|
Line 22: | Line 22: | ||
</code> | </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. | + | 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]]. | În Java, polimorfismul parametric este realizat prin [[https://en.wikipedia.org/wiki/Generics_in_Java|genericitate]]. | ||
Line 56: | Line 56: | ||
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. | 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: | + | 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> | <code haskell> | ||
Line 81: | Line 81: | ||
O **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. | O **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 warning> | + | <note important> |
Î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. | Î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> | ||
Line 225: | Line 225: | ||
Empty == Empty = True | Empty == Empty = True | ||
(Cons a as) == (Cons b bs) = (a == b) && (as == bs) | (Cons a as) == (Cons b bs) = (a == b) && (as == bs) | ||
+ | _ == _ = False | ||
</code> | </code> | ||
- | Primul rând exprimă ideea că putem compara două liste //de același tip// (''List a''), doar dacă //elementele lor sunt comparabile// (''(Eq a) =%%>%%''). | + | 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 fi, de exemplu, comparație între doi întregi, în cazul ''List Int''). | 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 fi, de exemplu, comparație între doi întregi, în cazul ''List Int''). | ||
Line 265: | Line 266: | ||
* 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. | * 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 | * 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 polimorfici. Am 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. | ||
- | 3. Considerăm următorul constructor de tip: | + | 5. Amintiț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 300: | 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 307: | 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 313: | 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. | + | c. Ce observați? Cum ați putea simplifica? |
- | + | ||
- | ===== 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 ===== | + | {{: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]] |