Polimorfnost

Svaki programski jezik je osmišljen sa ciljem da olakša proces programiranja računara, koliko god ovoj iskaz delovao očigledno. Zaista, iako se svaki program može napisati u mašinskom jeziku, programeri biraju programske jezike zbog osobina tih jezika koje omogućuju efikasnije programiranje. Osobine koje olakšavaju programiranja su zapravo apstrakcije kojima se nebitne pojedinosti skrivaju i samim tim obezbeđuje lakše izražavanje suštinski ideja o programu1.

Jednoj vrsti apstrakcija posvetićemo ovu lekciju, a na samom početku ćemo pokušati da motivišemo razloge za uvođenje takvih apstrakcija.

U prethodnim lekcijama naučili smo kako da definišemo funkcije, i upoznali smo se sa pojmom tipa funkcije. Programi koje smo pisali su bili mali i sačinjeni samo do nekoliko definicija funkcija. Ako bismo radili na velikim programima (kao što su web serveri, desktop aplikacije, itd...) uvideli bismo jedan jednostavan ali naporan problem. Naime, često bismo pisali gotovo iste funkcije koje imaju malo različite tipove.

Primer 1.

Zamislimo da je potrebno na implementiramo funkciju koja izražava određenu numeričku zakonitost (nebitno kakvu) po formuli koja je eksperimentalno dobijena \[y = f(x) = x\cdot x + x.\]

Ako očekujemo da će vrednosti \(x\) i \(y\) biti tipa Int, tada možemo napisati sledeću definiciju

f :: Int -> Int
f x = x*x + x

Sa druge strane, ako očekujemo da će \(x\) i \(y\) biti tipa Float, tada možemo napisati sledeću definiciju

f :: Float -> Float
f x = x*x + x

Kako ne možemo imati dve definicije istog imena (a pogotovu dve definicije istog imena sa različitim tipovima), problem nastaje ako želimo da u kodu definišemo obe. Tada, moramo odabrati novo ime za jednu od funkcija (npr. f i fFloat). Ako želimo da dodamo odgovarajuću funkciju i za tip Double, i toj funkciji moramo dati novo ime.

Opisano rešenje navedenog problema je jednostavno ali dovodi do drugih problema:

  1. Imena funkcija postaju duža, teža za pamćenje i za pisanje. Problem je posebno izražen kod funkcija više promenljiva.
  2. Ako se definicija promeni (npr. ako se gorenavedena formula promeni zbog preciznijih eksperimentalnih rezultata), tada je neophodno svaku od funkcija posebno izmeniti, što predstavlja veliki prostor za greške (i to one greške koje sistem tipove ne može otkriti).

Prethodni primer je trivijalan, ali demonstrira probleme koji se sreću na iole većim projektima bez obzira na programski jezik. Srećom, mnogi jezici su našli rešenja za navedene probleme, pa tako i Haskel. U Haskelu, moguće je definisati vrednosti (posebno, funkcije) za koje deluje da ne pripadaju jednom određenom tipu, već čitavoj kolekciji tipova. Ovakvi tipovi se nazivaju polimorfni tipovi2.

Primer 2.

Do sada smo već imali prilike da se susretnemo sa polimorfnom vrednošću (vrednošću koja ima polimorfni tip). To je bila prazna lista []. Za ovu vrednost možemo smatrati da pripada svakom tipu lista. U nastavku ćemo videti kog je tačno tipa [].

Primer 3.

Numerički literal (poput npr. 2) može predstavljati vrednost tipa Int, Integer, Float ili Double.

Polimorfnost se u Haskelu obezbeđuje na dva načina: kroz parametarski polimorfizam i kroz ad-hoc polimorfizam.

Parametarski polimorfizam

Tipska promenljiva (ili još tipski parametar), je ime koje predstavlja proizvoljan tip (eventualno iz neke kolekcije tipova). Za razliku od tipova, tipske promenljive zapisuju se sa početnim malim slovom, a najčešće se ime tipske promenljive sastoji samo od jednog slova. Ako tipska promenljiva predstavlja svaki mogući tip (a ne neku specifičnu kolekciju tipova), tada za tu tipsku promenljivu kažemo da je slobodna. Kasnije ćemo objasniti promenljive koje nisu slobodne.

Primer 4.

U tipu a -> Int, ime a predstavlja slobodnu tipsku promenljivu. Prema tome, navedeni tip može da predstavlja bilo koji tip oblika A -> Int, gde je A neki određen tip. Na primer: Bool -> Int, Int -> Int, [Char] -> Int, (Bool -> Char) -> Int, i beskonačno mnogo drugih mogućnosti...

