Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
pp:l02 [2020/03/01 20:14] lfa [Exerciții] |
pp:l02 [2021/03/16 19:24] (current) roxana_elena.stiuca [2.3. Strings in Haskell] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Funcții ====== | + | ====== 2. Pattern matching and basic types in Haskell ====== |
- | Scopul laboratorului: | + | ===== 2.1. Pattern matching ===== |
- | * Semnificația termenului //aplicație// | + | |
- | * Definirea funcțiilor anonime în Haskell | + | |
- | * Curry vs uncurry | + | |
- | * Închideri funcționale | + | |
+ | It is likely that in the above implementations you used ''head'' and ''tail'', or branch definitions combined with '_' to solve the exercises. Haskell supports a more elegant means of //value introspection//, called **pattern matching**. For instance, in: | ||
- | ===== Funcții de ordin superior ===== | + | <code haskell> |
+ | f [] = ... | ||
+ | f (h:t) = ... | ||
+ | </code> | ||
- | Funcțiile de ordin superior sunt funcții care lucrează cu alte funcții: le primesc ca parametrii sau le returnează. | + | ''(h:t)'' is a **pattern** which denotes a non-empty list where ''h'' is the first element and ''t'' is the rest of the list. In fact, ''[]'' is also a pattern denoting the empty lists. Patterns are **allways** surrounded by round parentheses. They can also be composite, as in the exercise below: |
- | Pentru a înțelege importanța lor, vom da următorul exemplu: ne propunem să scriem două funcții care primesc o listă și returnează, într-o nouă listă | + | 2.1.1. Write a function which returns the number of elements from a list. Use patterns. |
- | * toate elementele pare | + | |
- | * toate elementele mai mari decât 10 | + | |
- | <code haskell> | + | 2.1.2. Write a function which takes a list of integer lists, and concatenates them. (E.g. ''f [ [1,2,3],[4,5] ] = [1,2,3,4,5]''). |
- | evenElements [] = [] | + | |
- | evenElements (x:xs) = if even x | + | |
- | then x : evenElements xs | + | |
- | else evenElements xs | + | |
- | + | ||
- | greaterThan10 [] = [] | + | |
- | greaterThan10 (x:xs) = if x > 10 | + | |
- | then x : greaterThan10 xs | + | |
- | else greaterThan10 xs | + | |
- | </code> | + | |
- | <note> | + | 2.1.3 (!) Write the **same** function, this time using only the **cons** ('':'') operator as well as **patterns**. |
- | În loc de ''if'', puteți folosi următoarea sintaxă: | + | |
- | <code haskell> | + | |
- | myFunction x | + | |
- | | x < 10 = "One digit" | + | |
- | | x < 100 = "Two digits" | + | |
- | | x < 1000 = "Three digits" | + | |
- | | otherwise = "More than four digits" | + | |
- | </code> | + | |
- | </note> | + | |
- | Testăm funcțiile scrise: | + | 2.1.4. Write a function which removes duplicates from a list of integers. |
- | <code> | + | |
- | *Main> evenElements [1..20] | + | |
- | [2,4,6,8,10,12,14,16,18,20] | + | |
- | *Main> greaterThan10 [1..20] | + | |
- | [10,11,12,13,14,15,16,17,18,19,20] | + | |
- | </code> | + | |
- | Observăm că funcțiile definite mai sus sunt foarte asemănătoare. De fapt, doar condiția verificată în ''if'' diferă. Scriem, deci, o funcție generală care primește o funcție pentru testarea elementelor: | + | ===== 2.2. Types in Haskell ===== |
+ | 2.2.1. What are the types of ''x,y,z,w'' and that of ''l'' in the implementation below? | ||
<code haskell> | <code haskell> | ||
- | -- In primul pattern, nu folosim functia de testare, deci nu ne intereseaza ca aceasta | + | f (x:y:z:w:l) = w |
- | -- sa fie legata la un nume, lucru marcat prin "_" | + | f _ = 0 |
- | myFilter _ [] = [] | + | |
- | myFilter test (x:xs) = if test x | + | |
- | then x : myFilter test xs | + | |
- | else myFilter test xs | + | |
</code> | </code> | ||
- | Acum putem rescrie funcțiile noastre, într-un mod mai elegant, utilizând funcția de filtrare: | + | How about: |
<code haskell> | <code haskell> | ||
- | evenElements = myFilter even | + | f (x:y:z:w:l) = w |
- | + | ||
- | greaterThan10 = myFilter (> 10) | + | |
</code> | </code> | ||
- | + | What is the difference? | |
- | + | ||
- | ===== Currying vs. uncurrying ===== | + | 2.2.2. What is the type of the function: |
- | + | <code haskell> | |
- | Currying (numit după [[https://en.wikipedia.org/wiki/Haskell_Curry|tizul Haskell-ului]]) este procesul prin care, dintr-o funcție care ia mai multe argumente, se obține o secvență de funcții care iau un singur argument. | + | f x y = (x,y) |
- | + | ||
- | De exemplu, dintr-o funcție de două argumente ''f : X × Y → Z'' se obține o funcție\\ | + | |
- | ''curry(f) : X → (Y → Z)''. Noua funcție ''curry(f)'' primește un argument de tipul ''X'' și întoarce o funcție de tipul ''Y → Z'' (adică o funcție care primește un argument de tipul ''Y'' și întoarce un rezultat de tip ''Z''). Considerând operatorul ''→'' asociativ la dreapta, putem omite parantezele, i.e. ''curry(f) : X → Y → Z''. | + | |
- | + | ||
- | Operația inversă se numește "uncurry": ''f : X → Y → Z, uncurry(f): X × Y → Z''. | + | |
- | + | ||
- | Întorcându-ne la funcția de filtrare definită mai devreme, care este tipul ei? | + | |
- | + | ||
- | <code> | + | |
- | *Main> :t myFilter | + | |
- | myFilter :: (a -> Bool) -> [a] -> [a] | + | |
</code> | </code> | ||
- | Și în [[pp:l01|laboratorul trecut]], am observat că nu există o separare între domeniu și codomeniu, de genul\\ | + | In the body of a function definition, '','' is a **base constructor** for pairs. The following are pairs: ''(1,2), ("Matei",20), ([1,2],3)''. Note that there is no restriction on the type of the first and second values of a pair. |
- | ''(a -%%>%% Bool) x [a] -%%>%% [a]''. | + | |
- | <note important> | + | 2.2.3. What is the type of the function: |
- | Acest lucru se datorează faptului că, în Haskell, **toate funcțiile iau un singur argument**. Alternativ, putem spune despre ele că sunt //curried//. | + | <code haskell> |
- | + | f 'a' _ = [] | |
- | Tipul funcției noastre trebuie interpretat astfel:\\ | + | f x y = x:y |
- | primește ca argument o funcție de tipul ''a -%%>%% Bool'' (ia un argument și întoarce o booleană) și întoarce o funcție de tipul ''[a] -%%>%% [a]'' (ia o listă și întoarce o listă de același tip). | + | |
- | + | ||
- | De aceea, în exemplul de mai sus am putut definit ''evenElements'' (o funcție care ia o listă și returnează o listă) ca fiind ''myFilter even'' care are exact acest tip, i.e. ''[a] -%%>%% [a]''. | + | |
- | </note> | + | |
- | + | ||
- | <note> | + | |
- | Haskell pune la dispoziție funcțiile ''curry'' și ''uncurry''. | + | |
- | + | ||
- | <code> | + | |
- | *Main> :t curry | + | |
- | curry :: ((a, b) -> c) -> a -> b -> c | + | |
- | *Main> :t uncurry | + | |
- | uncurry :: (a -> b -> c) -> (a, b) -> c | + | |
</code> | </code> | ||
- | Funcțiile primite, respectiv returnate de ''curry'' și ''uncurry'' iau tot un singur argument, numai că acesta este un tuplu. | + | In Haskell, the type ''String'' is equivalent to ''[Char]'' (hence strings are lists of chars). Strings can be introspected using patterns just like any other list. |
- | + | ||
- | <code> | + | |
- | *Main> let evenElements = (uncurry myFilter) even | + | |
- | + | ||
- | <interactive>:12:39: | + | |
- | Couldn't match expected type '(a0 -> Bool, [a0])' | + | |
- | with actual type 'a1 -> Bool' | + | |
- | In the second argument of 'uncurry', namely 'even' | + | |
- | In the expression: (uncurry myFilter) even | + | |
- | In an equation for 'evenElements': | + | |
- | evenElements = (uncurry myFilter) even | + | |
- | *Main> let evenElements l = (uncurry myFilter) (even, l) | + | |
- | *Main> | + | |
- | </code> | + | |
- | + | ||
- | </note> | + | |
- | + | ||
- | Funcțiile le putem apela și cu un număr mai mic de argumente. În acest caz, funcția apelată a fost parțial aplicată și va rezulta o nouă funcție cu un număr mai mic de parametrii. | + | |
- | + | ||
- | <code> | + | |
- | f a b = a - b | + | |
- | </code> | + | |
- | Funcția ''f'' primește 2 parametrii și întoarce diferența dintre ei. | + | |
- | Dacă apelăm ''f'' cu un singur argument se întoarce o altă funcție care se așteaptă să primească un singur argument. | + | |
- | <code> | + | |
- | g = f 3 -- se crează o altă funcție care primește un singur parametru (g b = 3 - b) | + | |
- | g 4 -- se apeleaza funcția g | + | |
- | -1 -- rezultatul 3 - 4 | + | |
- | </code> | + | |
- | + | ||
- | ===== Funcții anonime ===== | + | |
- | + | ||
- | Ne propunem să scriem o funcție care primește o listă și întoarce toate elementele ei divizibile cu 5. Având deja o funcție de filtrare, putem scrie: | + | |
+ | 2.2.4. Let f be the function below: | ||
<code haskell> | <code haskell> | ||
- | testDiv5 x = mod x 5 == 0 | + | f "321CB" [("321CB", ["Matei", "Andrei", "Mihai"]), ("322CB",["George", "Matei"])] = ["Matei", "Mihai"] |
- | multiplesOf5 = myFilter testDiv5 | + | |
</code> | </code> | ||
- | Această abordare funcționează, însă am poluat spațiul de nume cu o funcție pe care nu o folosim decât o singură dată - ''testDiv5''. | + | What is the signature of ''f''? |
- | Funcțiile anonime (cunoscute și ca expresii lambda) sunt funcții fără nume, folosite des în lucrul cu funcții de ordin superior. | ||
- | În Haskell, se folosește sintaxa: ''\x -%%>%% x + 5''. Funcția definită ia un parametru și returnează suma dintre acesta și 5. Caracterul ''\'' este folosit pentru că seamănă cu λ (lambda). | + | ===== 2.3. Strings in Haskell ===== |
- | Rescriind funcția noastră, obținem: | + | 2.3.1. Write a function which takes a list of words and makes the first letter of each word uppercase. |
- | <code haskell> | + | |
- | multiplesOf5 = myFilter (\x -> mod x 5 == 0) | + | |
- | </code> | + | |
- | Următoarele expresii sunt echivalente: | + | 2.3.2. Write a function which takes a list of words and makes **all** letters uppercase. |
- | <code haskell> | + | 2.3.3. (!) Write a function which takes a text and a pattern and returns the number of occurrences of the pattern in the text. |
- | f x y = x + y | + | Example: |
- | f x = \y -> x + y | + | <code haskell> |
- | f = \x y -> x + y | + | search "I eat green apples" "eat" = 1 |
+ | search "ababaab" "aba" = 2 | ||
</code> | </code> | ||
- | <note> | + | 2.3.4. (!) What does ''f'', defined in exercise 2.2.4., do (note that ''Matei'' and ''Mihai'' both start with letter 'M')? Write an implementation for ''f''. |
- | Nu există vreo diferență între ''\x y -%%>%% x + y'' și ''\x -%%>%% \y -%%>%% x + y''. | + | |
- | </note> | + | |
- | + | ||
- | Exemple de alte limbaje unde se pot folosi funcții anonime: | + | |
- | * Python | + | |
- | <code python>print((lambda x, y: x + y)(3, 4))</code> | + | |
- | * C++ - începând cu C++11 | + | |
- | <code c++>std::cout << [](int a, int b){return a + b;}(3, 4) << std::endl;</code> | + | |
- | * JavaScript | + | |
- | <code javascript>print(function(a,b){return a+b}(3, 4))</code> | + | |
- | ===== Închideri funcționale ===== | + | |
- | Închiderile funcționale (**closures**) se folosesc de variabile libere în definiția lor. Cu alte cuvinte, sunt funcții care, pe lângă definiție, conțin și un **environment** de care se folosesc; acest environment conține variabilele libere despre care vorbeam. Denumirea provine din faptul că environment-ul nu este menționat explicit, el este determinat implicit; spunem că funcția **se închide** peste variabilele (//libere//) **a**, **b**, etc. | + | |
+ | 2.3.5. Write the signature and implement the following function: | ||
<code haskell> | <code haskell> | ||
- | flip f = \a -> \b -> f b a | + | f ["Matei", "Mihai"] ["Popovici","Dumitru"] = [("Matei","Popovici"), ("Mihai","Dumitru")] |
- | -- ^^^^^^^^^^^^+^^^^ | + | |
- | -- f este o variabilă liberă în contextul funcției anonime de după egal | + | |
</code> | </code> | ||
- | Ce ne returnează funcția flip, atunci când îi dăm un singur argument? O //închidere funcțională// **peste f**. Prin urmare, funcția **mod_flipped** de mai jos este o închidere funcțională care atunci când primește 2 parametrii va folosi funcția **mod** (stocată - într-un mod neobservabil -- în **contextul** său) și va returna restul împărțirii celui de-al doilea la primul. | + | 2.3.6. Implement the following function: |
<code haskell> | <code haskell> | ||
- | mod_flipped = flip mod | + | f ["Matei", "Mihai"] ["Popovici","Dumitru"] = ["MPopovici", "MDumitru"] |
</code> | </code> | ||
- | Alte exemple de închideri funcționale: | + | 2.3.7. Write a function which takes a list of pairs - student-name and grade, removes those with grades less than 5, splits the name in substrings, and adds one bonus point to people with three names. Example: |
<code haskell> | <code haskell> | ||
- | mod3 x = mod x 3 -- închidere funcțională peste funcția mod din Prelude și constanta 3 | + | f [("Dan Matei Popovici",9),("Mihai",4),("Andrei Alex",6)] = |
- | plus5 x = x + 5 -- închidere funcțională peste funcția (+) și constanta 5 | + | [(["Dan", "Matei", "Popovici"],10),(["Andrei", "Alex"],6)] |
- | (+5) -- aceeași ca mai sus; | + | |
- | -- (+) este o funcție care primește 2 argumente și se evaluează la suma lor | + | |
- | -- (+5) este închiderea funcțională rezultată în urma "hardcodării" unuia dintre argumente | + | |
</code> | </code> | ||
- | ===== Hoogle ===== | ||
- | |||
- | Hoogle este un motor de căutare pentru funcții Haskell, o unealtă foarte utilă pentru dezvoltatori, mai ales la început. Vă recomandăm să citiți [[pp:hoogle|această pagină]]. | ||
- | |||
- | ===== Exerciții ===== | ||
- | |||
- | 1. Definiti operatorul "$" din labul trecut. | ||
- | |||
- | 2. Care este tipul operatorului de compunere al functiilor? Definiti-l printr-o functie myComp. | ||
- | |||
- | 3. Consideram urmatoarele functii lambda care definesc f x y = x + y: | ||
- | <code haskell> | ||
- | f x = \y -> x + y | ||
- | f = \x -> \y -> x + y | ||
- | f = \x y -> x + y | ||
- | </code> | ||
- | * Care este tipul lui f? | ||
- | * Care este tipul lui f 5? | ||
- | |||
- | 4. Putem defini un set ca fiind o functie de tipul s :: Integer -> Bool, s x returneaza true daca x se afla in setul s. | ||
- | * Scrieti functia s | ||
- | * Definiti setul f = {1,2,3} (tot ca o functie) | ||
- | * Scrieti o functie n pentru multimea numerelor naturale | ||
- | * Scrieti, in 2 moduri, intersectia a 2 seturi. | ||
- | * (hard) Scrieti o functie care primeste o lista de seturi si calculeaza intersectia acestora | ||
- | |||
- | 5. Definiti o functie care primeste o functie g :: Integer -> Bool, o lista de numere intregi, si returneaza o lista cu numerele pentru care g returneaza True. | ||
- | |||
- | 6. Folositi functia de filtrare definita anterior, scrieti o functie care pastreaza numerele pozitive dintr-o lista de numere intregi | ||
- | |||
- | 7. Implementati functia map. Folosind functia map, scrieti o functie care primeste o lista de Bool, si returneaza o lista cu 1 in loc de True si 0 in loc de False. | ||
- | |||
- | 8. Folosind map si/sau filter, scrieti o functie care sa implementeze comportamentul: | ||
- | f "321CB" [("321CB", ["Matei", "Andrei", "Mihai"]), ("322CB",["George, Matei"])] = ["Matei", "Mihai"] | ||
- | | ||
- | 9. Folosind map si/sau filter, scrieti o functie care sa implementeze comportamentul: | ||
- | f [("Dan Matei Popovici",9),("Mihai",4),("Andrei Alex",6)] = [(["Dan", "Matei", "Popovici"],10),(["Andrei,Alex"],6)] | ||
- | |||
- | 10. Folosind map, scrieti o functie care primeste o lista de liste si returneaza lista obtinuta prin concatenarea lor intr-o singura lista. | ||
- | |||
- | 11. Rescrieti functia de la (10) folosind recursivitatea pe coada. Care sunt diferentele? | ||
- | |||
- | 12. Implementati foldr si foldl. | ||
- | |||
- | 13. Implementati concatenarea folosind fold. | ||
- | |||
- | 14. Implementati functia reverse folosind fold. | ||
- | |||
- | 15. (hard) Folosind foldr sau foldl, scrieti o functie care primeste o lista de seturi si calculeaza intersectia acestora | ||
- | ===== Recommended reading ===== | ||
- | * [[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/higher-order-functions|Learn you a Haskell for Great Good - Chapter 6: Higher Order Functions]] | ||
- | * [[https://wiki.haskell.org/Higher_order_function|Haskell Wiki - Higher Order Functions]] | ||
- | * [[https://wiki.haskell.org/Closure|Haskell Wiki - Closures]] | ||
- | * [[https://wiki.haskell.org/Combinator|Haskell Wiki - Combinator]] |