Funkcije

Pojam funkcije u Haskel jeziku ima izvesne sličnosti sa pojmom funkcije u imperativnim jezicima poput C-a ili Jave, ali izvesne razlike. Dok se u navedenim jezicima funkcija shvata kao niz instrukcija koje je potrebno izvršiti, u Haskelu funkcija predstavlja pravilnost po kojoj se od neke vrednost dobija druga vrednost. Drugim rečima u Haskelu funkcije predstavljaju transformacije vrednosti. Ovo shvatanje se podudara sa matematičkom definicijom funkcije.

Takođe, dok je u imperativnim jezicima pojam funkcije razdvojen od pojma podatka (te je teško ili nemoguće vršiti sa funkcijama ono što je moguće vršiti sa podacima), u Haskelu je granica između ova dva pojma mnogo tanja. U Haskelu, svaka funkcija je samo jedna vrednost koja je sadržana u odgovarajućem tipu. I kao što vrednosti tipa Int mogu transformisati sa odgovarajućim funkcijama, ili vrednosti tipa Bool sa logičkim operacijama i funkcijama, tako se i funkcije mogu transformisati sa odgovarajućim funkcijama...

Pojam funkcije i tip funkcije

U matematici, funkcija je pravilnost po kojem se se svi elementi nekog skupa pridružuju elementima nekog drugog skupa. Činjenicu da neka funkcija \(f\) elementima skupa \(A\) pridružuje elemente skupa \(B\) označavamo sa \(f\colon A \to B\) i kažemo da \(f\) ima (poseduje) tip \(A \to B\). Skup \(A\) nazivamo domen a skup \(B\) kodomen funkcije \(f\)1. Ako funkcija \(f\) elementu \(x\in A\) pridružuje vrednost \(y\in B\) to označavamo sa \(f(x) = y\).

Za funkciju tipa \(A \to B\) često kažemo da preslikava (slika, transformiše) skup \(A\) u skup \(B\). Za izraz \(f(x)\) kažemo da predstavlja primenu funkcije \(f\) na vrednost \(x\).

Dve funkcije \(f_1\) i \(f_2\) smatramo za jednake ako imaju isti domen, isti kodomen, i ako je \(f_1(x) = f_2(x)\) za svako \(x\) iz domena funkcije.

Primer 1.

Neka je \(\mathbb N = \{0, 1, 2, \dots\}\) skup prirodnih brojeva. Funkcija \(s\colon \mathbb N \to \mathbb N\) definisana sa izrazom \(s(x)=x + 1\) svakom prirodnom broju pridružuje naredni prirodni broj tj. važi \(s(0)=1\), \(s(1)=2\), \(s(2)=3\) itd... Domen i kodomen ove funkcije je skup prirodnih brojeva \(\mathbb N\).

Primer 2.

Funkcija \(c \colon \mathbb N \to \mathbb N\) definisana sa \(c(x) = 1\) svakom prirodnom broju pridružuje isti broj - broj \(1\). Za funkciju \(c\) kažemo da je konstantna funkcija jer je njena vrednost konstantna bez obrzira na vrednost \(x\).

Primer 3.

Neka je \(P\) skup svih tačaka jedne ravni, a \(T\) skup svih duži te ravni. Definišmo funkciju \(s\) koja svakoj duži iz \(T\) pridružuje središte te duži. Domen funkcije \(s\) je \(T\), kodomen je \(P\), a tip je \(T \to P\).

Primer 4.

Neka je \(X\) proizvoljan skup. Tada možemo definisati funkciju \(i \colon X\to X\) sa \(i(x) = x\). Funkcija i preslikava svaku vrednost u samu sebe. Funkciju i nazivamo identička funkcija na skupu \(X\).

U Haskelu, analogno matematici, funkcije predstavljaju pravilnosti po kojem se vrednostima nekog tipa (domena) pridružuju vrednosti drugog tipa (kodomena). Tip funkcije koja pridružuje vrednostima tipa A vrednosti tipa B označava se sa A -> B. Tip funkcije, kao i svaki drugi tip, predstavlja kolekciju nekih vrednosti. Tip A -> B predstavlja kolekciju svih funkcija koje tip A preslikavaju tip B.

