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 $
.
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 myFilter
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 myFilter 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