Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:l01 [2020/02/05 15:48] dmihai |
pp:l01 [2020/04/07 15:05] (current) pdmatei |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Haskell: Introducere ====== | + | ====== 1. Introduction to the Haskell language ====== |
- | Scopul acestui laborator este de a familiariza studenții cu limbajul Haskell. | + | The main (and, as we shall soon see, the only!) programming instrument in Haskell is the **function**. In mathematics, a function is a **relation** between specified sets ($math[f:A\rightarrow B]) that associates to every element of $math[A], exactly one element of $math[B]. This is precisely the interpretation of a function in Haskell. (Not to be confused with OOP **methods** or **procedures**). |
- | ===== Introducere ===== | + | The identity function is defined in Haskell as: |
- | + | <code haskell> | |
- | Haskell este un limbaj de programare [[https://en.wikipedia.org/wiki/Functional_programming|pur-funcțional]]. În limbajele imperative, programele sunt secvențe de instrucțiuni pe care calculatorul le execută ținând cont de stare, care se poate modifica în execuție. În limbajele funcționale nu există o stare, iar funcțiile nu au efecte secundare (e.g. nu pot modifica o variabilă globală), garantând că rezultatul depinde doar de argumentele date: aceeași funcție apelată de două ori cu aceleași argumente, întoarce același rezultat. Astfel, demonstrarea corectitudinii unui program este mai facilă. | + | f x = x |
- | + | ||
- | ===== Platforma Haskell ===== | + | |
- | + | ||
- | Pentru început, avem nevoie de un mod de a compila cod. Puteți descărca platforma Haskell de [[https://www.haskell.org/platform|aici]] (Windows/OS X/Linux) sau puteți instala pachetul "ghc". | + | |
- | + | ||
- | Vom lucra în modul interactiv, care ne permite să apelăm funcții din consolă și să vedem rezultatul lor. Pentru modul interactiv, rulați ''ghci''. | + | |
- | + | ||
- | Pentru informații suplimentare legate de GHCi, vă recomandăm să citiți pagina de wiki despre [[pp:haskell-environment|mediul de lucru în Haskell]]. | + | |
- | ===== Tipuri de date în Haskell ===== | + | |
- | + | ||
- | Haskell este un limbaj tipat static ([[http://wiki.c2.com/?StaticTyping|statically typed]]) care poate face inferență de tip ([[https://en.wikipedia.org/wiki/Type_inference|type inference]]). Asta înseamnă că tipul expresiilor este cunoscut la //compilare//; dacă nu este explicit, compilatorul îl //deduce//. Deasemenea, Haskell este tare tipat ([[https://en.wikipedia.org/wiki/Strong_and_weak_typing|strongly typed]]), ceea ce înseamnă că trebuie să existe conversii explicite între diferite tipuri de date. | + | |
- | + | ||
- | ==== Tipuri de bază ==== | + | |
- | + | ||
- | Haskell are tipuri asemănătoare celor din alte limbaje studiate până acum, ca C, Java etc: Int, Integer (întregi de dimensiune arbitrară), Float, Double, Char (cu apostrofuri, e.g. 'a'), Bool. | + | |
- | + | ||
- | <code> | + | |
- | Prelude> :t 'a' | + | |
- | 'a' :: Char | + | |
- | Prelude> :t True | + | |
- | True :: Bool | + | |
- | Prelude> :t 0 == 1 | + | |
- | 0 == 1 :: Bool | + | |
- | Prelude> :t 42 | + | |
- | 42 :: Num a => a | + | |
</code> | </code> | ||
- | <note>"::" se citește ca "are tipul". Deci, de exemplu, "True are tipul Bool".</note> | + | 1. Write a constant function in Haskell. |
- | Tipul lui ''42'' nu pare să fie ceva intuitiv, ca ''Int''. Deocamdată e suficient să menționăm faptul că, în Haskell, constantele numerice pot să se comporte ca diferite tipuri, în funcție de context. ''42'' poate să fie considerat întreg, în expresii ca ''42 + 1'', sau numărul în virgulă mobilă ''42.0'' în expresii ca ''42 * 3.14''. | + | 2. Write the Haskell implementation of the function below: $math[f(x,y) = x] (the //Ox// projection function) |
- | ==== Liste ==== | + | In mathematics, functions have a domain an codomain. In Haskell, functions have **types** or **signatures**. They often can be omitted in Haskell, but can |
+ | also be explicitly written as in: | ||
- | Listele sunt structuri de date omogene (i.e. pot conține doar elemente de același tip). O listă este delimitată de paranteze pătrate, cu elementele sale separate prin virgulă: | + | <code haskell> |
- | <code>[1, 2, 3, 4]</code> | + | f :: Integer -> Integer -> Integer |
- | + | f x y = x + y | |
- | Lista vidă este ''[]''. | + | |
- | + | ||
- | Tipul unei liste este dat de tipul elementelor sale; o listă de întregi are tipul ''[Int]''. Putem avea liste de liste de liste de liste ... atâta timp cât respectăm condiția de a avea același tip. | + | |
- | <code> | + | |
- | [['H', 'a'], ['s'], ['k', 'e', 'l', 'l']] -- corect, are tipul [[Char]] | + | |
- | [['N', 'u'], [True, False], ['a', 's', 'a']] -- greșit, conține și elemente de tipul [Bool] și de tipul [Char] | + | |
</code> | </code> | ||
- | <note> | + | or: |
- | Pentru a reprezenta șiruri de caractere, putem folosi tipul ''String'' care este un alias peste ''[Char]''.\\ | + | <code haskell> |
- | Astfel, ''"String"'' este doar un mod mai lizibil de a scrie\\ | + | f :: Bool -> Bool |
- | ''['S', 't', 'r', 'i', 'n', 'g']'', care, la rândul său, este un mod mai lizibil de a scrie\\ | + | f True = False |
- | '''S':'t':'r':'i':'n':'g':[]'' | + | f False = True |
- | </note> | + | |
- | + | ||
- | ==== Tupluri ==== | + | |
- | + | ||
- | Tuplurile sunt structuri de date eterogene (i.e. pot conține elemente de diferite tipuri). Un tuplu este delimitat de paranteze rotunde, cu elementele sale separate prin virgulă: | + | |
- | <code>(1, 'a', True)</code> | + | |
- | + | ||
- | Tipul unui tuplu este dat de numărul, ordinea și tipul elementelor sale: | + | |
- | <code> | + | |
- | Prelude> :t (True, 'a') | + | |
- | (True, 'a') :: (Bool, Char) | + | |
- | Prelude> :t ('a', True) | + | |
- | ('a', True) :: (Char, Bool) | + | |
- | Prelude> :t ("sir", True, False) | + | |
- | ("sir", True, False) :: ([Char], Bool, Bool) | + | |
</code> | </code> | ||
- | ===== Funcții în Haskell ===== | + | 3. Write a function together with its signature, which implements boolean AND: |
- | + | ||
- | ==== Apelare ==== | + | |
- | + | ||
- | În Haskell, funcțiile pot fi prefixate sau infixate. Cele prefix sunt mai comune, au un nume format din caractere alfanumerice și sunt apelate prin numele lor și lista de argumente, toate separate prin spații (fără paranteze sau virgule): | + | |
- | + | ||
- | <code> | + | |
- | Prelude> max 1 2 | + | |
- | 2 | + | |
- | </code> | + | |
- | + | ||
- | Funcțiile infixate sunt cele cu nume formate din caractere speciale, de forma //operand1 operator operand2// | + | |
- | + | ||
- | <code> | + | |
- | Prelude> 1 + 2 | + | |
- | 3 | + | |
- | </code> | + | |
- | + | ||
- | <note> | + | |
- | În anumite situații ne-am dori să schimbăm modul de afixare al funcțiilor (e.g. din motive de claritate).\\ | + | |
- | Pentru a prefixa o funcție infixată, folosim operatorul ''()''. Astfel, următoarele expresii sunt echivalente: | + | |
- | <code> | + | |
- | 3 * 23 | + | |
- | (*) 3 23 | + | |
- | </code> | + | |
- | + | ||
- | Dacă o funcție are două argumente, putem să o infixăm cu operatorul ''``'' (//backticks// - dacă nu aveți un layout exotic, ar trebui să fie pe tasta de dinainte de 1). Astfel, următoarele două expresii sunt echivalente: | + | |
<code haskell> | <code haskell> | ||
- | mod 10 3 | + | myand :: Bool -> Bool -> Bool |
- | 10 'mod' 3 | + | ... |
</code> | </code> | ||
- | În loc de ' (apostrof) trebuie folosit simbolul ''``'' (backtick) - pagina de wiki nu permite inserarea ''``'' într-o secțiune de cod. | ||
- | </note> | ||
- | |||
- | ==== Definirea unei funcții ==== | ||
- | Creați, în editorul de text preferat, un fișier cu extensia ''.hs'' în care vom defini prima funcție: | + | 4. What is the **signature** of the solutions for exercises 1. 2. ? Use '':t'' to find out. What does ''a'' mean? |
+ | 5. Write an implementation for: | ||
<code haskell> | <code haskell> | ||
- | -- intoarce modulul dublului celui mai mare dintre cele doua argumente | + | if' :: Bool -> a -> a -> a |
- | myFunc x y = abs (2 * max x y) | + | |
</code> | </code> | ||
+ | - How many parameters does ''if' '' take? | ||
+ | - What is the interpretation of each parameter? | ||
+ | - What should ''if' '' return? | ||
- | <note important> | ||
- | Rolul parantezelor aici este de a forța ordinea dorită de a efectua operațiilor. Altfel funcția ar înmulți 2 (modulul lui 2) cu cel mai mare dintre numere, pentru că aplicarea funcției are precedență mai mare. | ||
- | </note> | ||
- | |||
- | Puteți testa funcția din ghci: | ||
- | <code> | ||
- | Prelude> :l first.hs | ||
- | [1 of 1] Compiling Main ( first.hs, interpreted ) | ||
- | Ok, modules loaded: Main. | ||
- | *Main> myFunc 10 11 | ||
- | 22 | ||
- | *Main> myFunc (-10) (-11) | ||
- | 20 | ||
- | *Main> myFunc 3.14 2.71 | ||
- | 6.28 | ||
- | </code> | ||
- | |||
- | <note tip> | ||
- | Dacă folosiți o versiune mai veche de ghci, aveți nevoie de cuvântul cheie "let" pentru a defini o funcție direct în interpretor: | ||
- | <code> | ||
- | Prelude>let myFunc x y = abs (2 * max x y) | ||
- | </code> | ||
- | </note> | ||
- | |||
- | Observăm că funcția noastră merge și pentru numere întregi și pentru numere în virgulă mobilă. Am precizat mai devreme că orice expresie trebuie să aibă un tip, cunoscut la **compilare**. Cum noi nu am precizat tipul funcției noastre, compilatorul l-a dedus ca fiind: | ||
- | |||
- | <code> | ||
- | Prelude> :t myFunc | ||
- | myFunc :: (Num a, Ord a) => a -> a -> a | ||
- | </code> | ||
- | |||
- | Fără a intra în detalii, funcția noastră ia ca argumente două numere de același tip care pot fi ordonate și întoarce un rezultat de același tip. Metoda prin care se realizează această deducție va fi studiată în continuare la PP. | ||
- | |||
- | <note> | ||
- | Deși nu e întotdeauna necesar, specificarea manuală a tipului unei funcții e considerată "good practice". Editați fișierul de mai devreme astfel încât să arate așa: | ||
+ | 6. Write a function which takes three integers and returns the largest. Hint - sometimes parentheses are useful. | ||
<code haskell> | <code haskell> | ||
- | -- intoarce modulul dublului celui mai mare dintre cele doua argumente | + | f :: Integer -> Integer -> Integer -> Integer |
- | myFunc :: Int -> Int -> Int | + | |
- | myFunc x y = abs (2 * max x y) | + | |
</code> | </code> | ||
- | Observați că, acum, tipul arătat de '':t'' este cel specificat și primiți o eroare dacă încercați să pasați ca argumente numere în virgulă mobilă. | + | 7. What is the type of the function ''f'' defined below: |
- | </note> | + | |
- | + | ||
- | + | ||
- | ==== Pattern | + | |
- | matching ==== | + | |
- | + | ||
- | Vom scrie acum o altă funcție prin care ne propunem să calculăm, în mod recursiv, suma tuturor elementelor dintr-o listă. Pentru o listă vidă aceasta este 0, altfel este capul listei plus suma celorlalte elemente. | + | |
<code haskell> | <code haskell> | ||
- | {- | + | f x y z = if x then y else z |
- | - if este o expresie; cum toate expresiile | + | |
- | - trebuie sa intoarca o valoare, ramura else | + | |
- | - este necesara | + | |
- | - | + | |
- | - exista deja o functie "sum" predefinita si | + | |
- | - vrem sa evitam ambiguitatea apelului, motiv | + | |
- | - pentru care folosim un nume nou. | + | |
- | - (e interesat de mentionat ca, in Haskell, | + | |
- | - apostroful este un caracter valid in numele | + | |
- | - unei functii, i.e. am putea defini o functie | + | |
- | - sum'; din pacate, aici, pe wiki, acest lucru | + | |
- | - nu este suportat). | + | |
- | -} | + | |
- | mySum l = if null l | + | |
- | then 0 | + | |
- | else head l + mySum (tail l) | + | |
</code> | </code> | ||
- | Deși funcționează, implementarea de mai sus nu este foarte elegantă. Putem să ne folosim de pattern matching (ca la TDA-uri). | ||
+ | 8. What is the type of: | ||
<code haskell> | <code haskell> | ||
- | mySum2 [] = 0 | + | f x y z |
- | mySum2 (x:xs) = x + mySum2 xs | + | | x == True = y |
+ | | otherwise = z | ||
</code> | </code> | ||
+ | In the above implementation, the body (or expression) of the function ''f'' is defined similarly to a //branch-definition// in mathematics. In Haskell, this is called a //guard//. Guards have the syntax '' | <boolean condition> = <body expression>'', and are evaluated in the order in which they are defined. Whenever the first boolean condition is true, the body expression is returned. | ||
- | <note important> | ||
- | Ordinea din fișier este importantă. Patternurile sunt evaluate de sus în jos și ar trebui scrise de la cel mai particular la cel mai general (ultimul fiind un "catch-all"). | ||
- | La primul pattern match se va evalua expresia, iar următoarele pattern-uri nu vor mai fi verificate. | + | 9. Solve exercise 6. in a different way. (''&&'' may be helpful in boolean conditions). |
- | O excepție de la acest caz este utilizarea //guard-urilor// care nu conțin //otherwise//. Mai multe detalii [[http://learnyouahaskell.com/syntax-in-functions|aici]]. | + | 10. What is the type of ''[1,2,3]'' ? |
- | </note> | + | * Is ''1:[2,3]'' the same thing as ''1:2:3:[]'' ? |
+ | * Is ''1:(2:[])'' the same thing as ''(1:2):[]'' ? | ||
- | Dacă lista dată ca argument e vidă, atunci se face match pe primul pattern și rezultatul este 0. Altfel, se trece la pattern-ul următor; aici se fac două legări - capul listei este legat de numele ''x'', iar restul de numele ''xs'' (parantezele din jurul ''x:xs'' sunt necesare) și este apelată funcția în mod recursiv. | + | 11. Solve exercise 6 in a different way. (the function ''sort'' may be helpful, as well as the functions ''head'', ''tail'' and ''reverse''. Use '':t'' on each one, and test them). |
+ | 12. Implement a list reversal function. | ||
- | ==== Tail r | + | 13. Write a function which extracts the third to last number from a list and returns ''True'', if that number is odd (hint: the function ''mod'' may be useful) |
- | ecursion ==== | + | |
- | + | ||
- | Pentru a știi unde să întoarcă execuția după apelul unei funcții, un program ține adresele apelanților pe o stivă. | + | |
- | Astfel, după ce execuția funcției se termină, programul se întoarce la apelant pentru a continua secvența de instrucțiuni. | + | |
- | + | ||
- | În exemplul nostru, apelul ''mySum2 [1, 2, 3, 4, 5]'' se va evalua în felul următor: | + | |
<code> | <code> | ||
- | mySum2 [1, 2, 3, 4, 5] | + | V |
- | 1 + mySum2 [2, 3, 4, 5] | + | f [3,4,5,2,3,9] = False |
- | 1 + (2 + mySum2 [3, 4, 5]) | + | f [3,4,2,1,4,4] = True |
- | 1 + (2 + (3 + mySum2 [4, 5])) | + | |
- | 1 + (2 + (3 + (4 + mySum2 [5]))) | + | |
- | 1 + (2 + (3 + (4 + (5 + mySum2 [])))) | + | |
- | 1 + (2 + (3 + (4 + (5 + 0)))) | + | |
- | 15 | + | |
</code> | </code> | ||
- | Astfel, pentru a aduna capul listei la suma cozii, trebuie mai întâi calculată această sumă, iar apoi să se revină în funcția apelantă. | + | 14. Implement a function which returns the sum of integers from a list. |
- | Putem folosi un acumulator transmis ca parametru la funcția apelată, eliminând nevoia de a mai ține pe stivă informați despre apelant. | + | 15. Implement a function which takes a list of booleans and returns false if **at least** one boolean from the list is false. |
- | <note important> | + | 16. Implement a function which filters out all odd numbers from a list. |
- | Folosirea unui acumulator în acest scop este un tipar des întâlnit, util pentru că poate permite reducerea spațiului de stivă necesar de la O(n) la O(1) (unde n reprezintă lungimea listei). Unele limbaje (e.g. C) nu garantează această optimizare, care depinde de compilator.\\ | + | |
- | + | ||
- | Modul în care Haskell asigură tail-recursion o să fie mai clar când vom discuta despre modul de evaluare al funcțiilor. Tot atunci vom vedea și **capcanele** acestuia. | + | |
- | </note> | + | |
+ | 17. Implement a function which takes a list of booleans and returns a list of integers. In the latter, (''True'' becomes ''1'' and ''False'' becomes ''0''). Example: ''f [False, True, False] = [0,1,0]''. | ||
+ | 18. Sometimes it is helpful to define auxiliary functions which are not used later in our program. For instance: | ||
<code haskell> | <code haskell> | ||
- | -- Folosim sintaxa "let ... in" pentru a ascunde functia | + | f :: [[Integer]] -> [Bool] |
- | -- auxiliara folosita si a pastra forma functiei sum (un singur | + | f [] = [] |
- | -- argument) | + | f l = (g (head l)):(f (tail l)) |
- | mySum3 l = let sumAux [] acc = acc | + | where |
- | sumAux (x:xs) acc = sumAux xs (x + acc) | + | g [] = True |
- | in sumAux l 0 | + | g l = h (tail l) |
- | </code> | + | h [] = True |
+ | h l = False | ||
+ | </code> | ||
+ | What does the previous function do? Test it. | ||
- | ===== Exerciții ===== | + | 19. Implement a function which takes a list of booleans and returns the sum of ''True'' values from the list. Example ''f [False,True,False,False,True] = 2''. |
- | 1. Scrieți o funcție cu care să determinați factorialul unui număr. Scrieți o implementare straight-forward, fără restricții, apoi încercați să o faceți tail-recursive. | + | 20. Implement insertion sort. |
- | + | <code haskell> | |
- | + | inssort :: [Integer] -> [Integer] | |
- | 2. Scrieți o funcție care primește un număr ''n'' și întoarce al ''n''-lea număr din șirul lui Fibonacci (''1, 1, 2, 3, 5...'' unde primul ''1'' are indexul ''0''). Scrieți o implementare straight-forward, fără restricții, apoi încercați să o faceți tail-recursive. | + | inssort [] = |
- | + | inssort l = | |
- | + | ||
- | 3. a) Scrieți o funcție ''cat'' care primește două liste de același tip și returnează concatenarea lor. Funcția ar trebui să funcționeze exact ca operatorul ''++'' deja existent în Haskell. | + | |
- | + | ||
- | <code> | + | |
- | Prelude> :t (++) | + | |
- | (++) :: [a] -> [a] -> [a] | + | |
- | Prelude> [1, 2, 3] ++ [4, 5, 6] | + | |
- | [1,2,3,4,5,6] | + | |
</code> | </code> | ||
- | b) Scrieți o funcție ''inv'' care primește o listă și întoarce inversul ei. Funcția ar trebui să funcționeze exact ca funcția ''reverse'' deja existentă în Haskell. | ||
- | |||
- | <code> | ||
- | Prelude> :t reverse | ||
- | reverse :: [a] -> [a] | ||
- | Prelude> reverse [1, 2, 3] | ||
- | [3,2,1] | ||
- | </code> | ||
- | |||
- | |||
- | 4. Implementați o funcție care primește o listă și o sortează folosind algoritmul: | ||
- | |||
- | a) [[https://en.wikipedia.org/wiki/Merge_sort|merge sort]]. Ajutați-vă, în implementare, de metodele ''take'', ''drop'' și ''div''. | ||
- | |||
- | b) [[https://en.wikipedia.org/wiki/Insertion_sort|insert sort]]. | ||
- | |||
- | c) [[https://en.wikipedia.org/wiki/Quicksort|quick sort]]. | ||
- | |||
- | |||
- | 5. Implementați o funcție care primește o valoare și o listă sortată. Funcția trebuie să caute binar în listă și trebuie să returneze ''True'' dacă valoarea se găsește în listă, iar ''False'' în caz contrar. | ||
- | |||
- | {{:pp:lab1-skel.zip|Laborator 1 - Schelet}}\\ | ||
- | ==== Recommended reading ==== | + | ===== Further reading ===== |
* [[http://learnyouahaskell.com/starting-out|Learn you a Haskell for Great Good - Chapter 2: Starting Out]] | * [[http://learnyouahaskell.com/starting-out|Learn you a Haskell for Great Good - Chapter 2: Starting Out]] | ||
* [[http://learnyouahaskell.com/syntax-in-functions|Learn you a Haskell for Great Good - Chapter 4: Syntax in Functions]] | * [[http://learnyouahaskell.com/syntax-in-functions|Learn you a Haskell for Great Good - Chapter 4: Syntax in Functions]] | ||
* [[http://learnyouahaskell.com/recursion|Learn you a Haskell for Great Good - Chapter 5: Recursion]] | * [[http://learnyouahaskell.com/recursion|Learn you a Haskell for Great Good - Chapter 5: Recursion]] | ||
* [[http://book.realworldhaskell.org/read/getting-started.html|Real World Haskell - Chapter 1: Getting Started]] | * [[http://book.realworldhaskell.org/read/getting-started.html|Real World Haskell - Chapter 1: Getting Started]] |