Na ilustraciji vidimo dva tipa C i D, čije vrednosti su predstavljene kao tačkice. Neku funkciju f tipa C -> D, možemo zamisliti kao strelice koje vrednosti tipa C "povezuju" sa vrednostima tipa D. Na ilustraciji je radi preglednosti prikazana samo jedna takva strelica, ona koja vrednost x povezuje sa f x. Naravno, po definiciji pojma funkcije, svaka vrednost iz C mora biti povezana sa nekom vrednošću iz D.
Primer 5.

Funkcija not svakoj logičkoj vrednosti dodeljuje njenu negaciju koja je takođe logička vrednost. Prema tome, funkcija not ima tip Bool -> Bool.

Primer 6.

Funkcija even, predefinisana u GHCi-ju, svakom celom broju dodeljuje logičku vrednost u zavisnosti da li je taj broj paran. Prema tome, tip funkcije even je2 Int -> Bool. Isto važi i za funkciju odd koja proverava da li je broj neparan.

Za razliku od imperativnih jezika, funkcije u Haskelu uvek moraju da poseduju kodomen. Drugim rečima, primena funkcije na vrednost mora i sama da predstavlja vrednost nekog tipa. U imperativnim jezicima neretko koristimo void funkcije koje nemaju povratnu vrednost. Takve funkcije ne postoje u Haskelu.

Zadatak 1. Neka je \(B\) skup koji sadrži dve vrednosti. Koliko postoji funkcija tipa \(B \to B\)? Koje su to funkcije?

Zadatak 2. Neka je \(U\) skup koji sadrži jednu vrednost. Koliko postoji funkcija tipa \(\mathbb N \to U\)? Koliko postoji funkcija tipa \(U \to \mathbb N\)?

Lambda izrazi

Haskel je zasnovan na matematičkom formalizmu zvanom lambda račun. Lambda račun je formalni sistem koji omogućuje predstavljane pojma izračunljivosti pomoću definisanja i primene funkcija. Ovde neće biti prezentovan lambda račun, ali mnoge stvari koje slede važe i za sam lambda račun.

U Haskelu, funkcije su vrednosti koje se definišu pravilom apstrakcije3. U Haskelu sintaksa za apstrakciju je \x -> v. Lambda apstrakcija se sastoji od znaka \ (koji je odabran tako da podseća na slovo \(\lambda\)), zatim imena parametra (u ovom primeru to je x ali može biti bilo koja validna labela), zatim strelice -> nakon koje sledi povratna vrednost funkcije odnosno neki izraz koji potencijalno zavisi od parametra (tj. od x). Izraz \x -> v nazivamo lambda funkcija a izraz koji sledi nakon strelice (tj. v), nazivamo telo lambda funkcije.

Primer 7.

Funkcija koja vraća dvostruki argument se definiše pravilom apstrakcije kao: \x -> (2 * x) Matematičkom notacijom zapisano, navedeni izraz predstavlja funkciju \(g(x) = 2 x\). Ime parametra je u potpunosti proizvoljno uzeto. Ista funkcija se može definisati i kao \y -> (2 * y) ili kao \broj -> (2 * broj), itd... Telo prve navedene lambda funkcije je (2 * x). Primetimo da navedenim kodom nismo definisali i ime funkcije4. Da bismo mogli ovu funkciju da primenjujemo na vrednosti, dodelićemo je nekom imenu:

g = (\x -> (2 * x))
Primer validne definicije funkcije u .hs datoteci.

Nakon učitavanja navedenog koda u GHCi, možemo koristiti funkciju g i primenjivati5 je na neke vrednosti. Da bi funkciju primenili na vrednost potrebno je tu vrednost navesti nakon imena funkcije, razdvajajući ih razmakom:

ghci> g 10
20
Izraz g 10 shvatamo kao \(g(10)\). Nismo ranije napomenuli, ali dužina razmaka (dokle god je taj razmak u jednoj liniji), ne igra nikakvu ulogu.

Svaki izraz oblika m n, gde su m i n neki izrazi, nazivamo aplikacija6. U Haskelu, svaka aplikacija predstavlja pokušaj primene izraza m na izraz n. Međutim, m n nema smisla ako m nije funkcija. Na primer, "primena" 'a' 'b' daje grešku

ghci> 'a' 'b'
<interactive>:15:1: error:
    • Couldn't match expected type ‘Char -> t’ with actual type ‘Char’
    • The function ‘'a'’ is applied to one value argument,
        but its type ‘Char’ has none
      In the expression: 'a' 'b'
      In an equation for ‘it’: it = 'a' 'b'
    • Relevant bindings include it :: t (bound at <interactive>:15:1)