Parametarski polimorfni tipovi su oni tipovi koji sadrže slobodne tipske promenljive.

Najjednostavniji primer vrednosti koja poseduje parametarski polimorfan tip, je konstantna funkcija poput naredne:

konstantnoTačno :: a -> Bool
konstantnoTačno = \x -> True

Navedenom definicijom, kao3 da je definisano beskonačno mnogo funkcija sa različitim domenima: za svaki tip T po jedna funkcija tipa T -> Bool. Sve te funkcije nazivamo istim imenom konstantnoTačno. Kada u Haskel programu primenimo funkciju konstantnoTačno na neku određenu vrednost, tada tipska promenljiva a u tipu funkcije preuzima tip vrednosti na koju primenjujemo funkciju. Na primer, ako napisemo konstantnoTačno 'a' tada možemo smatrati da ime konstantnoTačno u navedenom izrazu predstavlja funkciju tipa Char -> Bool (jer je samo funkciju tog tipa moguće primeniti na vrednost tipa Char).

Nesto složeniji primer funkcije sa polimorfnim tipom je identička funkcija, definisana u Prelidu kao id:

id :: a -> a
id = \x -> x

Tip identičke funkcije sadrži tipsku promenljivu i u domenu i u kodomenu. Kad god se u tipu na više mesta mesta pojavljuje promenljiva sa istim imenom, tada sva pojavljivanja te promenljive određuju isti tip. Stoga, funkcija id predstavlja funkciju tipa Int -> Int ili funkciju tipa [Char] -> [Char] i tako dalje, ali ne može predstavljati funkciju tipa Bool -> Float. Sam tip a -> a ukazuje na to da domen i kodomen funkcije moraju biti isti tipovi, prema tome id True ili id False mogu predstavljati samo vrednosti tipa Bool.

Prethodno navedene funkcije su bile trivijalne. Ipak, polimorfne funkcije su izuzetno korisne, i mi smo ih zapravo već koristili.

Funkciju head (koja vraća prvi element prosleđene liste), ima smisla primenjivati na liste tipa [Int], [Char], [Bool] itd... Prema tome, umesto da se za svaki tip liste definise posebna funkcija, definisana je jedinstvena funkcija head sa polimorfnim tipom [a] -> a:

head :: [a] -> a
head (x:_) = x
Podudaranjem oblika liste, možemo rastaviti listu na glavu i rep.

U ovoj funkciji, vidimo da se tipska promenljiva pojavljuje unutar zagrada []. Prema tome [a] ne predstavlja svaki tip, vec predstavlja svaki tip lista. Npr. tip Int nije oblika [a], i stoga head 3 nije dobro tipiziran izraz. Sa druge strane, ako head primenimo na [True, True, False] tada Haskel "poklapa" domen [a] sa tipom [Bool], odakle sledi da a mora biti Bool. Prema tome, head [True, True, False] je vrednost tipa Bool.

I ostale funkcije koje smo do sada koristili za rad sa listama (poput tail, init, last, null) imaju polimorfne tipove. Samim tim te funkcije se mogu koristiti za listom bilo kog tipa.

Zadatak 1. Kog tipa je funkcija null koja određuje da li je lista prazna?

Funkcija null se može primeniti na listu bilo kog tipa, te je njen domen [a]. Rezultat provere je logička vrednost. Stoga je null :: [a] -> Bool.

Nije nužno da vrednost polimorfnog tipa bude funkcija.

Primer 5.

prazna lista [] je sama za sebe vrednost tipa [a], i samim tim predstavlja praznu listu bilo kog tipa. Ali to ne znači da, na primer, prazne liste u izrazima [] ++ [True] i [] ++ ['a'], predstavljaju iste vrednosti. Jedna od navedenih praznih lista je tipa [Bool] a druga [Char]. Ove dve vrednosti nisu iste, ali poseduju isto ime4.

Iz prethodnih redova vidimo da polimorfne vrednosti poprimaju različiti tip u zavisnosti od izraza u kojima se nalaze (otuda i naziv poli-morfne).

Ponekad nije dovoljno da tip poseduje samo jednu promenljivu. Ako se u tipu nalaze dve ili vise razlicitih promenljiva, tada su te promenljive nezavisne.

Primer 6.

