This is an old revision of the document!
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
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
if
, puteți folosi următoarea sintaxă:
myFunction x | x < 10 = "One digit" | x < 100 = "Two digits" | x < 1000 = "Three digits" | otherwise = "More than four digits"
Testăm funcțiile scrise:
*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]
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:
-- 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
Acum putem rescrie funcțiile noastre, într-un mod mai elegant, utilizând funcția de filtrare:
evenElements = myFilter even greaterThan10 = myFilter (> 10)
Currying vs. uncurrying
Currying (numit după 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?
*Main> :t myFilter myFilter :: (a -> Bool) -> [a] -> [a]
Și în laboratorul trecut, am observat că nu există o separare între domeniu și codomeniu, de genul
(a -> Bool) x [a] -> [a]
.
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]
.
curry
și uncurry
. *Main> :t curry curry :: ((a, b) -> c) -> a -> b -> c *Main> :t uncurry uncurry :: (a -> b -> c) -> (a, b) -> c
Funcțiile primite, respectiv returnate de curry
și uncurry
iau tot un singur argument, numai că acesta este un tuplu.
*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>
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.
f a b = a - b
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.
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
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:
testDiv5 x = mod x 5 == 0 multiplesOf5 = myFilter testDiv5
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:
multiplesOf5 = myFilter (\x -> mod x 5 == 0)
Următoarele expresii sunt echivalente:
f x y = x + y f x = \y -> x + y f = \x y -> x + y
\x y -> x + y
și \x -> \y -> x + y
.
Exemple de alte limbaje unde se pot folosi funcții anonime:
- Python
print((lambda x, y: x + y)(3, 4))
- C++ - începând cu C++11
std::cout << [](int a, int b){return a + b;}(3, 4) << std::endl;
- JavaScript
print(function(a,b){return a+b}(3, 4))
Î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.
flip f = \a -> \b -> f b a -- ^^^^^^^^^^^^+^^^^ -- f este o variabilă liberă în contextul funcției anonime de după egal
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.
mod_flipped = flip mod
Alte exemple de închideri funcționale:
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
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 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:
f x = \y -> x + y f = \x -> \y -> x + y f = \x y -> x + y
- 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