Ova GHCi greška nam govori da se izraz 'a' ne može protumačiti kao funkcija i nema smisla primeniti a na b.

U prethodnom primeru, kada smo u GHCi prompt uneli g 10 i pritisnuli Enter, GHCi je zamenio ime g sa odgovarajućim lambda izrazom. Tom zamenom se dobija izraz (\x -> (2 * x)) 10. Štaviše, i sami smo mogli da unesemo izraz u prompt i dobili bismo isti rezultat:

ghci> (\x -> (2 * x)) 10
20
Primena funkcije na vrednost bez prethodnog imenovanja te funkcije. Ovo ilustruje zašto se lambda funkcije nazivaju i anonimne funkcije

Izraz oblika (\x -> a) b, gde su a i b proizvoljni lambda izrazi, naziva se redeks.

Primer redeksa je upravo (\x -> (2 * x)) 10 ali i g 10 jer je g po definiciji (\x -> (2 * x)). Za razliku od izraza 'a' 'b', svaki redeks predstavlja smislenu aplikaciju funkcije na vrednost.

Beta redukcija

Ako je data matematička funkcija \(f(x)=2*x\), tada se postupak nalaženja vrednosti \(f(10)\) sastoji u tome da se svako pojavljivanje promenljive \(x\) u izrazu \(2*x\) zameni sa argumentom \(10\). Time se dobija izraz \(2*10\) koji može direktno da se izračuna. Analognim postupkom redeksi se mogu svesti na neku vrednost koja u sebi ne sadrži redeks.

Primer 8.

Da bi evaluirao redeks (\x -> (2 * x)) 10, GHCi u telu lambda funkcije zamenjuje x sa 10, čime se dobija 2 * 10. Izraz 2 * 10 se zatim sračunava na nivou hardvera.

Opisani postupak nazivamo beta redukcija. Tačnije beta redukcija je zamena redeksa (\x -> a) b sa izrazom koji se dobije kada se izrazu a sva pojavljivanja imena x zamene sa izrazom b. Pritom se početak lambda izraza, \x ->, uklanja.

Primer 9.

Neka je dat redeks (\x -> (((\y -> (y + 1)) x) * x)) 10. Ovaj redeks je zanimljiv, zato što se u telu lambda funkcije nalazi druga lambda funkcija. Beta redukcijom vrednost 10 zamenjujemo umesto x u telu "veće" lambda funkcije (((\y -> (y + 1)) 10) * 10) U rezultatu beta redukcije imamo redeks (\y -> (y+1)) 10 koji takođe možemo da redukujemo, pri čemu ostatak izraza ostaje nepromenjen: ((((10 + 1))) * 10). Višestruke zagrade ne igraju ulogu, te smo došli do izraza ((10 + 1) * 10) koji se direktno računa do vrednosti 110.

Postavlja se pitanje, da li je poredak evaluacije redeksa važan? Da li bismo isti rezultat dobili i da smo prvo izvršili beta redukciju unutrašnjeg redeksa a zatim i spoljašnjeg?

Primer 10.

Nastavak prethodnog primera. Beta redukcijom unutrašnjeg redeksa izraza u (\x -> (((\y -> (y + 1)) x) * x)) 10 dobijamo izraz (\x -> ((x + 1) * x)) 10. Vršenjem još jedne beta redukcije dobijamo isti izraz kao malopre ((10 + 1) * 10). Kao što vidimo, poredak vršenja lambda redukcije nije uticao na krajnju vrednost!

Osobina da poredak vršenja beta redukcije ne utiče na krajnu vrednost je poznata kao konfluentnost. Iako neće za svaki izraz važiti konfluentnost, često ipak hoće. Više o tome ćemo reći kasnije...

Tip funkcije

Do sada smo smo objasnili kako se definišu funkcije, ali nismo naveli mnogo o tipu funkcije. Razlog tome je što je Haskel automatski zaključivao tip definisanih funkcija. Ipak, kao i sa drugim vrednostima, programer može (i poželjno je) da eksplicitno navede tip funkcije.

Primer 11.

Tip funkcije g iz prethodne sekcije možemo eksplicitno da navedemo u kodu:

