Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Funcții ====== Scopul laboratorului: * Semnificația termenului //aplicație// * Definirea funcțiilor anonime în Haskell * Curry vs uncurry * Închideri funcționale ===== Funcții de ordin superior ===== Funcțiile de ordin superior sunt funcții care lucrează cu alte funcții: le primesc ca parametrii sau le returnează. 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ă * toate elementele pare * toate elementele mai mari decât 10 <code haskell> 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> Î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: <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: <code haskell> -- In primul pattern, nu folosim functia de testare, deci nu ne intereseaza ca aceasta -- sa fie legata la un nume, lucru marcat prin "_" myFilter _ [] = [] myFilter test (x:xs) = if test x then x : myFilter test xs else myFilter test xs </code> Acum putem rescrie funcțiile noastre, într-un mod mai elegant, utilizând funcția de filtrare: <code haskell> evenElements = myFilter even greaterThan10 = myFilter (> 10) </code> ===== Currying vs. uncurrying ===== 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. 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> Și în [[pp:l01|laboratorul trecut]], am observat că nu există o separare între domeniu și codomeniu, de genul\\ ''(a -%%>%% Bool) x [a] -%%>%% [a]''. <note important> Acest lucru se datorează faptului că, în Haskell, **toate funcțiile iau un singur argument**. Alternativ, putem spune despre ele că sunt //curried//. Tipul funcției noastre trebuie interpretat astfel:\\ 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> Funcțiile primite, respectiv returnate de ''curry'' și ''uncurry'' iau tot un singur argument, numai că acesta este un tuplu. <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: <code haskell> testDiv5 x = mod x 5 == 0 multiplesOf5 = myFilter testDiv5 </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''. 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). Rescriind funcția noastră, obținem: <code haskell> multiplesOf5 = myFilter (\x -> mod x 5 == 0) </code> Următoarele expresii sunt echivalente: <code haskell> f x y = x + y f x = \y -> x + y f = \x y -> x + y </code> <note> 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. <code haskell> flip f = \a -> \b -> f b a -- ^^^^^^^^^^^^+^^^^ -- f este o variabilă liberă în contextul funcției anonime de după egal </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. <code haskell> mod_flipped = flip mod </code> Alte exemple de închideri funcționale: <code haskell> mod3 x = mod x 3 -- închidere funcțională peste funcția mod din Prelude și constanta 3 plus5 x = x + 5 -- închidere funcțională peste funcția (+) și constanta 5 (+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> ===== 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]]