This shows you the differences between two versions of the page.
pp:21:laboratoare:racket:intro [2021/02/28 17:02] bot.pp |
pp:21:laboratoare:racket:intro [2021/03/15 10:36] (current) bot.pp |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Racket: Introducere ====== | ====== Racket: Introducere ====== | ||
- | * Data publicării: 29.02.2021 | + | * Data publicării: 28.02.2021 |
- | * Data ultimei modificări: 29.02.2021 | + | * Data ultimei modificări: 28.02.2021 |
===== Obiective ===== | ===== Obiective ===== | ||
Line 43: | Line 43: | ||
==== Programarea funcțională ==== | ==== Programarea funcțională ==== | ||
- | Una dintre principalele diferențe aduse de programarea funcțională este absența **efectelor laterale**, și se datorează faptului că programarea funcțională este atemporală. Nu există atribuiri, nu există secvență de comenzi, o anumită expresie are o singură valoare pe tot parcursul programului. Elementul central este **funcția** (văzută însă nu în sens procedural, ci mai degrabă în sens matematic). Programele constau în compuneri și aplicări de funcții. Exemplu (limbajul //Haskell//): | + | Una dintre principalele diferențe aduse de programarea funcțională este absența **efectelor laterale**, și se datorează faptului că programarea funcțională este atemporală. Nu există atribuiri, nu există secvență de comenzi, o anumită expresie are o singură valoare pe tot parcursul programului. Elementul central este **funcția** (văzută însă nu în sens procedural, ci mai degrabă în sens matematic). Programele constau în compuneri și aplicări de funcții. Exemplu (limbajul ''%%Haskell%%''): |
<code> | <code> | ||
- | insertion_sort [] = [] insertion_sort (x:xs) = insert x (insertion_sort xs) | + | insertion_sort [] = [] |
- | insert y [] = [y] insert y (x:xs) = if y < x then (y:x:xs) else x:(insert y xs) | + | insertion_sort (x:xs) = insert x (insertion_sort xs) |
+ | |||
+ | insert y [] = [y] | ||
+ | insert y (x:xs) = if y < x then (y:x:xs) else x:(insert y xs) | ||
</code> | </code> | ||
Observați asemănarea izbitoare între codul Haskell și **definirea axiomelor unui TDA**, studiată la cursul de //Analiza Algoritmilor// funcțiile sunt definite pe cazurile de aplicare și folosesc recursivitatea pentru a referi un caz deja implementat. | Observați asemănarea izbitoare între codul Haskell și **definirea axiomelor unui TDA**, studiată la cursul de //Analiza Algoritmilor// funcțiile sunt definite pe cazurile de aplicare și folosesc recursivitatea pentru a referi un caz deja implementat. | ||
- | Funcția //**insertion_sort**// primește o listă ca parametru, și o sortează prin inserție. Dacă parametrul lui //insertion_sort// este lista vidă, atunci funcția va întoarce lista vidă (care este sortată trivial). Altfel, //insertion_sort// sortează recursiv sublista //xs//, apoi inserează pe poziția corespunzătoare în această listă (sortată) elementul //x//. | + | Funcția **''%%insertion_sort%%''** primește o listă ca parametru, și o sortează prin inserție. Dacă parametrul lui //insertion_sort// este lista vidă, atunci funcția va întoarce lista vidă (care este sortată trivial). Altfel, ''%%insertion_sort%%'' sortează recursiv sublista ''%%xs%%'', apoi inserează pe poziția corespunzătoare în această listă (sortată) elementul ''%%x%%''. |
- | Funcția //**insert**// primește doi parametri: | + | Funcția **''%%insert%%''** primește doi parametri: |
* un element | * un element | ||
* o listă sortată. | * o listă sortată. | ||
- | Dacă lista primită ca parametru este vidă, //insert// întoarce o listă cu un singur element (primul parametru). Altfel, introduce elementul //y// în listă, astfel încât sortarea să se conserve. | + | Dacă lista primită ca parametru este vidă, ''%%insert%%'' întoarce o listă cu un singur element (primul parametru). Altfel, introduce elementul ''%%y%%'' în listă, astfel încât sortarea să se conserve. |
- | Exemplul de mai sus este sugestiv pentru modul de construcție a programelor funcționale: rezultatul final se obține din rezultate intermediare, prin **compuneri și aplicări** de funcții. Definind funcția //insertion_sort// pe o listă nevidă, am observat că avem nevoie întâi să sortăm recursiv lista fără primul element, apoi să inserăm primul element "la locul lui" în lista sortată. Programarea funcțională **nu** ne permite secvențe de instrucțiuni de tipul "întâi fă asta, apoi fă cealaltă", așa că am reformulat secvența de comenzi într-o secvență de aplicări de funcții: "inserează primul element în rezultatul obținut prin sortarea restului". Nu este nicio problemă că încă nu avem o funcție care inserează un element într-o listă sortată; de fiecare dată când avem nevoie de un rezultat încă necalculat, ne putem **imagina** (//wishful thinking//) că avem deja o funcție care realizează calculul respectiv și putem apela acea funcție, urmând să o implementăm ulterior. Exact așa am procedat cu funcția //insert//. Această abordare ne face să spunem că programarea funcțională este de tip //[[https://en.wikipedia.org/wiki/Wishful_thinking|wishful thinking]]//. | + | Exemplul de mai sus este sugestiv pentru modul de construcție a programelor funcționale: rezultatul final se obține din rezultate intermediare, prin **compuneri și aplicări** de funcții. Definind funcția ''%%insertion_sort%%'' pe o listă nevidă, am observat că avem nevoie întâi să sortăm recursiv lista fără primul element, apoi să inserăm primul element "la locul lui" în lista sortată. Programarea funcțională **nu** ne permite secvențe de instrucțiuni de tipul "întâi fă asta, apoi fă cealaltă", așa că am reformulat secvența de comenzi într-o secvență de aplicări de funcții: "inserează primul element în rezultatul obținut prin sortarea restului". Nu este nicio problemă că încă nu avem o funcție care inserează un element într-o listă sortată; de fiecare dată când avem nevoie de un rezultat încă necalculat, ne putem **imagina** (//wishful thinking//) că avem deja o funcție care realizează calculul respectiv și putem apela acea funcție, urmând să o implementăm ulterior. Exact așa am procedat cu funcția ''%%insert%%''. Această abordare ne face să spunem că programarea funcțională este de tip //[[https://en.wikipedia.org/wiki/Wishful_thinking|wishful thinking]]//. |
Observați în exemplul de mai sus și **absența** efectelor laterale. Niciuna dintre funcții nu modifică zone de memorie din afara acesteia. | Observați în exemplul de mai sus și **absența** efectelor laterale. Niciuna dintre funcții nu modifică zone de memorie din afara acesteia. | ||
Line 75: | Line 78: | ||
* înrudit cu [[http://www.racket-lang.org/new-name.html|Scheme]] | * înrudit cu [[http://www.racket-lang.org/new-name.html|Scheme]] | ||
- | În centrul limbajului Racket se află **evaluarea expresiilor** constând în **aplicări de funcții**. Fie următoarea aplicare a funcției //max//, în **C**: ''%%int result;%%'' ''%%result = max (3,4);%%'' Comparativ, iată aplicarea funcției //max// în //Racket//: ''%%(max 3 4)%%'' În **C**, apelul de funcție se realizează direct prin nume; acesta este urmat de paranteze, iar între paranteze sunt enumerați parametrii. În **Racket**, paranteza deschisă indică exclusiv faptul că urmează o **aplicare de funcție**. Între paranteze se află numele funcției, urmat de parametri. Următoarea construcție: ''%%(max (+ 2 3) 4)%%'' se interpretează astfel: | + | În centrul limbajului Racket se află **evaluarea expresiilor** constând în **aplicări de funcții**. Fie următoarea aplicare a funcției //max//, în **C**: |
- | * evaluează aplicarea funcției //max//, care primește doi parametri | + | <code c> |
- | * primul parametru reprezintă o altă aplicare a funcției //+// cu parametrii //2// și //3// | + | int result; |
- | * aplicarea funcției //+// se evaluează la 5 | + | result = max (3,4); |
- | * funcția //max// determină maximul între două numere, iar aplicarea ei pe //5// și //4// se evaluează la 5. | + | |
+ | </code> | ||
+ | Comparativ, iată aplicarea funcției ''%%max%%'' în ''%%Racket%%'': | ||
+ | |||
+ | <code lisp> | ||
+ | (max 3 4) | ||
+ | |||
+ | </code> | ||
+ | În **C**, apelul de funcție se realizează direct prin nume; acesta este urmat de paranteze, iar între paranteze sunt enumerați parametrii. În **Racket**, paranteza deschisă indică exclusiv faptul că urmează o **aplicare de funcție**. Între paranteze se află numele funcției, urmat de parametri. Următoarea construcție: | ||
+ | |||
+ | <code lisp> | ||
+ | (max (+ 2 3) 4) | ||
+ | |||
+ | </code> | ||
+ | se interpretează astfel: | ||
+ | |||
+ | * evaluează aplicarea funcției ''%%max%%'', care primește doi parametri | ||
+ | * primul parametru reprezintă o altă aplicare a funcției ''%%+%%'' cu parametrii ''%%2%%'' și ''%%3%%'' | ||
+ | * aplicarea funcției ''%%+%%'' se evaluează la 5 | ||
+ | * funcția ''%%max%%'' determină maximul între două numere, iar aplicarea ei pe ''%%5%%'' și ''%%4%%'' se evaluează la 5. | ||
Racket este un limbaj în care argumentele sunt transmise funcției prin valoare (**call-by-value**), astfel că prima expresie evaluată în exemplul de mai sus este (+ 2 3). | Racket este un limbaj în care argumentele sunt transmise funcției prin valoare (**call-by-value**), astfel că prima expresie evaluată în exemplul de mai sus este (+ 2 3). | ||
- | Următoarea construcție: ''%%(max (3) 4)%%'' este **invalidă**. **Racket** va interpreta //(3)// ca pe o tentativă de a aplica funcția cu numele //3// pe zero parametri. Codul va genera eroare. | + | Următoarea construcție: |
- | **Exercițiu**: încercați să priviți orice construcție din limbajul Racket ca pe o funcție. De exemplu, //if//: ''%%(if (= 2 3) 2 (max 2 3))%%'' Putem observa faptul că //if// se comportă ca o funcție cu trei parametri: | + | <code lisp> |
+ | (max (3) 4) | ||
+ | |||
+ | </code> | ||
+ | este **invalidă**. | ||
+ | |||
+ | **Racket** va interpreta ''%%(3)%%'' ca pe o tentativă de a aplica funcția cu numele ''%%3%%'' pe zero parametri. Codul va genera eroare. | ||
+ | |||
+ | **Exercițiu**: încercați să priviți orice construcție din limbajul Racket ca pe o funcție. De exemplu, ''%%if%%'': | ||
+ | |||
+ | <code lisp> | ||
+ | (if (= 2 3) 2 (max 2 3)) | ||
+ | |||
+ | </code> | ||
+ | Putem observa faptul că ''%%if%%'' se comportă ca o funcție cu trei parametri: | ||
* primul parametru este **condiția**. Condiția reprezintă o aplicare de funcție, care întoarce (se evaluează la) ''%%true%%'' sau ''%%false%%'' | * primul parametru este **condiția**. Condiția reprezintă o aplicare de funcție, care întoarce (se evaluează la) ''%%true%%'' sau ''%%false%%'' | ||
Line 92: | Line 128: | ||
* al treilea parametru reprezintă expresia de evaluat când condiția este ''%%false%%'' | * al treilea parametru reprezintă expresia de evaluat când condiția este ''%%false%%'' | ||
- | Cum 2 este diferit de 3, codul de mai sus va întoarce al treilea parametru (evaluarea expresiei //(max 2 3)//). | + | Cum 2 este diferit de 3, codul de mai sus va întoarce al treilea parametru (evaluarea expresiei ''%%(max 2 3)%%''). |
===== Tipuri de date ===== | ===== Tipuri de date ===== | ||
Line 111: | Line 147: | ||
==== Simboluri ==== | ==== Simboluri ==== | ||
- | Simbolurile (numite și literali) sunt valori formate din unul sau mai multe caractere, fără spațiu. Diferențierea dintre un nume (care este legat la o valoare, similar unei variabile din limbajele imperative) și un simbol se face atașând în fața valorii simbolului un apostrof: //'simbol//. | + | Simbolurile (numite și literali) sunt valori formate din unul sau mai multe caractere, fără spațiu. Diferențierea dintre un nume (care este legat la o valoare, similar unei variabile din limbajele imperative) și un simbol se face atașând în fața valorii simbolului un apostrof: ''%%'simbol%%''. |
- | **Atenție!** Apostroful în fața unui simbol (sau, vedem mai jos, a unei expresii în paranteză) este un operator, echivalent cu funcția //quote//, care determină ca simbolul sau expresia care îi urmează să **nu** fie evaluată. Astfel, //'simbol// se valuează la un simbol și nu se încearcă evaluarea unei variabile cu numele //simbol// și găsirea unei valori asociate acestui nume. | + | **Atenție!** Apostroful în fața unui simbol (sau, vedem mai jos, a unei expresii în paranteză) este un operator, echivalent cu funcția ''%%quote%%'', care determină ca simbolul sau expresia care îi urmează să **nu** fie evaluată. Astfel, ''%%'simbol%%'' se valuează la un simbol și nu se încearcă evaluarea unei variabile cu numele ''%%simbol%%'' și găsirea unei valori asociate acestui nume. |
==== Perechi ==== | ==== Perechi ==== | ||
- | O pereche este un tuplu de două elemente, care pot avea tipuri diferite. Pentru manipularea perechilor, Racket ne pune la dispoziție un constructor (//cons//) și doi selectori (//car// și //cdr//). Utilizarea acestora este demonstrată în exemplele de mai jos: | + | O pereche este un tuplu de două elemente, care pot avea tipuri diferite. Pentru manipularea perechilor, Racket ne pune la dispoziție un constructor (''%%cons%%'') și doi selectori (''%%car%%'' și ''%%cdr%%''). Utilizarea acestora este demonstrată în exemplele de mai jos: |
<code lisp> | <code lisp> | ||
Line 131: | Line 167: | ||
==== Liste ==== | ==== Liste ==== | ||
- | Denumirea "Lisp" a limbajului părinte al Racket-ului provine de la "List Processing", și într-adevăr lista este o structură de bază în cele două limbaje. Mulțumită faptului că se pot construi perechi eterogene (între elemente de tipuri diferite, de exemplu între un element și o listă), Racket implementează orice listă nevidă ca pe o pereche între primul element și restul listei. Astfel, tipul //listă// **împrumută** de la tipul //pereche// constructorul //cons// și selectorii //car// și //cdr//, la care se adaugă constructorul //null// pentru **lista vidă**. Exemple: ` | + | Denumirea "Lisp" a limbajului părinte al Racket-ului provine de la "List Processing", și într-adevăr lista este o structură de bază în cele două limbaje. Mulțumită faptului că se pot construi perechi eterogene (între elemente de tipuri diferite, de exemplu între un element și o listă), Racket implementează orice listă nevidă ca pe o pereche între primul element și restul listei. Astfel, tipul //listă// **împrumută** de la tipul //pereche// constructorul ''%%cons%%'' și selectorii ''%%car%%'' și ''%%cdr%%'', la care se adaugă constructorul ''%%null%%'' pentru **lista vidă**. Exemple: |
<code lisp> | <code lisp> | ||
Line 139: | Line 175: | ||
</code> | </code> | ||
- | Funcția **list** construiește o listă nouă care va conține elementele date ca argumente funcției: | + | **Funcția** ''%%list%%'' construiește o listă nouă care va conține elementele date ca argumente funcției: |
<code lisp> | <code lisp> | ||
Line 145: | Line 181: | ||
</code> | </code> | ||
- | Astfel, putem construi lista (1 2 3 4) fie folosind apostroful -- //'(1 2 3 4)// -- fie folosind funcția //list// -- //(list 1 2 3 4)//, dar apostroful nu poate substitui oricând funcția //list//, după cum se observă în exemplele de mai jos: | + | Astfel, putem construi lista (1 2 3 4) fie folosind apostroful -- ''%%'(1 2 3 4)%%'' -- fie folosind funcția ''%%list%%'' -- ''%%(list 1 2 3 4)%%'', dar apostroful nu poate substitui oricând funcția ''%%list%%'', după cum se observă în exemplele de mai jos: |
<code lisp> | <code lisp> | ||
Line 159: | Line 195: | ||
</code> | </code> | ||
- | Funcțiile //car// și //cdr// pot fi compuse pentru a obține diverse elemente ale listei. Exemple: | + | Funcțiile ''%%car%%'' și ''%%cdr%%'' pot fi compuse pentru a obține diverse elemente ale listei. Exemple: |
<code lisp> | <code lisp> | ||
Line 167: | Line 203: | ||
</code> | </code> | ||
- | Racket permite forme prescurtate pentru compuneri de funcții de tip //car// și //cdr//. Rescriem exemplele de mai sus folosind aceste forme prescurtate: | + | Racket permite forme prescurtate pentru compuneri de funcții de tip ''%%car%%'' și ''%%cdr%%''. Rescriem exemplele de mai sus folosind aceste forme prescurtate: |
<code lisp> | <code lisp> | ||
Line 191: | Line 227: | ||
===== Legarea variabilelor ===== | ===== Legarea variabilelor ===== | ||
- | Un identificator poate fi legat la o valoare folosind (printre altele) construcția //(define identificator valoare)//. Efectul //define//-ului este de a permite referirea unei expresii (adesea complexă) cu ajutorul unui nume concis, nu acela de a atribui o valoare unei variabile. În urma //define//-urilor **nu** se suprascriu valori la anumite locații din memorie. Într-un program Racket nu se poate face //define// de mai multe ori la același simbol). | + | Un identificator poate fi legat la o valoare folosind (printre altele) construcția ''%%(define identificator valoare)%%''. Efectul ''%%define%%''-ului este de a permite referirea unei expresii (adesea complexă) cu ajutorul unui nume concis, nu acela de a atribui o valoare unei variabile. În urma ''%%define%%''-urilor **nu** se suprascriu valori la anumite locații din memorie. Într-un program Racket nu se poate face ''%%define%%'' de mai multe ori la același simbol). |
<code lisp> | <code lisp> | ||
Line 202: | Line 238: | ||
===== Funcții anonime (lambda) ===== | ===== Funcții anonime (lambda) ===== | ||
- | O funcție anonimă se definește utilizând cuvântul cheie //lambda//. Sintaxa este: ''%%(lambda (arg1 arg2 ...) ce_întoarce_funcția)%%''. | + | O funcție anonimă se definește utilizând cuvântul cheie ''%%lambda%%''. Sintaxa este: ''%%(lambda (arg1 arg2 ...) ce_întoarce_funcția)%%''. |
<code lisp> | <code lisp> | ||
Line 232: | Line 268: | ||
</code> | </code> | ||
- | Putem oricând scrie //λ// în loc de //lambda// (folosind //Ctrl+//). | + | Putem oricând scrie ''%%λ%%'' în loc de ''%%lambda%%'' (folosind ''%%Ctrl+\%%''). |
===== Funcții utile ===== | ===== Funcții utile ===== | ||
Line 283: | Line 319: | ||
* programarea este de tip **wishful thinking** | * programarea este de tip **wishful thinking** | ||
* secvența de instrucțiuni devine **compunere de funcții** (secvență de aplicări de funcții) | * secvența de instrucțiuni devine **compunere de funcții** (secvență de aplicări de funcții) | ||
- | * lipsesc atribuirile (variabilă = valoare) și instrucțiunile de ciclare precum //for// sau //while//, dar puteți obține același efect folosind **funcții recursive**. În loc de ciclare și atribuiri (adică în loc să ținem starea curentă a problemei în variabile), vom folosi recursivitate și starea curentă a problemei se va pasa ca parametru în funcțiile recursive. Citiți și recitiți acest paragraf în tandem cu exemplele de mai jos. | + | * lipsesc atribuirile (variabilă = valoare) și instrucțiunile de ciclare precum ''%%for%%'' sau ''%%while%%'', dar puteți obține același efect folosind **funcții recursive**. În loc de ciclare și atribuiri (adică în loc să ținem starea curentă a problemei în variabile), vom folosi recursivitate și starea curentă a problemei se va pasa ca parametru în funcțiile recursive. Citiți și recitiți acest paragraf în tandem cu exemplele de mai jos. |
* scrierea funcțiilor recursive derivă direct din **scrierea axiomelor** TDA-urilor implicate în problemă | * scrierea funcțiilor recursive derivă direct din **scrierea axiomelor** TDA-urilor implicate în problemă | ||
- | **Exemplul 1**: o funcție care calculează factorialul unui număr natural n. Știm că TDA-ul Natural are doi constructori de bază, //0// și //succ//. Scriem axiomele operatorului factorial: | + | **Exemplul 1**: o funcție care calculează factorialul unui număr natural n. Știm că TDA-ul Natural are doi constructori de bază, ''%%0%%'' și ''%%succ%%''. Scriem axiomele operatorului factorial: |
<code> | <code> | ||
Line 331: | Line 367: | ||
===== Resurse ===== | ===== Resurse ===== | ||
- | * {{ intro-ex.zip |Exerciții}} | + | * [[https://ocw.cs.pub.ro/courses/_media/pp/21/laboratoare/racket/intro-skel.zip|Schelet]] |
- | * {{ intro-sol.zip |Soluții}} | + | * [[https://ocw.cs.pub.ro/courses/_media/pp/21/laboratoare/racket/intro-sol.zip|Soluții]] |
- | * {{ intro-cheatsheet.pdf |Cheatsheet}} | + | * [[https://github.com/cs-pub-ro/PP-laboratoare/raw/master/racket/intro/racket-cheatsheet-1.pdf|Cheatsheet]] |
===== Referinţe ===== | ===== Referinţe ===== | ||
Line 345: | Line 381: | ||
* [[https://en.wikipedia.org/wiki/Strong_typing|Tipare strong]] | * [[https://en.wikipedia.org/wiki/Strong_typing|Tipare strong]] | ||
* [[https://en.wikipedia.org/wiki/Eager_evaluation|Evaluare aplicativă]] | * [[https://en.wikipedia.org/wiki/Eager_evaluation|Evaluare aplicativă]] | ||
- | * [[https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-user/Coding-style.html|Scheme coding style]] | + | * [[https://www.gnu.org/software/mit-scheme/documentation/stable/mit-scheme-user/Coding-style.html|Scheme coding style]] |