g :: Int -> Int
g = (\x -> (2 * x))
Iz tipa Int -> Int vidimo da funkcija uzima jednu vrednost tipa Int i vraća takođe vrednost tipa Int.
Primer 12.

Nešto složeniji primer bi bila funkcija koja uzima listu tipa [Int], i vraća prvi član te liste uvećan za jedan:

h :: [Int] -> Int
h = (\x -> (head x + 1))
Aplikacija ima najveći prioritet te se izraz head x + 1 tumači kao (head x) + 1.

U GHCi okruženju sa :type naredbom možemo proveravati i tipove funkcija:

ghci> :type h
h :: [Int] -> Int

Zanimljivo je primetiti šta se dešava sa tipom funkcije kada se funkcija primeni na vrednost. Pre svega, Haskel će proveriti da li se tip te vrednosti i domen funkcije podudaraju. Ako to nije slučaj, tada će doći do tipske greške.

Primer 13.

Ako primenimo funkciju h :: [Int] -> Int na broj, dobićemo grešku:

ghci> h 2
<interactive>:3:3: error:
    • No instance for (Num [Int]) arising from the literal ‘2’
    • In the first argument of ‘h’, namely ‘2’
      In the expression: h 2
      In an equation for ‘it’: it = h 2

Naravno, razlog ove greške je taj što se funkcija h "očekuje" vrednost tipa [Int] a ne Int. Domen funkcije h je [Int], i stoga se ona može primenjivati samo na vrednosti koje poseduju ovaj tip.

Ako nije došlo do tipske greške, tada primena funkcija na neku vrednost predstavlja dobro tipiziran izraz. Pritom ako je funkcija f tipa a -> b, a vrednost v tipa a, tada je f v predstavlja vrednost tipa b. Beta redukcijom moguće je odrediti tačnu vrednost izraza f v, ali i bez redukcije znamo da ta vrednost poseduje tip b.

Primer 14.

Funkcija h iz prethodnog primera poseduje tip [Int] -> Int. Aplikacija h [1,2,3] poseduje tip Int:

ghci> :type (h [1,2,3])
h [1,2,3] :: Int
ghci> h [1,2,3]
2

Prosto rečeno, primenom funkcije tipa A -> B na vrednosti tipa A "brišemo" A -> iz tipa7.

Zadatak 3. Napisati funkciju povrsinaKruga :: Double -> Double koja za zadati poluprečnik kruga vraća njegovu površinu. Konstanta \(\pi\) je dostupna u Haskelu kao pi.
povrsinaKruga :: Double -> Double
povrsinaKruga = (\r -> (pi * r ** 2))

Funkcije više promenljiva

Do sada je prikazano kako se mogu definisati funkcije samo sa jednim parametrom, ali su u prethodnoj lekciji korišćene funkcije sa dva parametara (kao što je funkcija mod koja određuje ostatak pri deljenju ili funkcija elem koja ispituje da li se element nalazi u listi). Postavlja se pitanje kako se ovakve funkcije definišu?

Pažljivi čitalac koji se seća sadržaja prethodne lekcije mogao je sam da konstruiše funkcije sa dva parametara. Naime, u prethodnoj sekciji smo videli kako se uređeni parovi mogu konstruisati. Prema tome, ako želimo funkciji da prosledimo dve vrednosti, dovoljno je da napišemo funkciju koja uzima jedan uređen par8.

ljubav :: (String, String) -> String
ljubav = (\x -> (fst x ++ " voli " ++ snd x ++ "!"))
Funkcija f uzima uređen par niski i vraća nisku.
ghci> ljubav ("Ana", "Milovana")
"Ana voli Milovana!"
ghci> ljubav ("Marija", "Marijana")
"Marija voli Marijana!"

Pozivanje ovako definisane funkcije podseća na pozivanje funkcija u imperativnim jezicima. Ali primetimo da smo mi u prethodnoj lekciji funkcije poput mod pozivali sa mod 7 3 a ne sa mod (7, 3). Takođe možemo da primetimo da iako funkciji prosleđujemo dva podatka, funkcija ljubav suštinski uzima samo jednu vrednost tipa (String, String).

