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 (și de ce nu contează în Haskell) * Î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. Scrieți o funcție ce primește un număr $ x$ și o listă de numere de forma $ [a_1, a_2, ... a_n]$ ca parametrii și calculează $ x - a_1 - a_2 - ... - a_n$. 2. Inversați o listă folosind foldl. 3. Definiți o închidere funcțională care prefixează [1,2,3] la o listă primită ca parametru. 4. Scrieți o închidere funcțională care elimină caracterul 'a' dintr-un string primit ca parametru. * Scrieți o implementare ce utilizează o funcție de tip ''fold'' * Scrieți o alta care folosește funcția ''filter'' 5. Implementați o funcție ce primește ca parametru o lista de string-uri și returnează concatenarea lor folosind fold. 6. Se dau două cuvinte de aceeași lungime. Să se numere câte caractere sunt diferite în cele doua șiruri. 7. Definiți o funcție de ordin superior care primește o funcție și un număr, și aplică de două ori funcția pe numărul respectiv. 8. Definiți o funcție care primește un operator binar, și întoarce același operator în care ordinea parametrilor a fost inversată //(e.g. 1/3 -> 3/1)// 9. Implementați și testați funcțiile: * foldl * foldr * map * filter * zipWith * compunerea de funcții (operatorul ''.'' din Haskell) <note tip> Dacă nu cunoașteți vreuna dintre funcții, o puteți căuta pe Hoogle pentru a vedea tipul și scopul ei. </note> 10. Implementați, folosind foldl sau foldr: * map * filter 11. Implementați o funcție ce primește 2 parametri: un șir de cifre de forma "111222111333112" și un număr K. Această funcție trebuie să facă următorii pași de K ori: * se grupează cifrele identice în felul următor "111222111333112" -> ["111", "222", "111", "333", "11", "2"] * pentru fiecare element din lista rezultată se generează un element de forma (numărul de apariții al cifrei din șir, cifra din șir) ex: ["111", "222", "111", "333", "11", "2"] -> [("3", "1"), ("3", "2"), ("3", "1"), ("3", "3"), ("2", "1"), ("1", "2")] * se face flatten pe lista rezultată la pasul anterior ex: [("3", "1"), ("3", "2"), ("3", "1"), ("3", "3"), ("2", "1"), ("1", "2")] -> "313231332112" <note> Pentru mai multe informații despre secvență: https://en.wikipedia.org/wiki/Look-and-say_sequence </note> {{:pp:lab2-skel.zip|Laborator 2 - Schelet}}\\ ===== 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]]