Funkcija fst uzima proizvoljan uređen par i vraća prvu koordinatu. Funkcija fst mora da nam vrati prvu koordinatu para (True, 2) :: (Bool, Int) ili ("Pi", 3.141) :: ([Char], Float) ili bilo kog drugog uređenog para. Stoga domen funkcije fst moramo zapisati kao (a, b), a samim tim je kodomen a, odnosno važi fst :: (a, b) -> a5. Ilustrovano na konkretnom slučaju, ako fst primenimo na (2, 'a') :: (Int, Char) tada se fst ponaša kao funkcija tipa (Int, Char) -> Int.

Naravno, tip funkcije snd je (a, b) -> b. U ovom tipu, tipska promenljiva b vezuje tip druge koordinate sa tipom povratne vrednosti (tj. kodomenom).

Zadatak 2. Kog tipa je funkcija reverse koja obrće listu?

Funkcija reverse uzima listu bilo kog tipa i vraća listu istog tipa. Prema tome tip ove funkcije je [a] -> [a]. Tip funkcije reverse nije [a] -> [b] jer tipske promenljive a i b mogu predstavljati različite tipove.

Zadatak 3. Implementirati funkciju koja vraća prvu koordinatu proizvoljne uređene trojke.

Proizvoljna uređena trojka ima tip oblika (A, B, C) gde su A, B, C neki proizvoljni tipovi. Stoga polimorfni tip koji predstavlja svaku uređenu trojku je (a, b, c). Podudaranjem oblika možemo konstruisati narednu funkciju:

fst3 :: (a, b, c) -> a
fst3 (x, _, _) = x
Pošto ne koristimo vrednosti druge i treće koordinate, te vrednosti ne moramo da vezujemo novim imenom, već možemo iskoristi džoker.

Zadatak 4. Implementirati funkciju zameni :: (a, b) -> (b, a) koja menja mesta koordinatama uređenog para.

Zadatak 5. Implementirati funkciju razmeni :: (a, b) -> (c, d) -> ((a, d), (c, b)) koja razmenjuje koordinate dva uređena para.

Iz prethodnih primera , vidimo da su tipovi sa slobodnim tipskim promenljivama izuzetno korisni. Mnoge funkcije iz prelida koje smo već koristili su parametarski polimorfne. Ipak, tehnika parametarskog polimorfizma i dalje ne rešava konkretan problem koji smo opisali na početku lekcije. Tada smo hteli da implementiramo funkciju koja bi prihvatala razne numeričke tipove. Tu funkciju ne možemo definisati sa sledećim kodom, koliko god to bilo privlačno:

f :: a -> a
f x = 253 * x + 17

Prethodna definicija nije dobra, iz prostog razloga zato što tipska promenljiva a može predstaviti svaki tip. Za tipove poput Char ili Bool, navedena definicija nema smisla jer se vrednosti tog tipa ne mogu sabirati niti množiti brojevima. Stoga bi bilo korisno kada bismo ograničili tipsku promenljivu a samo na one tipove koji dozvoljavaju sabiranje i množenje. Upravo ćemo to prikazati u nastavku.

Ad-hoc polimorfizam

Slobodne tipske promenljive mogu predstavljati bilo koji tip. Ponekad je poželjno ograničiti tipsku promenljivu na neku specifičnu kolekciju tipova. Upravo takva ograničenja se izražavaju kroz klase tipova.

Tipska klasa (ili skraćeno klasa6) je kolekcija tipova koji dozvoljavaju pozivanje određenih funkcija nad tipovima te klase. Često te funkcije nazivamo metode klase. Ime konkretne klase uvek počinje velikim slovom. Za svaki tip koji pripada nekoj klasi kažemo da je instanca te klase.

Primer 7.

U Haskelu je definisana klasa Show koja poseduje metodu show. Tipovi poput Int ili Bool pripadaju ovoj klasi, i nad njima se moze pozivati funkcija show koja vrednost nekog tipa predstavlja kao nisku.

ghci> show True
"True"  
ghci> show 2
"2"

Sa druge strane, bilo koji tip funkcija (npr. tip Int -> Bool) ne pripada ovoja klasi, te izraz poput show (\x -> x == 0) nije dobro tipiziran.

Primer 8.

Ugrađena klasa Num predstavlja sve tipove cije vrednosti se mogu sabirati, oduzimati i množiti. Između ostalog, tipovi Int, Integer, Float i Double pripadaju ovoj klasi.

Za razliku od vrednosti koja pripada samo jednom tipu, tipovi se mogu istovremeno nalaziti u više7 klasa (ili nijednoj). Činjenicu da neki tip T pripada klasi C, označavamo sa C T.