Lambda račun, koji je osmišljen pola veka pre Haskela, je zamišljen kao minimalan formalni sistem i kao takav takav ne poseduje pojam uređene n-torke. Umesto toga, funkcije sa više parametara u lambda računu se realizuju uz pomoć 'trika'. Naime: da bi dobili funkciju f od više parametara, kreiraćemo funkciju f od jednog parametra čija povratna vrednost je druga funkcija. Na taj način, izraz (f x) predstavlja novu funkciju koju možemo da primenimo na neku vrednost. Ako je rezultat i ove primene funkcija, tada taj rezultat možemo da primenimo na sledeću vrednost i tako dalje.

Primer 15.

Funkcija koja uzima dve vrednosti tipa Double i vraća njihovu aritmetičku sredinu može se definisati sa: aritm = (\x -> (\y -> ((x+y)/2))).

U GHCi okruženju možemo da se uverimo da funkcija ispravno računa aritmetičku sredinu:

ghci> (aritm 8) 10
9

Zašto je funkcija pozvana na ovaj način? Pogledajmo samu definicije funkcije aritm. Izraz aritm 8 predstavlja jedan redeks. Beta redukcijom ovaj redeks se svodi na (\y -> ((8+y)/2)). Ali ovaj rezultat je ponovo jedna funkcija, stoga je možemo primeniti na vrednost 10. Aplikacija (\y -> ((8+y)/2)) 10 se beta-redukuje u izraz (8 + 10)/2 koji se zatim sračunava u vrednost 9.

Ipak, aplikacija (aritm 8) 10 ne izgleda baš u potpunosti isto kao aplikacija mod 7 2 koju smo videli u prethodnoj lekciji. Razlog tome je što u Haskelu aplikacija (primena funkcije na argument) je levo asocijativna operacija što znači da se izraz (f x) y može jednostavnije zapisati kao f x y. I zaista, ako se vratimo na prethodni primer, možemo se uveriti u to:

ghci> aritm 8 10
9
ghci> aritm 20 100
60

Kog tipa je funkcija aritm? Za trenutak pretpostavimo da su parametri x i y tipa Double9. Funkcija aritm uzima vrednost tipa Double i vraća neku funkciju, nazovimo je g. Funkcija g uzima i vraća vrednost tipa Double te je njen tip Double -> Double. Sada možemo da zaključimo da funkcija aritm poseduje tip Double -> (Double -> Double) jer uzima vrednost tipa Double i vraća vrednost tipa Double -> Double.

