Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:l05 [2018/03/26 13:55] dmihai |
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 tipuri, atunci putem lucra pe tipul respectiv cu operațiile definite în clasă. | + | 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 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 egalitate. Aceasta 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ții, cealaltă 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> | </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 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)''. | + | <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 trecute, analizând tipurile unor expresii, ați întâlnit notații asemănătoare: | + | 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> | <code> | ||
- | Prelude> :t elem | + | Prelude> data Color = Red | Green | Blue deriving (Eq, Show) |
- | elem :: (Eq a) => a -> [a] -> Bool | + | Prelude> Red == Red |
+ | True | ||
+ | Prelude> Red == Green | ||
+ | False | ||
+ | Prelude> Red | ||
+ | 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ăm 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 a b = Left a | Right b | + | |
+ | <code> | ||
+ | *Main> :t elem | ||
+ | elem :: (Eq a) => 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) => a -> String | + | 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? | ||
- | 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 lor, câ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 a as) (Cons b 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'' și 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 fi, de exemplu, comparație între doi întregi, în cazul ''List Int''). | ||
+ | |||
+ | <note> | ||
+ | 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> | </note> | ||
Line 142: | 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 reale 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 182: | 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 189: | 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 195: | 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]] |