Korišćenjem klasa, moguće je ograničiti tipsku promenljivu na neku određenu klasu. Klasno ograničenje kojim se tipska promenljiva a ograničava na tipove klase C se zapisuje kao C a. Ovakva ograničenja se razdvajaju od samog tipa sa strelicom =>. U slučaju da postoji vise ograničenja, tada se ta ograničenja navode u obliku uređene \(n\)-torke.

Primer 9.

Tip Show a => a označava sve tipove koji pripadaju klasi Show.

Primer 10.

Tip Show a => a -> [Char] je tip polimorfne funkcije čiji domen je neki tip klase Show, a kodomen tip niska.

Funkcija show koja vrednost preslikava u nisku ima upravo ovaj tip.

Primer 11.

Tip (Num a, Show b) => a -> b označava tip polimorfne funkcije čiji domen je neki tip iz klase Num, a kodomen neki tip iz klase Show.

Klase su korisne jer omogućavaju korišćenje funkcija sa istim imenom nad različitim tipovima što značajno pojednostavljuje programiranje.

U Haskel programima često se koriste naredne klase:

  1. Klasa Show je klasa svih tipova čije vrednosti mogu da se reprezentuju kao niska. Nad vrednostima instance klase Show dozvoljeno je pozivati funkciju show koja vrednost pretvara u nisku.
  2. Klasa Eq je klasa svih tipova čije vrednosti se mogu porediti sa operatorima == i /=.
  3. Klasa Ord je klasa svih tipova čije vrednosti se mogu porediti pomoću operatora <, <=, > i >=.
  4. Klasa Num je klasa svih tipova čije vrednosti se mogu sračunavati sa operatorima +, -, *.
  5. Klasa Fractional je podklasa klase Num koja sadrži tipove nad kojima se može pozivati operacija /.

Tipovi poput Bool, Int, Integer, Float, Double, Char pripadaju klasama Eq, Ord i Show. To znači da vrednosti ovih tipova možemo da poredimo sa ==, /=, <, <=, >, >= kao i da pretvaramo u niske sa funkcijom show.

Tipovi Int, Integer, Float, Double svi pripadaju klasi Num. Vrednosti ovog tipa možemo da sabiramo, oduzimamo, i množimo sa operatorima +, -, *. Tipovi Float i Double pripadaju klasi Fractional8.

Primer 12.

Sa tipskim klasama moguće je rešiti probleme o kojima smo govorili na početku ove lekcije. Funkciju iz prvog primera, \(y = f(x) = x \cdot x + x\) možemo implementirati tako da sa jednom definicijom obuhvatimo sve numeričke tipove:

f :: Num a => a -> a
f x = x*x + x

Gornji kôd je dobro tipiziran iz narednog razloga: za svaku vrednost x :: N gde je N neki tip iz klase Num, važi da je x*x dobro tipizaran izraz tipa N. Kako su izrazi x*x i x takođe tipa N, sledi da je x*x + x :: N. Prema tome, primenom funkcije f na neku vrednost x :: N dobijamo takođe vrednost N.

Ako definišemo naredne numeričke konstante različitih tipova, uverićemo se da je f moguće primeniti na svaku od njih:

x1 :: Int
x1 = 5

x2 :: Integer
x4 = 5

x3 :: Float
x3 = 5

x4 :: Double
x4 = 5
ghci> f x1
30
ghci> f x2
30
ghci> f x3
30
ghci> f x4
30

Iz primera vidimo da klase zaista omogućavaju polimorfizam: vrednost f kao da istovremeno poseduje različite tipove Int -> Int, Integer -> Integer, Float -> Float i Double -> Double.

Važno je imati na umu da metode neke određene klase su različite funkcije (što možda nije očigledno u prethodnom primeru). Na primer, operacija + u smislu tipa Int nije isto što i operacija + u smislu tipa Integer iako na prvi pogled ne deluje tako.

Primer 13.

Definišimo:

y1 :: Int
y1 = 12345678987654321

y2 :: Integer
y4 = 12345678987654321

y3 :: Float
y3 = 12345678987654321

y4 :: Double
y4 = 12345678987654321
ghci> f y1
-1878358338631772398
ghci> f y2
152415789666209432556012777625362
ghci> f y3
1.5241581e32
ghci> f y4
1.5241578966620942e32
Prilikom računanja f y1 došlo je do prekoračenja zbog čega je krajnji rezultat besmislen. Tip Integer omogućeje predstavljanje proizvoljne velikih celih brojeva, te f y2 daje tačan rezultat. Tipovi Float i Double mogu predstaviti mnogo veće brojeve nego Int ali po cenu tačnosti: vidimo da poslednja dva rezultata imaju samo \(8\) odnosno \(18\) tačnih cifara.