Koristeći opisan postupak, možemo konstruisati funkcije koje uzimaju tri ili više argumenata10. Tom prilikom konstruišemo funkcije tipa a1 -> (a2 -> ( ... -> (aN -> b)) gde tipovi a1, ... aN predstavljaju tipove parametara funkcije od \(N\) argumenata, a b povratnu vrednost takve funkcije. Primenom funkcije f tipa a1 -> (a2 -> ( ... -> (aN -> b)) na vrednost tipa a1 dobija se funkcija tipa a2 -> (a3 -> (... -> (aN -> b))). Novodobijena funkcija može se primeniti na vrednost tipa a2 čime se dobija funkcija tipa a3 -> (... -> (aN -> b)). Postupak se može ponavljati sve dok se ne stigne do vrednosti tipa b.

Funkcija koja "uzima" ili "vraća" neku drugu funkciju naziva se funkcija višeg reda. Mnogi programski jezici nemaju podršku za funkcije višeg reda, ali kao što vidimo, u Haskelu je ovaj pojam je sasvim prirodan.

Zadatak 4. Napisati funkciju zapremina :: Double -> (Double -> (Double -> Double)) koja na osnovu tri argumenta računa zapreminu kvadra čije su dužine stranica zadate tim argumentima.
zapremina :: Double -> (Double -> (Double -> Double))
zapremina = (\a -> (\b -> (\c -> (a * b * c))))

Karijevanje

U prethodnoj sekciji prikazana su dva načina (dve tehnike) definisanja funkcija dva argumenta. Prvi, i jednostavniji, način podrazumeva konstrukciju funkcije koja uzima uređen par, dok drugi način podrazumeva konstrukciju funkcije višeg reda. Prva tehnika daje funkciju tipa (a, b) -> c dok druga tehnika daje funkciju tipa a -> (b -> c).

Intuitivno je jasno da su ove dve tehnike podjednako dobre. Svaka funkcija koja se može definisati na jedan od pomenutih načina može se definisati i na drugi. Stvar je ukusa za koju tehniku ćemo se opredeliti11. Ono što je zanimljivo je da je Haskel toliko ekspresivan jezik da se lako može definisati funkcija višeg reda koja transformiše funkcije tipa (a, b) -> c u funkcije tipa a -> (b -> c)! Ovakva transformacija se naziva karijevanje12. Označimo funkciju koja vrši ovu transformaciju sa k.

Kog tipa je funkcija k? Funkcija k uzima funkciju tipa (a, b) -> c i vraća funkciju tipa a -> (b -> c). Stoga je tip ove funkcije ((a, b) -> c) -> (a -> (b -> c)).

Kada smo utvrdili tip, možemo da implementiramo k. Pretpostavimo da je f :: (a, b) -> c funkcija koju želimo da transformišemo. Pošto funkcija k vraća vrednost tipa a -> (b -> c), u telu definicije moramo napisati izraz poput \x -> (\y -> ...)13. U telu ove lambda funkcije mora se nalaziti rezultat primene f na uređen par (x, y). Prema tome, k možemo da definišemo sa narednim kodom

k :: ((a, b) -> c) -> (a -> (b -> c))
k = (\f -> (\x -> (\y -> (f (x, y)))))

Na sličan način možemo da definišemo i inverznu funkciju k' koja funkcije tipa a -> (b -> c) transformiše u funkcije tipa (a, b) -> c. Jasno, tip funkcije k' je (a -> (b -> c)) -> ((a, b) -> c). Ako je g funkcija tipa a -> (b -> c) tada je k' g funkcija koja uzima uređen par, te ćemo stoga takvu funkciju konstruisati sa lambda apstrakcijom \p -> ... gde p predstavlja neki uređen par. U telu lambda funkcije je potrebno da se nađe vrednost (g x) y gde su x i y koordinate uređenog para p. Stoga, k' možemo da definišemo sa narednim kodom

k' :: (a -> (b -> c)) -> ((a, b) -> c)
k' = (\g -> (\p -> ((g (fst p)) (snd p))))

Korišćenjem let in sintakse možemo dobiti malo čitljiviju definiciju

k' :: (a -> (b -> c)) -> ((a, b) -> c)
k' = (\g -> (\p -> (let x = fst p; y = snd p in (g x) y)))

Uverimo se da navedene funkcije zaista dobro rade. Uzmimo primer od malopre:

ljubav :: (String, String) -> String
ljubav = (\x -> (fst x ++ " voli " ++ snd x ++ "!"))
ghci> ljubav' = k ljubav
ghci> ljubav' "Ana" "Milovana"
"Ana voli Milovana!" 
Transformacija zaista funkcioniše: funkcija ljubav' ne uzima uređen par već je u pitanju funkcija višeg reda.

Sa druge strane, od ugrađene funkcije mod možemo dobiti našu verziju funkcije koja uzima jedan uređen par brojeva:

ghci> mod' = k' mod
ghci> mod' (7, 2)
1

Funkcije koje smo konstruisali u prethodnim redovima dostupne su pod imenima curry i uncury i poseduju sleće tipove

curry :: ((a, b) -> c) -> (a -> (b -> c))
uncurry :: (a -> b -> c) -> ((a, b) -> c)

Zadatak 5. Konstruisati funkciju curry3 koja uzima funkciju tipa (a, b, c) -> d i daje funkciju tipa a -> (b -> (c -> d)).

Zadatak 6. Kog tipa je izraz uncurry curry?

Zadaci

Zadatak 7. Rešenja \(x_1\) i \(x_2\) kvadratne jednačine \(ax^2+bx+c=0\) su data sa kvadratnom formulom \[x_{1,2} = \frac{-b\pm\sqrt{b^2-4ac}}{2a}.\] Implementirati funkciju resenja :: Double -> (Double -> (Double -> (Double, Double))) koja za tri prosleđena argumenta \(a\), \(b\) i \(c\) vraća uređeni par rešenja koja su dobijena putem navedene formule. Pretpostaviti da će koeficijenti biti tako zadati da su sve računske operacije definisane.

Zadatak 8. Napisati funkciju primeni :: (Int -> Bool) -> ((Int, Int) -> (Bool, Bool)) koja uzima jednu funkciju f :: Int -> Bool, jedan uređen par tipa (Int, Int), i vraća uređen par koji se dobija primenom f na svaku koordinatu uređenog para.

Kada se primeni primeni na neku funkciju f, potreno je da dobijemo lambda funkciju koja uzima par i vraća par dobijen primenom f na svaku od koordinata. Ova lambda funkcija se može zapisati kao \par -> (f (fst par), f (snd par)). Jedino što je potrebno je da uvedemo i f kao jedan parametar, tj. da napravimo apstrakciju po f. Time dobijamo:

primeni :: (Int -> Bool) -> ((Int, Int) -> (Bool, Bool))
primeni = (\f -> (\par -> (f (fst par), f (snd par))))

Da bi testirali našu funkciju, definisaćemo i funkciju paran paran :: Int -> Bool paran = (\n -> (0 == mod n 2))

Nakon učitavanja u GHCi, uveravamo se da funkcija zaista radi:

ghci> primeni paran (2, 7)
(True,False)

Za naredne zadatke neophodno je podsetiti se funkcija za rad sa listama iz prethodne lekcije: length, head, last, init, tail, reverse, elem.

Zadatak 9. Napisati funkciju inicijali :: [Char] -> ([Char] -> [Char]) koja od dve niske koje predstavlaju ime i prezime pravi inicijale. Na primer inicijali "Nikola" "Tesla" daje "N.T.". Pretpostaviti da su obe niske neprazne.

Korišćenjem head :: [a] -> a funkcije, "uzećemo" prva slova imena i prezimena. Primena funkcije head na nisku nam daje vrednost tipa Char. Stoga dobijenu vrednost moramo postaviti u novu nisku, da bismo mogli koristiti operator ++ koji nadovezuje niske.

inicijali :: [Char] -> ([Char] -> [Char])
inicijali = (\x1 -> (\x2 -> (
    let y1 = head x1; y2 = head x2
    in [x1] ++ "." [x2] ++ "."
  )))
Zadatak 10. Napisati funkciju ukloni3 :: [Char] -> [Char] koja uklanja prva tri elementa iz niske. Pretpostaviti da će funkcija uvek biti primenjena na niske dužine veće od tri.

Funkcija tail :: [a] -> [a] nam vraća rep niske (sve osim prvog elmenta). Primenom ove funkcije tri puta na neku listu, efektivno uklanjamo prva tri elementa iz liste

ukloni3 :: [Char] -> [Char]
ukloni3 = (\x -> (tail (tail (tail x))))

Primetimo da u rešenju zadatka ne postoji ništa što je specifično za niske karaktera. Prema tome možemo da implementiramo opštiju funkciju koja se može primeniti na liste bilo kog tipa. Dovoljno je samo da u dekleraciji tipa funkcije, tip Char zamenimo sa tipskom promenljivom a.

ukloni3 :: [a] -> [a]
ukloni3 = (\x -> (tail (tail (tail x))))
Zadatak 11. Za nisku kažemo da je palindrom ako se čita isto i sa levo i sa desno, npr. "anavolimilovana" jeste palindorm. Napisati funkciju palindrom :: [Char] -> Bool koja proverava da li je niska palindorm.

Niske, odnosno vrednosti tipa [Char], pripadaju klasi Eq. Zbog toga je moguće koristiti operator == za poređenje dve niske. Niska s je palindrom ako je jednaka nisci reverse s. Ovo zapažanje nas direktno dovodi do rešenja:

palindrom :: [Char] -> Bool
palindorm = (\s -> (s == reverse s))
Zadatak 12. Sa tipom (Double, Double) moguće je predstaviti vektor dvodimenzionalne ravni. Napisati funkciju zbir :: (Double, Double) -> ((Double, Double) -> (Double, Double)) koja sabira vektore.
zbir :: (Double, Double) -> ((Double, Double) -> (Double, Double))
zbir = (\u -> (\v -> (fst u + fst v, snd u + snd v)))
Zadatak 13. Funkcija max :: Ord a => a -> (a -> a) uzima dve vrednosti i vraća veću od vrednosti. Napisati funkciju max3 :: Ord a => a -> (a -> (a -> a)) koja vraća maksimum tri vrednosti. Naravno Ord => je klasno ograničenje koja nam govori da tipska promenljiva a pripada klasi Ord, te se vrednosi ovog tipa mogu porediti sa <, >, <= i >=.

Ako su nam date vrednosi x, y i z tada ćemo prvo naći veću od vrednosi x i y a zatim ćemo tu veću vrednost da uporedimo sa z:

max3 :: Ord a => a -> a -> a -> a
max3 = (\x -> (\y -> (\z -> max (max x y) z)))