Differences

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

Link to this comparison view

pp:24:teme:haskell-imagini-functionale [2024/04/09 15:58]
mihnea.muraru [Etapa 1]
pp:24:teme:haskell-imagini-functionale [2024/05/11 22:42] (current)
mihnea.muraru [Notă pentru curioși (facultativ)]
Line 2: Line 2:
  
   * Data publicării:​ 09.04.2024   * Data publicării:​ 09.04.2024
-  * Data ultimei modificări: ​09.04.2024+  * Data ultimei modificări: ​11.05.2024
   * Deadline hard: ziua laboratorului 10   * Deadline hard: ziua laboratorului 10
   * [[https://​curs.upb.ro/​2023/​mod/​forum/​view.php?​id=158012|Forum temă]]   * [[https://​curs.upb.ro/​2023/​mod/​forum/​view.php?​id=158012|Forum temă]]
Line 72: Line 72:
 Este suficient ca arhiva pentru **vmchecker** să conțină doar modulul ''​Shallow''​. Este suficient ca arhiva pentru **vmchecker** să conțină doar modulul ''​Shallow''​.
  
 +===== Etapa 2 =====
 +
 +În cadrul reprezentării concrete din etapa 1, funcțiile de **definire și manipulare** a regiunilor și transformărilor surprind **complexitatea** operațiilor,​ în timp ce funcția ''​inside'',​ de **interogare** a regiunilor, are o implementare **banală**,​ în termenii reprezentării regiunilor ca funcții caracteristice. Această abordare are **avantajul** că **noi regiuni, transformări și operații** pe acestea pot fi **adăugate ușor**, independent de definițiile anterioare. De exemplu, dacă dorim să adăugăm o nouă regiune elementară,​ cum este un triunghi, sau o nouă operație, ca diferența a două regiuni, putem introduce funcții separate, fără a modifica definițiile existente.
 +
 +Ce se întâmplă totuși dacă încercăm să **schimbăm interpretarea** unei regiuni? Spre exemplu, în loc de **funcția caracteristică** din etapa 1, care stabilește apartenența unui punct la o regiune, acum am dori ca printr-o regiune să înțelegem **numărul de subregiuni și transformări elementare** din descrierea acelei regiuni. De exemplu, regiunea ''​union (circle 2) (applyTransformation (translation 1 0) (rectangle 2 2))''​ ar căpăta reprezentarea ''​(2,​ 1)'',​ pentru că sunt utilizate 2 subregiuni elementare (un cerc și un dreptunghi) și 1 transformare elementară (o translație) în construcția ei. Într-o altă situație, am putea dori ca printr-o regiune să înțelegem **descrierea textuală a operațiilor** care construiesc acea regiune. De exemplu, pentru aceeași regiune ca mai sus, reprezentarea textuală ar putea fi ''"​Circle 2 + Translation 1 0 @ Rectangle 2 2"''​. Această schimbare de interpretare s-ar realiza **anevoios**,​ fiind necesară **reimplementarea** tuturor funcțiilor existente în termenii noii interpretări.
 +
 +În etapa 2, vom explora așa-numitele **//deep embeddings//​** pentru regiuni și transformări,​ în sensul că funcțiile de definire și manipulare a regiunilor nu vor mai viza o anumită reprezentare concretă, ci vor genera un **arbore de sintaxă abstractă (AST)**, care surprinde operațiile generice utilizate în construcția unei regiuni sau transformări. De exemplu, regiunea de mai sus ar căpăta reprezentarea abstractă ''​Union (Circle 2) (Transform (Translation 1 0) (Rectangle 2 2))'',​ unde simbolurile reprezintă constructori de date definiți de noi. Abordarea are următoarele **avantaje**:​
 +
 +  * Putem adăuga **ușor** diverse **interpretări** ale acestui AST, în forma unor funcții, care să recupereze orice reprezentare concretă dorim (de exemplu, funcțiile caracteristice din etapa 1 sau celelalte două exemple de reprezentări de mai sus). Practic, situația este **inversată** față de etapa 1, în sensul că funcțiile de **definire și manipulare** vor avea de data aceasta implementări banale, în termenii unor constructori de date, în timp ce **complexitatea** operațiilor va fi surprinsă în funcțiile de **interpretare**. ​
 +  * Avem posibilitatea **preprocesării** AST-ului înaintea interpretării lui, în sensul că regiunile și transformările pot fi simplificate. De exemplu, două translații succesive pot fi înlocuite cu una singură cu parametrii corespunzători.
 +
 +Ca **dezavantaj**,​ adăugarea de **noi regiuni, transformări și operații** pe acestea se realizează dificil, fiind necesară modificarea **tipurilor** care descriu AST-urile, și în consecință extinderea tuturor **interpretărilor existente**.
 +
 +Observați **dualismul** celor două abordări, bazate pe //​shallow//,​ respectiv //deep embeddings//,​ în sensul că ce se realizează ușor într-una se implementează dificil în cealaltă, fără ca una dintre abordări să o domine pe cealaltă. În literatură,​ acest lucru poartă numele de **//​problema expresivității//​** (//​[[https://​en.wikipedia.org/​wiki/​Expression_problem|expression problem]]//​),​ care se manifestă la mai multe niveluri, inclusiv între diferite paradigme de programare.
 +
 +În cadrul etapei 2, veți porni de la tipuri de date deja definite în schelet pentru reprezentarea AST-urilor regiunilor și transformărilor,​ și **veți implementa** funcții care:
 +
 +  * **interpretează** AST-ul pentru a recupera reprezentările concrete din etapa 1
 +  * **descompun** secvențe imbricate de transformări (vezi ''​combineTransformations''​) în variante liniarizate
 +  * **fuzionează** transformări elementare consecutive de același fel (translații cu translații și scalări cu scalări)
 +  * **optimizează** (simplifică) transformările dintr-un AST, prin deplasare în sus și fuzionare.
 +
 +Construcțiile și mecanismele noi de limbaj pe care le veți exploata în rezolvare, pe lângă cele din etapa 1, sunt:
 +
 +  * **tipurile de date utilizator**.
 +
 +Întrucât **optimizarea** AST-ului constituie funcționalitatea cea mai pretențioasă din această etapă, o **exemplificăm**,​ pas cu pas, pentru AST-ul de mai jos (exemplul apare și în comentariile funcției ''​optimizeTransformations''​):​
 +
 +<code haskell>
 +Union (Transform (Combine [ Translation 1 2
 +                          , Combine [ Translation 3 4
 +                                    , Scaling 2
 +                                    ]  ​
 +                          , Scaling 3
 +                          ])
 +                 ​(Complement (Transform (Scaling 4)
 +                                        (Transform (Scaling 2) (Circle 5)))))
 +      (Transform (Translation 4 6) (Rectangle 6 7))
 +</​code>​
 +
 +Principiile de optimizare sunt descrise pe larg în schelet, dar întotdeauna când prelucrăm o anumită regiune, trebuie **să optimizăm mai întâi recursiv subregiunile**. Prima optimizare propriu-zisă se produce la nivelul subarborelui ''​Transform (Scaling 4)                          (Transform (Scaling 2) (Circle 5))'',​ care conține două scalări succesive, care pot fi **alipite** în ''​Transform (Scaling 8) (Circle 5)'',​ iar întregul AST este acum:
 +
 +<code haskell>
 +Union (Transform (Combine [ Translation 1 2
 +                          , Combine [ Translation 3 4
 +                                    , Scaling 2
 +                                    ]  ​
 +                          , Scaling 3
 +                          ])
 +                 ​(Complement (Transform (Scaling 8) (Circle 5))))
 +      (Transform (Translation 4 6) (Rectangle 6 7))
 +</​code>​
 +
 +În continuare, mergem un nivel mai sus, la subarborele ''​Complement (Transform (Scaling 8) (Circle 5))''​. Pentru a permite eventuala alipire a scalării cu 8 cu o altă transformare de deasupra, este util să **extragem transformarea în fața complementului**,​ ca în ''​Transform (Scaling 8) (Complement (Circle 5))'',​ iar întregul AST devine:
 +
 +<code haskell>
 +Union (Transform (Combine [ Translation 1 2
 +                          , Combine [ Translation 3 4
 +                                    , Scaling 2
 +                                    ]  ​
 +                          , Scaling 3
 +                          ])
 +                 ​(Transform (Scaling 8) (Complement (Circle 5))))
 +      (Transform (Translation 4 6) (Rectangle 6 7))
 +</​code>​
 +
 +Mergem iarăși un nivel mai sus, la subarborele ''​Transform (Combine ...) (Transform (Scaling 8) (Complement (Circle 5)))'',​ unde observăm că transformările din secvența ''​Combine'',​ și anume două translații,​ respectiv două scalări consecutive,​ pot fi **alipite**;​ mai departe, scalările mai pot **fuziona** cu scalarea cu 8 din fața complementului. Din păcate, secvența ''​Combine''​ conține transformări **imbricate**,​ care trebuie mai întâi **liniarizate** pentru a putea fi ușor alipite. În urma liniarizării,​ se obține lista ''​[Translation 1 2, Translation 3 4, Scaling 2, Scaling 3]'',​ iar dacă la aceasta **adăugăm și prima transformare de mai jos**, ''​Scaling 8'',​ și fuzionăm, obținem lista ''​[Translation 4 6, Scaling 48]'',​ iar întregul AST devine:
 +
 +<code haskell>
 +Union (Transform (Combine [Translation 4 6, Scaling 48]) (Complement (Circle 5)))
 +      (Transform (Translation 4 6) (Rectangle 6 7))
 +</​code>​
 +
 +Rămâne să prelucrăm întreaga reuniune. Obiectivul este același: să înlesnim eventuale alipiri cu transformări de deasupra, **extrăgând în fața reuniunii transformările de dedesubt**, dacă este posibil, cum am procedat mai sus la complement. Din moment ce avem acum două ramuri, nu putem să extragem decât **cel mai lung prefix de transformări comune**, lăsând sufixele necomune dedesubt. Prefixul cu pricina este ''​[Translation 4 6]'',​ iar AST-ul devine în final:
 +
 +<code haskell>
 +Transform (Translation 4 6)
 +          (Union (Transform (Scaling 48) (Complement (Circle 5)))
 +                 ​(Rectangle 6 7))
 +</​code>​
 +
 +Modulul de interes din **schelet** este ''​Deep'',​ care conține **reprezentarea** AST-urilor regiunilor și a transformărilor,​ precum și **operațiile** pe care trebuie să le implementați. De asemenea, trebuie să aveți în același director și modulul ''​Shallow''​ din etapa 1. Găsiți detaliile despre **funcționalitate** și despre **constrângerile de implementare**,​ precum și **exemple**,​ direct în schelet. Aveți de completat definițiile care încep cu ''​%%*** TODO ***%%''​.
 +
 +Pentru **rularea testelor**, încărcați în interpretor modulul ''​TestDeep''​ și evaluați ''​main''​.
 +
 +Este suficient ca arhiva pentru **vmchecker** să conțină modulele ''​Deep''​ și ''​Shallow''​.
 +
 +===== Etapa 3 =====
 +
 +În etapa 2, am văzut cum **interpretarea** //deep embeddings//​ (AST-urilor) ale transformărilor și regiunilor prin funcțiile ''​toTransformation''​ și ''​toRegion''​ a permis **recuperarea** //shallow embeddings//​ din etapa 1. Mai precis, **paralela** dintre implementările celor două etape este următoarea:​
 +
 +  * **Un singur caz** al funcției de interpretare din etapa 2, aferent unui anumit //​pattern//,​ corespunde unei **funcții de sine stătătoare** din etapa 1. De exemplu, cazul ''​toRegion (Union region1 region2) = ...''​ din etapa 2 corespunde funcției ''​union region1 region2 = ...''​ din etapa 1.
 +  * O funcție din etapa 1 primește ca parametri reprezentări concrete pe care le **manipulează direct**, în timp ce în etapa 2 este necesară **interpretarea explicită** a parametrilor. De exemplu, în etapa 1, definiția unei funcții are forma ''​union region1 region2 = ... region1 ... region2 ...'',​ în care parametrii sunt utilizați ca atare, în timp ce în etapa 2, un caz are forma ''​toRegion (Union region1 region2) = ... (toRegion region1) ... (toRegion region2) ...'',​ în care parametrii trebuie interpretați în prealabil.
 +
 +Din cele de mai sus decurg natural două **întrebări**,​ pe care le vom discuta în continuare:
 +
 +  - Fiecărui //shallow embedding// îi **corespunde** o interpretare a //deep embeddings//​ și viceversa sau relația dintre ele este mai complicată?​
 +  - Putem **ascunde interpretarea recursivă explicită** a substructurilor din cadrul //deep embeddings//,​ pentru a ne concentra doar pe maniera în care **combinăm** produsele interpretării (de exemplu, pentru a evita invocarea explicită a lui ''​toRegion''​ pe ''​region1''​ și ''​region2''​)?​
 +
 +Pentru a răspunde la **întrebarea 1**, introducem distincția dintre operații compoziționale și necompoziționale:​
 +
 +  * Rezultatul unei operații **compoziționale** aplicate pe o structură depinde doar de rezultatul **aceleiași operații** aplicate recursiv substructurilor,​ **nu și de alte caracteristici** ale acestora. De exemplu, operația de calcul al **lungimii unei liste** este compozițională,​ întrucât ''​length (x : xs)''​ depinde doar de ''​length xs'',​ nu și de alt aspect al lui ''​xs'',​ cum ar fi ''​xs''​ însuși, primul element al lui ''​xs''​ sau suma elementelor lui ''​xs''​. Alte exemple de operații compoziționale sunt calculul **sumei elementelor** unei liste, al **înălțimii** unui arbore binar (funcția ''​height''​ din laboratorul 8) și funcția **''​toRegion''​** din etapa 2.
 +  * Rezultatul unei operații **necompoziționale** depinde și de **alte caracteristici** ale substructurilor,​ în afară de rezultatul aplicării recursive a operației pe acestea. De exemplu, calcul **mediei aritmetice** a elementelor unei liste este o operație necompozițională,​ întrucât nu putem determina ''​average (x : xs)''​ doar pe baza lui ''​average xs'',​ având nevoie separat de suma elementelor și de lungimea listei. De asemenea, ​ funcția ''​isBalanced''​ din laboratorul 8, care verifică dacă un arbore binar este **echilibrat**,​ este o operație necompozițională,​ întrucât ''​isBalanced (Node key left right)''​ depinde nu numai de ''​isBalanced left''​ și ''​isBalanced right'',​ ci și de ''​height left''​ și ''​height right''​.
 +
 +Cu toate acestea, o operație necompozițională poate fi **transformată** într-una compozițională,​ o metodă fiind îmbogățirea rezultatului calculat. De exemplu, funcția ''​isBalanced''​ de mai sus poate fi rescrisă compozițional dacă, în loc să întoarcă doar rezultatul boolean, întoarce o **pereche** cu rezultatul boolean **și** înălțimea arborelui; în acest fel, am avea la dispoziție toată informația necesară doar din aplicațiile recursive ''​isBalanced left''​ și ''​isBalanced right''​.
 +
 +În concluzie, având în vedere că în cadrul unui //shallow embedding// avem direct la dispoziție o **anumită reprezentare concretă** a entităților noastre (de exemplu, regiuni ca funcții caracteristice în etapa 1), din care în general nu putem recupera alte caracteristici (de exemplu, nu putem recupera secvența operațiilor care au construit o regiune având la dispoziție doar funcția ei caracteristică),​ //shallow embeddings//​ **corespund exclusiv interpretărilor compoziționale** ale //deep embeddings//​ (cum este ''​toRegion''​ din etapa 2). Aceasta înseamnă că un //shallow embedding// poate fi privit întotdeauna ca o interpretare a //deep embeddings//,​ dar **nu și viceversa** (cel puțin nu direct).
 +
 +Mergând mai departe la **întrebarea 2**, regăsim ideea de **//fold// (reducere)**. De exemplu, pe liste, funcționala ''​foldr''​ are tocmai scopul de a încapsula prelucrarea recursivă a restului listei, permițându-i programatorului să se concentreze doar pe maniera de **îmbinare** a elementului curent cu rezultatul prelucrării recursive (acumulatorul),​ prin funcția binară trimisă ca parametru către ''​foldr''​. Având în vedere că funcția de combinare, pe care o vom denumi de acum înainte **//​combiner//​**,​ primește ca parametru direct rezultatul prelucrării recursive a restului listei, operațiile implementabile ca reduceri (prin funcționala ''​foldr''​) sunt întotdeauna **compoziționale**.
 +
 +Pasul următor este să vedem cum putem **extinde** ideea de reducere pe alte tipuri de date, în afară de liste; în particular, pe transformări și regiuni. În laboratorul 9, ați descoperit că funcționala ''​foldr''​ aparține clasei ''​Foldable'',​ instanțiată inclusiv de constructorul listă. Din păcate, tipul funcției este inspirat de constructorii de date ai tipului listă (//cons// și lista vidă) și **nu este suficient de expresiv** pentru a permite implementarea **oricărei operații compoziționale** pe un tip de date oarecare. De exemplu, deși pentru un tip de arbore binar poate fi dată o definiție a funcționalei ''​foldr''​ (ca în laboratorul 9), unele operații compoziționale,​ ca determinarea numărului de chei sau a sumei cheilor **pot** fi implementate prin ''​foldr'',​ în timp ce alte operații compoziționale,​ ca determinarea înălțimii arborelui, **nu pot** fi implementate prin ''​foldr''​. Motivul este că //​combiner//​-ul ia ca parametru un singur acumulator, în timp ce pentru determinarea înălțimii ar fi necesari doi acumulatori,​ aferenți celor doi subarbori.
 +
 +Pentru a ne da seama cum putem defini un mecanism de reducere **particularizat** pe un anumit tip de date, care să permită exprimarea **oricărei operații compoziționale** pe acesta, să ne amintim cum au fost aleși parametrii funcționalei ''​foldr'',​ inițial dedicată listelor. Listele posedă doi constructori de date, //cons// și lista vidă; pentru **fiecare constructor de date**, definim **câte un parametru** al funcționalei ''​foldr'',​ al cărui tip se obține **înlocuind referirile recursive la tipul listă cu tipul acumulatorului**. Mai precis:
 +
 +  * //cons// are tipul ''​(:​) :: e -> [e] -> [e]'',​ unde ''​e''​ este tipul elementelor. Înlocuind referirile recursive la ''​[e]''​ cu tipul ''​a''​ al acumulatorului,​ obținem tipul primului parametru al lui ''​foldr'',​ și anume ''​e -> a -> a''​.
 +  * Lista vidă are tipul ''​[] :: [e]''​. În urma aceleiași înlocuiri, obținem tipul celui de-al doilea parametru al lui ''​foldr'',​ și anume ''​a''​.
 +
 +Astfel, se obține binecunoscutul tip al lui ''​foldr''​ particularizat pe liste, și anume ''​(e -> a -> a) -> a -> [e] -> a''​.
 +
 +Dacă dorim să **adaptăm** această idee pentru tipul ''​RegionAST'',​ trebuie să inventăm o nouă funcțională de reducere, ''​foldRegionAST'',​ și, la fel ca mai sus, să asociem fiecărui constructor de date câte un parametru al acestei funcții. De exemplu:
 +
 +  * Constructorul ''​FromPoints''​ are tipul ''​[Point] -> RegionAST''​. Înlocuind referirea recursivă la ''​RegionAST''​ cu tipul ''​a''​ al acumulatorului,​ obținem tipul parametrului aferent al lui ''​foldRegionAST'',​ și anume ''​[Point] -> a''​.
 +  * Contructorul ''​Union''​ are tipul ''​RegionAST -> RegionAST -> RegionAST''​. În urma aceleiași înlocuiri, obținem tipul parametrului aferent al lui ''​foldRegionAST'',​ și anume ''​a -> a -> a''​ ș.a.m.d.
 +
 +Din păcate, ''​RegionAST''​ posedă șapte constructori de date (ar putea fi și mai mulți), și transmiterea unui **număr atât de mare de parametri** funcționalei ''​foldRegionAST''​ devine greoaie. Din fericire, se poate recurge la următorul artificiu:
 +
 +  - Definim mai întâi un nou tip de date, ''​RegionShape a'',​ cu aceiași constructori de date ca ''​RegionAST'',​ dar care utilizează tipul ''​a''​ pentru câmpuri. De exemplu, constructorul ''​Union''​ este acum definit prin ''​Union a a''​ în loc de ''​Union RegionAST RegionAST''​. Rolul lui ''​RegionShape''​ este de a constitui un **eșafodaj comun**, atât pentru **construcția AST-urilor**,​ cât și pentru mecanismul de **reducere**.
 +  - Tipul original ''​RegionAST''​ poate fi **recuperat** particularizând parametrul de tip ''​a''​ al constructorului de tip ''​RegionShape''​ la ''​RegionAST''​ însuși. Mai precis, ''​RegionAST''​ este definit prin **ecuația de punct fix** ''​RegionAST = RegionShape RegionAST''​.
 +  - **//​Combiner//​-ele** pot fi definite ca operând direct pe valori ale tipului ''​RegionShape a'',​ cu tipul ''​a''​ particularizat în raport cu rezultatul dorit al operației de reducere. De exemplu, dacă se dorește reducerea unui ''​RegionAST''​ la o descriere textuală a sa, //​combiner//​-ul poate căpăta tipul ''​RegionShape String''​. Aceasta înseamnă că, atunci când //​combiner//​-ul tratează prin //pattern matching// cazul ''​Union string1 string2'',​ presupune că cele două câmpuri ale constructorului ''​Union''​ **conțin deja rezultatele reducerilor recursive ale subarborilor** la șiruri de caractere, astfel încât se poate **concentra direct pe combinarea** lor pentru a obține reprezentarea ca șir de caractere a întregii regiuni (exact ca la funcționala ''​foldr''​ pe liste).
 +
 +În final, în loc ca funcționala ''​foldRegionAST''​ să primească drept parametri **șapte //​combiner//​-e**,​ câte unul pentru fiecare constructor de date al tipului ''​RegionAST'',​ primește **un singur //​combiner//​**,​ care tratează prin //pattern matching// șapte cazuri, asigurând o mult mai bună **modularizare**.
 +
 +O întrebare pertinentă este dacă **beneficiile** abordării bazate pe reduceri (//​folds//​),​ prezentate mai sus, justifică efortul de elaborare. Dincolo de **simplificarea** implementărilor operațiilor compoziționale,​ prin ascunderea interpretării recursive a substructurilor,​ care sporește **lizibilitatea**,​ există avantaje mai puțin evidente: reducerile au multe proprietăți matematice cunoscute, și descrierea explicită a unei operații ca o reducere poate înlesni **demonstrarea formală a corectitudinii** unui program și chiar aplicarea de **optimizări** la nivelul compilatorului.
 +
 +În cadrul etapei 3, veți instanția diverse clase și veți implementa mecanismul de reducere descris mai sus. Mai precis, veți scrie funcții care:
 +
 +  * reprezintă AST-urile transformărilor și regiunilor ca **șiruri de caractere**,​ pentru o mai bună vizualizare la consolă, instanțiind clasa ''​Show''​
 +  * permit construcția regiunilor prin intermediul **operatorilor aritmetici** (de exemplu, reuniune văzută ca adunare), instanțiind clasa ''​Num''​
 +  * permit **aplicarea unei funcții** asupra câmpurilor aferente unor tipuri, instanțiind clasa ''​Functor''​
 +  * permit **reducerea** AST-urilor transformărilor și a regiunilor
 +  * definesc anumite **operații compoziționale** pe transformări și regiuni în termenii unor reduceri.
 +
 +Construcțiile și mecanismele noi de limbaj pe care le veți exploata în rezolvare, pe lângă cele din etapa 2, sunt:
 +
 +  * **polimorfismul ad-hoc**
 +  * **clasele**.
 +
 +Modulul de interes din **schelet** este ''​Folds'',​ care conține definițiile **tipurilor de date** necesare, precum și **operațiile** pe care trebuie să le implementați. De asemenea, trebuie să aveți în același director și modulul ''​Shallow''​ din etapa 1. Găsiți detaliile despre **funcționalitate** și despre **constrângerile de implementare**,​ precum și **exemple**,​ direct în schelet. Aveți de completat definițiile care încep cu ''​%%*** TODO ***%%''​.
 +
 +Pentru **rularea testelor**, încărcați în interpretor modulul ''​TestFolds''​ și evaluați ''​main''​.
 +
 +Este suficient ca arhiva pentru **vmchecker** să conțină modulele ''​Folds''​ și ''​Shallow''​.
 +
 +==== Notă pentru curioși (facultativ) ====
 +
 +Secțiunea prezintă niște informații suplimentare despre etapa 3, și nu afectează rezolvarea temei.
 +
 +Mecanismul de reducere din schelet poate fi generalizat în felul următor. În primul rând, ideea de definire a unui AST (precum ''​RegionAST''​) ca **punct fix al unui ''​Functor''​** (precum ''​RegionShape''​),​ poate fi surprinsă explicit:
 +
 +<code haskell>
 +newtype Fix f = C (f (Fix f))
 +
 +type TransformationAST = Fix TransformationShape
 +type RegionAST ​        = Fix RegionShape
 +</​code>​
 +
 +În acest fel, poate fi definită o **unică funcțională** ''​foldAST'',​ cu tipul mai general ''​Functor f => (f a -> a) -> Fix f -> a'',​ pentru care funcționalele ''​foldTransformationAST''​ și ''​foldRegionAST''​ devin cazuri particulare. Constrângerea ''​Functor f''​ este necesară pentru utilizarea lui ''​fmap''​ în implementare,​ ca în cerința temei, care știe să propage idiosincratic reducerea la substructuri. **//​Combiner//​-ul** are acum tipul mai general ''​f a -> a''​ și poartă numele de **algebră asociată functorului** ''​f''​.
 ===== Precizări ===== ===== Precizări =====
  
Line 79: Line 246:
 ===== Resurse ===== ===== Resurse =====
  
-  * [[https://ocw.cs.pub.ro/courses/_media/pp/24/teme/haskell/etapa1.zip|Schelet ​etapa 1]]+  * {{pp:24:​teme:​haskell:​etapa1.zip|Schelet etapa 1}} 
 +  * {{pp:​24:​teme:​haskell:​etapa2.zip|Schelet etapa 2}} 
 +  * {{pp:​24:​teme:​haskell:​etapa3.zip|Schelet etapa 3}} 
 + 
 +===== Referințe ===== 
 + 
 +  * de Moor, O., & Gibbons, J. (Eds.). (2003). //The Fun of Programming//. Palgrave Macmillan. 
 +  * Gibbons, J., & Wu, N. (2014). Folding domain-specific languages: deep and shallow embeddings (Functional pearl). In //Proceedings of the 19th ACM SIGPLAN international conference on Functional programming/​/. 
 + 
 +===== Changelog ===== 
 + 
 +  * 11.05 (12:05): Publicat checker etapa 3. 
 +  * 06.05 (11:40): Publicat etapa 3, enunț și schelet; urmează checker-ul. 
 +  * 23.04 (21:20): Publicat etapa 2. 
 +  * 19.04 (10:25): Flexibilizat checker ​etapa 1 în privința comparațiilor pe numere reale, pentru reducerea riscului de eșec al testelor din cauza erorilor de aproximare. Dacă testele treceau oricum pe checker-ul anterior, nu este necesar să mai faceți nimic. 
 +  * 16.04 (10:10): Încărcat checker etapa 1. 
 +  * 14.04 (17:00): Adăugat clarificare în comentariile funcției ''​combineTransformations'',​ pentru evidențierea corespondenței cu aplicările individuale ale transformărilor din listă.
pp/24/teme/haskell-imagini-functionale.1712667496.txt.gz · Last modified: 2024/04/09 15:58 by mihnea.muraru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0