Ako smo u pretposlednjem primeru demonstrirali kako klase omogućuju polimorfizam, u poslednjem primeru smo uvideli zašto se taj polimorfizam naziva ad hoc: konkretna implementacija metoda razlikuje se od klase do klase!

Naravno, i dalje nismo razjasnili mnoge stvari oko klasa, kao na primer kako se klase definišu ili kako se tipovi uvode u klase. Više o tipskim klasama, njihovom definisanju i instanciranju, biće rečeno u lekciji Tipske klase. Za sada je bitno samo da razumemo tipske potpise koji sadrže klasna ograničenja.

Klase i složeni tipovi

Videli smo u lekciji o tipovima, da je od postojećih tipova moguće dobiti nove tipove lista i proizvode (npr. od Int dobijamo [Int]). Deluje prirodno da se neke osobine tipova prenose pri ovom procesu. Tačnije, ako tip T pripada nekoj klasi C, tada je nekad smisleno da i [T] ili (T,T) pripadaju istoj to klasi. Srećom, ovakva "nasleđivanja" su moguća i prisutna u Haskelu.

Ako neki tip A pripada klasi Eq, tada i tip [A] pripada klasi Eq. To praktično znači da ako se vrednosti nekog tipa mogu porediti sa operatorima == i /=, tada se i liste tog tipa mogu porediti sa istim operatorima. Poređenja lista se vrše član po član. Analogno važi i za klase Ord i Show: ako tip A pripada nekoj od ovih klasa, tada i [A] pripada istoj klasi. Prema tome, tipovi poput [Bool], [Int], [Char] ali i [[Bool]], [[[Int]]] itd... pripadaju klasama Eq, Ord i Show.

Primer 14.

Kako tip Int pripada klasi show, sledi da i tipovi [Int] i [[Int]] pripadaju istoj klasi.

ghci> show [1,2,3]
"[1,2,3]"
ghci> show [[1,2],[3,4],[5,6]]
"[[1,2],[3,4],[5,6]]"

Za uređene \(n\)-torke važi sličan princip. Ako tipovi A, B, ... T pripadaju klasi Eq, tada i tip (A, B,..., T) pripada klasi Eq. Ovo takođe važi i za klase Ord i Show.

Primer 15.

Već znamo da vrednosti tipa Int i Char možemo da poredimo sa ==:

ghci> 8 == 2 * (3 + 1)
True
ghci> 'a' == 'b'
False

Prema gore napisanom, i vrednosti tipa (Int, Char) možemo da poredimo:

ghci> (2, 'a') == (3, 'b')
False
ghci> (2, 'a') == (2, 'a')
True

Dva uređena para će biti jednaka samo ako su im jednake prve koordinate i jednake druge koordinate.

Što se tiče poređenja sa > i <, ono se kao i kod lista vrši leksikografski, koordinate se porede par po par.

ghci> (2, 'a') < (3, 'b')
True
ghci> (100, 'W') < (100, 'A')
False
Prvo poređenje je tačno jer je 2 < 3, i pri ovom poređenju nije ni došlo do provere 'a' < 'b'. Rezultat drugog poređenja sledi iz činjenice da je 'A' < 'W' a ne 'W' < 'A'.
Zadatak 6. Da li tip [(Int, Char)] pripada klasi Eq?

Da. Oba tipa Int i Char pripadaju klasi Eq. Prema tome, i tip (Int, Char) pripada klasi Eq, jer uređen par dva tipa iz klase Eq takođe pripada klasi Eq. Sada, iz toga što (Int, Char) pripada klasi Eq sledi da i tip [(Int, Char)] pripada klasi Eq.

I zaista, vrednosti tipa [(Int, Char)] možemo da poredimo sa == i /=:

ghci> [(2,'a')] == [(3,'b'), (2,'a')]
False
Zadatak 7. 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 vrednosti ovog tipa mogu porediti sa <, >, <= i >=.

Ako su nam date vrednosti x, y i z tada ćemo prvo naći veću od vrednosti 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

Zaključivanje tipova

GHC kompajler (skoro) uvek može da zaključi tip izraza. Zbog toga često nije neophodno navoditi tipske dekleracije u izvornim datotekama.9

