Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:2024:tema3 [2024/04/20 12:07] tpruteanu |
pp:2024:tema3 [2024/05/03 09:49] (current) tpruteanu |
||
---|---|---|---|
Line 2: | Line 2: | ||
<note> | <note> | ||
- | Schelet: TODO | + | Schelet: {{:pp:2024:lambda-interpreter.zip|}} |
</note> | </note> | ||
<note warning> | <note warning> | ||
- | **Deadline:** TODO | + | **Deadline:** miercuri 29 mai, ora 23:59 |
* Temele trebuie submise pe [[curs.upb.ro]], în assignment-ul ''Tema 3''. | * Temele trebuie submise pe [[curs.upb.ro]], în assignment-ul ''Tema 3''. | ||
* Pentru întrebări folosiți forum-ul dedicat de pe [[curs.upb.ro]]. | * Pentru întrebări folosiți forum-ul dedicat de pe [[curs.upb.ro]]. | ||
Line 17: | Line 17: | ||
</note> | </note> | ||
- | O sa definim o expresie lambda cu ajutorul urmatorului **TDA**: | + | O să definim o expresie lambda cu ajutorul următorului **TDA**: |
<code haskell> | <code haskell> | ||
data Lambda = Var String | data Lambda = Var String | ||
Line 25: | Line 25: | ||
<note important> | <note important> | ||
- | In schelet, definitia **TDA**-ului contine si un constructor pentru un macro, pentru cerintele **1** si **2** il puteti ignora, o sa fie introdus in cadrul cerintei **3**. | + | În schelet, definiția **TDA**-ului conține și un constructor pentru macro-uri, pentru cerințele **1** și **2** îl puteți ignora, o să puteți lua punctaj maxim fără să faceți pattern matching pe el, o să fie introdus în cadrul cerinței **3**. |
</note> | </note> | ||
- | Variabilele sunt declarate de tipul ''String'', pentru simplitate o sa considerăm variabile orice șir de caractere format numai din litere mici ale alfabetului englez. | + | Variabilele sunt declarate de tipul ''String'', pentru simplitate o să considerăm variabilă orice șir de caractere format numai din litere mici ale alfabetului englez. |
===== 1. Evaluation ===== | ===== 1. Evaluation ===== | ||
- | |||
- | Glosar de termeni: | ||
- | * //$\beta$-reducere// - //reducere// a unei lambda-expresii, in sensul in care a fost prezentat la curs. | ||
- | * **redex** - lambda-expresie de forma $( \lambda x.e_1 \ e_2 )$. | ||
<note> | <note> | ||
- | Înainte de a reduce o expresie (realizarea $\beta$-reducerii), trebuie să rezolvăm //**coliziunile de nume**//. \\ | + | **Reminder**: |
- | Dacă am încerca să reducem un **redex** fără a face substituții textuale (un **redex** e o expresie reductibilă, **i.e.** are forma $( \lambda x.e_1 \ e_2 $) există riscul de a pierde întelesul original al expresiei. \\ | + | * **redex** - o expresie reductibilă, **i.e.** are forma $( \lambda x.e_1 \ e_2 $) |
- | Spre exemplu **redex**-ul: $(\lambda x.\lambda y.(x \ y) \ \lambda x.y)$, ar fi redus la: $\lambda y.(\lambda x.y \ y)$. Acest efect nedorit are denumirea intuitivă de //variable-capture//: Variabila inițial liberă $math[y] a devenit legată dupa reducere. \\ | + | * **normal-form** - expresie care nu mai poate fi redusa (nu contine niciun **redex**) |
- | Puteți observa că expresia și-a pierdut sensul original, pentru că **y**-ul liber din $\lambda x.y$ e acum //bound// de $\lambda y.$ din expresia în care a fost înlocuit. \\ | + | |
- | Astfel, expresia corecta ar fi: $\lambda a.(\lambda x.y \ a)$. \\ | + | |
</note> | </note> | ||
- | Pentru a detecta si rezolva //variable capture//, o sa pregatim cateva functii ajutatoare: \\ | + | Evaluarea unei //expresii lambda// constă în realizarea de $\beta$-reducerii până ajungem la o expresie echivalentă în formă normală. |
+ | |||
+ | Un detaliu de implementare este că înainte de a realiza $\beta$-reducerea, va trebui să rezolvăm posibilele //**coliziuni de nume**//. \\ | ||
+ | Dacă am încerca să reducem un **redex** fără a face substituții textuale există riscul de a pierde întelesul original al expresiei. \\ | ||
+ | Spre exemplu **redex**-ul: $(\lambda x.\lambda y.(x \ y) \ \lambda x.y)$, ar fi redus la: $\lambda y.(\lambda x.y \ y)$. Acest efect nedorit are denumirea intuitivă de //variable-capture//: Variabila inițial liberă $math[y] a devenit legată după reducere. \\ | ||
+ | Puteți observa că expresia și-a pierdut sensul original, pentru că **y**-ul liber din $\lambda x.y$ e acum //bound// de $\lambda y.$ din expresia în care a fost înlocuit. \\ | ||
+ | Astfel, reducerea corectă ar fi: $\lambda a.(\lambda x.y \ a)$. \\ | ||
+ | |||
+ | Pentru a detecta și rezolva //variable capture//, o să pregătim câteva funcții ajutătoare: \\ | ||
**1.1.** (//5p//) Implementați funcția auxiliară ''vars'' care returnează o listă cu toate ''String''-urile care reprezintă variabile într-o expresie. | **1.1.** (//5p//) Implementați funcția auxiliară ''vars'' care returnează o listă cu toate ''String''-urile care reprezintă variabile într-o expresie. | ||
\\ | \\ | ||
- | **1.2.** (//5p//) Implementați funcția auxiliară ''free_vars'' care returnează o listă cu toate ''String''-urile care reprezintă variabile libere într-o expresie. (**notă**: dacă o variabilă este liberă în expresie în mai multe contexte, o să apară o singură dată în listă). \\ | + | **1.2.** (//5p//) Implementați funcția auxiliară ''freeVars'' care returnează o listă cu toate ''String''-urile care reprezintă variabile libere într-o expresie. (**notă**: dacă o variabilă este liberă în expresie în mai multe contexte, o să apară o singură dată în listă). \\ |
- | **1.3.** (//10p//) Implementați funcția auxiliară ''new_vars'' care primeste o lista de ''String''-uri si intoarce cel mai mic ''String'' lexicografic care nu apare in lista (**e.g.** ''new_vars ["a", "b", "c"]'' o sa intoarca ''"d"''). | + | **1.3.** (//10p//) Implementați funcția auxiliară ''newVars'' care primește o listă de ''String''-uri și intoarce cel mai mic ''String'' lexicografic care nu apare în listă (**e.g.** ''new_vars ["a", "b", "c"]'' o să întoarcă ''"d"''). |
---- | ---- | ||
- | **1.4.** (//20p//) Implementați funcția ''reduce'' care reduce un **redex** luând în considerare și //**coliziunile de nume**//. Funcția primește **redex**-ul 'deconstruit' și returnează expresia rezultată. \\ | + | **1.4.** (//5p//) Implementați funcția ''isNormalForm'' care verifică daca o expresie este în formă normală. \\ |
+ | |||
+ | **1.5.** (//20p//) Implementați funcția ''reduce'' care realizează $\beta$-reducerea unui **redex** luând în considerare și //**coliziunile de nume**//. Funcția primește **redex**-ul 'deconstruit' și returnează expresia rezultată. \\ | ||
<code haskell> | <code haskell> | ||
reduce :: String -> Lambda -> Lambda -> Lambda | reduce :: String -> Lambda -> Lambda -> Lambda | ||
reduce x e_1 e_2 = undefined | reduce x e_1 e_2 = undefined | ||
- | -- oriunde apare variabile x in e_1, este inlocuita cu e_2 | + | -- oriunde apare variabila x în e_1, este inlocuită cu e_2 |
</code> | </code> | ||
\\ | \\ | ||
Line 64: | Line 68: | ||
O să facem reducerea „step by step”, implementăm o funcție care reduce doar următorul **redex** comform unei strategii. Apoi aplicăm acesți pași până expresia rămasă este în formă normală. | O să facem reducerea „step by step”, implementăm o funcție care reduce doar următorul **redex** comform unei strategii. Apoi aplicăm acesți pași până expresia rămasă este în formă normală. | ||
\\ | \\ | ||
- | **1.5.** (//10p//) Implementați funcția ''normal_step'' care aplică un pas de reducere după strategia Normală. \\ | + | **1.6.** (//10p//) Implementați funcția ''normalStep'' care aplică un pas de reducere după strategia Normală. \\ |
- | **1.6.** (//10p//) Implementați funcția ''applicative_step'' care aplică un pas de reducere după strategia Aplicativă. \\ | + | **1.7.** (//10p//) Implementați funcția ''applicativeStep'' care aplică un pas de reducere după strategia Aplicativă. \\ |
- | **1.7.** (//5p//) Implementați funcția ''is_normal_form'' care verifica daca o expresie este în formă normală. \\ | + | **1.8.** (//5p//) Implementați funcția ''simplify'', care primeste o funcție de step și o aplică până expresia rămâne în formă normală, și întoarce o listă cu toți pași intermediari ai reduceri. \\ |
- | **1.8.** (//5p//) Implementați funcția ''simplify'', care primeste o functie de step si o aplica pana expresie ramane in forma normala, si intoarce o lista cu toti pasi intermediari ai reduceri. \\ | + | |
===== 2. Parsing ===== | ===== 2. Parsing ===== | ||
- | Momentan putem să evaluăm expresii care le definim tot noi sub formă de cod, pentru a avea un interpretor funcțional, trebuie să putem lua expresii sub forma de șiruri de caractere și să le transformăm în **TDA**-uri (acest proces se numește **parsare**). \\ | + | Momentan putem să evaluăm expresii definite tot de noi sub formă de cod. Pentru a avea un interpretor funcțional, trebuie să putem lua expresii sub forma de șiruri de caractere și să le transformăm în **TDA**-uri (acest proces se numește **parsare**). \\ |
O gramatică pentru expresii lambda ar putea fi: \\ | O gramatică pentru expresii lambda ar putea fi: \\ | ||
<code> | <code> | ||
<lambda> ::= <variable> | '\' <variable> '.' <lambda> | (<lambda> <lambda>) | <lambda> ::= <variable> | '\' <variable> '.' <lambda> | (<lambda> <lambda>) | ||
- | <variable> ::= 'a' | 'b' | 'c' | ... | 'z' | + | <variable> ::= <variable><alpha> | <alpha> |
+ | <alpha> ::= 'a' | 'b' | 'c' | ... | 'z' | ||
</code> | </code> | ||
\\ | \\ | ||
- | **2.1.** (//50p//) Implementați funcția ''parse_lambda'' care parsează un ''String'' și returnează o expresie SAU o eroare (sub forma de ''String''). | + | **2.1.** (//40p//) Implementați funcția ''parseLambda'' care parsează un ''String'' și returnează o expresie |
<note important> | <note important> | ||
Line 101: | Line 105: | ||
===== 3. Steps towards a programming language ===== | ===== 3. Steps towards a programming language ===== | ||
- | Teoretic, folosind parserul și evaluatorul anterior, putem să evaluăm orice rezultat computabil, expresiile lambda fiind suficient de expresive, însă este foarte greu să scrii astfel de expresii. Pentru a fi mai ușor de folosit, vrem să introducem noțiunea de variabile. Pentru asta o să folosim conceptul de **macro**. Primul pas ar fi să extindem definiția unei expresii cu un constructor ''Macro'' care acceptă un ''String'' ca parametru (denumirea macro-ului). | + | Folosind parserul și evaluatorul anterior, putem să evaluăm orice rezultat computabil, expresiile lambda fiind suficient de expresive, însă, cum probabil ați văzut la curs și laborator, este foarte greu să scrii astfel de expresii. Pentru a fi mai ușor de folosit, vrem să putem denumi anumite sub-expresii pentru a le putea refolosi ulterior. Pentru asta o să folosim conceptul de **macro**. Primul pas ar fi să extindem definiția unei expresii cu un constructor ''Macro'' care acceptă un ''String'' ca parametru (denumirea macro-ului). O să introducem și sintaxa: orice șir de caractere format numai din litere mari ale alfabetului englez si cifre e considerat un macro. |
- | + | ||
- | Pentru a folosi un macro, introducem sintaxa: orice șir de caractere format numai din litere mari ale alfabetului englez si cifre e considerat un macro. | + | |
Câteva exemple de expresii cu macro-uri sunt: | Câteva exemple de expresii cu macro-uri sunt: | ||
Line 111: | Line 113: | ||
$ \lambda x.(NOT \ \lambda y.AND) $ \\ | $ \lambda x.(NOT \ \lambda y.AND) $ \\ | ||
- | Pentru a evalua o expresie cu macro-uri, introducem noțiunea de //**context computațional**//. Contextul în care evaluăm o expresie este pur și simplu un dicționar de nume de macro-uri și expresii pe care aceste nume le înlocuiesc. Astfel când evaluăm un macro, facem pur și simpu substituție textuală cu expresia găsită în dicționar. | + | Pentru a putea folosi macro-uri, trebuie să introducem noțiunea de //**context computațional**//. Contextul în care evaluăm o expresie este pur și simplu un dicționar de nume de macro-uri și expresii pe care aceste nume le înlocuiesc. Astfel când evaluăm un macro, facem pur și simpu substituție textuală cu expresia găsită în dicționar. |
- | **3.1.** (//10p//) Implementați funcția ''replace_macros'' care ia un context și o expresie care poate să conțină macro-uri, și întoarce expresia după evaluarea tuturor macro-urilor SAU o eroare in cazul in care o variabila nu a fost gasita (expresia returnata ar trebui sa nu mai contina macro-uri, ca sa putem folosi ''simplify'' implementat anterior). | + | În cazul în care nu găsim macro-ul în context, nu o să știm cum să evaluăm expresia, asa că am vrea să întoarcem o eroare. O să extindem tipul de date întors la ''Either String [Lambda]'' și o să întoarce ''Left'' în caz de eroare și ''Right'' în cazul în care evaluarea se termina cu succes. |
+ | |||
+ | **3.1.** (//15p//) Implementați funcția ''simplifyCtx'' care ia un context și o expresie care poate să conțină macro-uri, face substituțiile macro-urilor (sau returnează eroare dacă nu reușeste) și evaluează expresia rezultată folosind strategia de step primită. (**Hint:** putem refolosi ''simplify'' ca să nu rescriem logica?) | ||
<note info> | <note info> | ||
- | Codul atunci cand lucrezi cu ''Maybe'' sau ''Either'' poata sa devina complicat atunci cand faci ''case'' pe fiecare variabila sa verifici erorile, de asta exista o monada definita atat peste tipul de date ''Maybe'' cat si peste ''Either'', foloseste ''do'' notation sa iti usurezi viata. | + | Codul atunci când lucrezi cu ''Maybe'' sau ''Either'' poate să devina complicat dacă folosim ''case''-uri pe toate variabilele, pentru a ușura lucrul cu ele există monade definite atât peste tipul de date ''Maybe'' cât și peste ''Either'', poți folosi ''do'' notation să îți ușurezi viața. |
</note> | </note> | ||
Line 123: | Line 127: | ||
</note> | </note> | ||
- | Ca sa ne folosim de macro-uri ne trebuie si o metoda de a le defini. Pentru asta o sa definim conceptul de linie de cod: | + | Ultimul pas ca să ne putem folosi de macro-uri e să găsim o metodă de a le defini. Pentru asta o sa definim conceptul de linie de cod: |
<code haskell> | <code haskell> | ||
- | data Code = Code Lambda | + | data Line = Eval Lambda |
- | | Assign String Lambda | + | | Binding String Lambda |
</code> | </code> | ||
- | O linie de cod poate sa fie ori o expresie lambda, ori o definitie de macro. Astfel daca o sa evaluam mai multe linii de cod, in expresii o sa ne putem folosi de macro-urile definite anterior. | + | O linie de cod poate să fie ori o expresie lambda, ori o definiție de macro. Astfel daca o sa evaluam mai multe linii de cod, în expresii o sa ne putem folosi de macro-urile definite anterior. |
**3.2.** (//5p//) Modificați parser-ul vostru astfel încât să parsați și expresii care conțin macro-uri. | **3.2.** (//5p//) Modificați parser-ul vostru astfel încât să parsați și expresii care conțin macro-uri. | ||
- | **3.3.** (//5p//) Implementați funcția ''parse_code'' care să parseze o linie de cod, daca gaseste erori o sa intoarca o eroare (sub forma de ''String''). | + | **3.3.** (//5p//) Implementați funcția ''parseLine'' care să parseze o linie de cod, dacă găsește erori o să întoarcă o eroare (sub formă de ''String''). |
===== 4.Default Library ===== | ===== 4.Default Library ===== | ||
- | Acum ca avem un interpretor functional pentru calcul lambda, hai sa definim si cateva expresii uzuale, ca sa le putem folosi ca un context default pentru interpretorul nostru. | + | Acum că avem un interpretor funcțional pentru calcul lambda, hai să definim și câteva expresii uzuale, ca să le putem folosi ca un context default pentru interpretorul nostru (un fel de standard library). |
- | In fisierul ''Default.hs'' sunt deja definiti cativa combinatori. Hai sa definim si alte lucruri extra. | + | În fișierul ''Default.hs'' sunt deja definiti câțiva combinatori. Definiții restul expresiilor. |
- | **4.1.** (//5p//) Definiti ca expresii Lambda cateva macro-uri utile pentru lucrul cu Booleene (''TRUE'', ''FALSE'', ''AND'', ''OR'', ''NOT'', ''XOR''). | + | **4.1.** (//6p//) Definiți ca expresii lambda câteva macro-uri utile pentru lucrul cu Booleene (''TRUE'', ''FALSE'', ''AND'', ''OR'', ''NOT'', ''XOR''). |
- | **4.2.** (//5p//) Definiti ca expresii Lambda cateva macro-uri utile pentru lucrul cu perechi (''PAIR'', ''FIRST'', ''SECOND''). | + | **4.2.** (//4p//) Definiți ca expresii lambda câteva macro-uri utile pentru lucrul cu perechi (''PAIR'', ''FIRST'', ''SECOND''). |
- | **4.3.** (//10p//) Definiti ca expresii Lambda cateva macro-uri utile pentru lucrul cu numere naturale (''N0'', ''N1'', ''N2'', ''SUCC'', ''PRED'', ''ADD'', ''SUB'', ''MULT''). | + | **4.3.** (//5p//) Definiti ca expresii lambda câteva macro-uri utile pentru lucrul cu numere naturale (''N0'', ''N1'', ''N2'', ''SUCC'', ''PRED'', ''ADD'', ''SUB'', ''MULT''). |
<note> | <note> | ||
- | Pentru a rezolva cerinta **4** este nevoie ca cerinta **1** sa fie completa, pentru ca o sa ne folosim de ''simplify'' implementat de voi sa testam expresiile, deoarece vrem sa testam comportamentul lor, nu structura. | + | Pentru a fi punctați pentru cerința **4** este nevoie ca cerința **1** sa fie completată, pentru ca o sa ne folosim de ''simplify'' implementat de voi să testăm expresiile, deoarece vrem să testăm comportamentul lor, nu structura. |
</note> | </note> | ||
===== REPL ===== | ===== REPL ===== | ||
- | La finalul temei, o să puteți să rulați ''runhaskell main.hs'' pentru a porni un **REPL**, care se folosește de funcțiile și parserul făcute de voi. În acesta puteți să evaluați diverse expresii lambda, cum a fost prezentat anterior. | + | La finalul temei, puteți rula ''runhaskell main.hs'' pentru a vedea aplicația creată de voi :). O să pornească un **REPL** în care puteți scrie expresii lambda pentru a le evalua (main-ul se folosește de evaluarea normală implementată de voi), puteți crea binding-uri noi sau folosi binding-uri din contextul default creat. |
+ | |||
+ | Există și câteva comenzi utile: | ||
+ | * '':q'' - pentru a ieși din **REPL** | ||
+ | * '':r'' - pentru a sterge contextul, reluând contextul default | ||
+ | * '':ctx'' - pentru a afișa contextul curent | ||
===== Punctare ===== | ===== Punctare ===== | ||
Line 159: | Line 169: | ||
* 5p - **1.1.** - aflarea variabilelor | * 5p - **1.1.** - aflarea variabilelor | ||
* 5p - **1.2.** - aflarea variabilelor libere | * 5p - **1.2.** - aflarea variabilelor libere | ||
- | * 5p - **1.3.** - generarea unei noi variabile | + | * 10p - **1.3.** - generarea unei noi variabile |
- | * 20p - **1.4.** - reducerea unui redex | + | * 5p - **1.4.** - verificarea formei normale |
- | * 10p - **1.5.** - step normal | + | * 20p - **1.5.** - reducerea unui redex |
- | * 10p - **1.6.** - step aplicativ | + | * 10p - **1.6.** - step normal |
- | * 5p - **1.7.** - verificarea formei normale | + | * 10p - **1.7.** - step aplicativ |
- | * 10p - **1.8.** - reducerea unei expresii step by step | + | * 5p - **1.8.** - reducerea unei expresii step by step |
- Parsing | - Parsing | ||
- | * 50p - **2.1.** parsare | + | * 40p - **2.1.** parsare |
- Steps towards a programming language | - Steps towards a programming language | ||
- | * 10p - **3.1.** evaluarea unui macro | + | * 15p - **3.1.** evaluarea unei expresii cu macro-uri |
* 5p - **3.2.** parsarea expresiilor cu macro-uri | * 5p - **3.2.** parsarea expresiilor cu macro-uri | ||
* 5p - **3.3.** parsarea liniilor de cod | * 5p - **3.3.** parsarea liniilor de cod | ||
- Code | - Code | ||
- | * 5p - **4.1.** expresii pt boolene | + | * 6p - **4.1.** expresii boolene |
- | * 5p - **4.2.** expresii perechi | + | * 4p - **4.2.** expresii perechi |
- | * 10p - **4.2.** expresii numere naturale | + | * 5p - **4.2.** expresii numere naturale |
După cum s-a anunțat la începutul semestrului, pentru studenții care au punctaj maxim pe toate 3 temele de pe parcursul semestrului, o să se echivaleze examenul din sesiune cu punctaj maxim. | După cum s-a anunțat la începutul semestrului, pentru studenții care au punctaj maxim pe toate 3 temele de pe parcursul semestrului, o să se echivaleze examenul din sesiune cu punctaj maxim. | ||
Line 184: | Line 194: | ||
===== Testing ===== | ===== Testing ===== | ||
- | Pentru testare puteți rula un set de teste unitare cu ''runhaskell test.hs''. Pentru a testa doar o cerință, puteți da unul din argumentele [lambda | parser | code | default] pentru a rula cerințele 1, 2, 3 sau 4. | + | Pentru testare puteți rula un set de teste unitare cu ''runhaskell test.hs''. Pentru a testa doar o cerință, puteți da unul din argumentele [lambda | parser | binding | default] pentru a rula testele doar pentru cerința 1, 2, 3 sau 4. |
Pentru fiecare test v-a aparea **PASSED** / **FAILED**, și în caz de **FAILED**, diferențele între rezultatul vostru și cel dorit. | Pentru fiecare test v-a aparea **PASSED** / **FAILED**, și în caz de **FAILED**, diferențele între rezultatul vostru și cel dorit. | ||
Line 196: | Line 206: | ||
Temele trebuie submise pe curs.upb.ro, în assignment-ul ''Tema 3''. | Temele trebuie submise pe curs.upb.ro, în assignment-ul ''Tema 3''. | ||
- | În arhivă trebuie să se regăsească: | + | În arhivă trebuie să se regăsească //cel puțin//: |
- | * Code.hs | + | * ''Lambda.hs'' |
- | * Lambda.hs | + | * ''Parser.hs'' |
- | * Parser.hs | + | * ''Binding.hs'' |
- | * <alte fisiere>.hs | + | * ''ID.txt'' - acest fișier va conține o singură linie, formată din ID-ul unic al fiecărui student |
- | * ID.txt - acest fisier va contine o singura linie, formata din ID-ul unic al fiecarui student | + | |