Zaključivanje tipova nam takođe omogućava da u GHCi okruženju proverimo tipove izraza. To možemo uraditi pomoću naredbe :type nakon koje navodimo izraz čiji tip nas interesuje:

ghci> :type [True, False]
[True, False] :: [Bool]
ghci> :type [True, False] !! 1
[True, False] !! 1 :: Bool
ghci> :type length [True, False]
length [True, False] :: Int
ghci> :type (True, 'a')
(Bool, Char) 
Primetimo da se :type komanda odnosi na ceo ostatak linije. Stoga nije neophodno pisati :type ([True] !! 0), već samo :type [True] !! 0, itd...

Zaključivanje tipova je složen proces, ali možemo demonstrirati osnove ovog procesa.

  1. Pri zaključivanju tipa izraza [True, False], kompajler prvo uviđa da se radi o listi. Prema tome, prvo se zaključuje da je tip ovog izraza [a] za neki neodređeni tip a. Zatim, kompajler uvidom u prvi element ove liste zaključuje da je tip a baš Bool, pa je samim tim tip celog izraza [Bool]. Naravno pri ovom procesu se proverava da svi ostali elementi liste imaju isti tip.
  2. Operacijom !! se pristupa \(n\)-tom elementu liste. Kako lista u izrazu [True, False] !! 1 ima tip [Bool] sledi da izraz [True, False] !! 1 ima tip Bool.
  3. Izraz length [True, False] je nešto složeniji. Funkcija length poseduje polimorfan tip [a] -> Int. U ovom slučaju jasno je da će povratna vrednost funkcije imati tip Int. Ali svakako, pri proveri dobre tipiziranosti celog izraza kompajler prvo mora da utvrdi da argument poseduje tip koji se može poklopiti sa [a].

Rezultat :type naredbe će nas iznenaditi u slučaju numeričkih literala:

ghci> :type 2
2 :: Num a => a

Za izraz 2 GHCi nije mogao da zaključi konkretan tip. Literal 2 može predstavljati vrednost tipa različitih numeričkih tipova. Zbog toga GHCi zaključuje da je 2 nekog tipa a koji pripada klasi Num, sto se naravno označava sa Num a => a.

Ako potražimo tip izraza 3.14 uvidećemo da je u pitanju tip Frac a => a. Svakako izraz 3.14 predstavlja jedan racionalan broj, te ne može biti tipa Int ili Integer. Ali racionalan broj može biti predstavljen i kao Float i kao Double vrednost. Zbog toga ni ovde nemamo konkretan odgovor već klasno ograničenje.

Karijevanje

U lekciji o funkcijama prikazana su dva načina definisanja funkcije sa više parametara. 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 opredeliti10. Ono što je zanimljivo je da je Haskel toliko ekspresivan jezik da se lako može definisati funkcija višeg reda koja transformiše funkciju tipa (A, B) -> C u funkciju tipa A -> (B -> C) (gde su A, B i C neki tipovi). Ovakva transformacija se naziva karijevanje11. 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 primena k f predstavlja vrednost tipa A -> (B -> C), podudaranjem oblika možemo definisati da k f predstavlja neki lambda izraz \x y -> .... U telu ovog lambda izraza 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)

U definiciji funkcije k nigde nismo koristili činjenicu da radimo sa vrednostima tipova A, B ili C, tj nismo iskoristili osobine ovih tipova. Prema tome, funkciju k možemo učiniti u potpunosti parametarski polimorfnom:

k :: ((a, b) -> c) -> (a -> (b -> c))
k f = \x y -> f (x, y)
Umesto na funkcije nekog konkretnog tipa, sada se k može primeniti na sve funkcije čiji je domen uređen par.

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 \(x, y) -> .... U telu lambda funkcije je potrebno da se nađe vrednost (g x) y. Stoga, k' možemo da definišemo sa narednim kodom

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

Uverimo se da navedene funkcije zaista dobro rade. Uzmimo primer is lekcije o funkcijama:

ljubav :: (String, String) -> String
ljubav (x, y) = x ++ " voli " ++ y ++ "!"
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 8. Konstruisati funkciju curry3 koja uzima funkciju tipa (a, b, c) -> d i daje funkciju tipa a -> (b -> (c -> d)).

Zadatak 9. Konstruisati funkciju curry2x2 koja uzima funkciju tipa (a, b) -> (c, d) -> e i daje funkciju tipa a -> (b -> (c -> (d -> e))).

Zadatak 10. Kog tipa je izraz uncurry curry?