Haskellben a magasabb rendű függvények olyan függvények, amelyek-nek bemeneti paraméterük és kimeneti értékük is lehet függvény. Angolul higher-order function a megnevezésük.
4.21. feladat Írjunk egy Haskell-függvényt, amely a paraméterként meg-adott függvényt duplán alkalmazza egy megadott bemeneten.
duplaz :: (a -> a) -> a -> a duplaz fg x = fg (fg x)
> duplaz (\ x -> x + 1) 10 12
> duplaz sqrt 2 1.189207115002721
A duplaz magasabb rendű függvény, mert az fgparaméter egy függvény.
Működés szempontjából az fgfüggvényt kétszer alkalmazza a második pa-raméterére.
A Haskellben több könyvtárfüggvény is létezik, amely magasabb rendű függvény, ilyenek például a map, filter, foldr, foldl stb. A továb-biakban a map és a filter függvények működését tárgyaljuk, a többi függvényre a jegyzet későbbi fejezeteiben kerül sor.
Amapfüggvénynek két argumentuma van: a második egy lista, az első pedig egy függvény, amelyet alkalmaz a lista minden elemére.
4.22. feladat Írjunk egy olyan Haskell-lekérdezést, amely meghatározza a paraméterként megadottDoubleelemtípusú lista elemeinek négyzetgyökét.
> map sqrt [3.0, 4.0, 5.0, 6.0, 7.0]
[1.7320508075688772, 2.0,2 .23606797749979, 2.449489742783178, 2.6457513110645907]
4.9. Magasabb rendű függvények 59 Az előző fejezetben megírtnovelLs,novelLsP, illetvevaltakoz függ-vények is megadhatók map-et használva. Figyeljük meg, hogy a függvények lista típusú bemeneti paraméterei nincsenek feltüntetve. Haskellben egy függvény utolsó paraméterei ugyanis elhagyhatók, ha a törzsében is, szá-mításba véve a sorrendet, utolsó paraméterként jelennek meg.
novelLsMap :: (Num a) => [a] -> [a]
novelLsMap = map (+ 1)
novelLsPMap :: (Num a) => a -> [a] -> [a]
novelLsPMap p = map (+ p)
valtakozMap :: Integral a => a -> [Bool]
valtakozMap n = map even [0..n-1]
4.23. feladat Írjunk egy olyan Haskell-lekérdezést, amely meghatározza minden listabeli elemre a páros osztók listáját.
parosO :: (Integral a) => a -> (a, [a])
parosO n = (n, [i | i <- [2, 4..div n 2], mod n i == 0])
> parosO 60
(60,[2,4,6,10,12,20,30])
Első lépésként írtunk egyparosOfüggvényt, amely egy kételemű tuple típu-sú értéket határozott meg, ahol a tuple első eleme maga a bemenet, második eleme pedig egy lista, amelyben a bemenet páros osztóit generáltuk ki. Ha a bemenetnek nincs páros osztója, akkor az eredmény üres lista lesz.
Második lépésként meghívjuk a mapfüggvényt úgy, hogy első paramé-tere apárosOfüggvény, második pedig egy lista legyen, amelybe azokat az számokat írjuk, amelyeknek a páros osztóit szeretnénk meghatározni.
A következő lekérdezés megadja az 50és 59közötti számok páros osz-tóinak listáját.
> map parosO [50..59]
[(50,[2,10]),(51,[]),(52,[2,4,26]),(53,[]),(54,[2,6,18]), (55,[]),(56,[2,4,8,14,28]),(57,[]),(58,[2]),(59,[])]
A filter függvénynek is két argumentuma van: a második egy lista, az első pedig egy logikai függvény, amely alapján kiválasztásra kerülnek a listabeli elemek.
4.24. feladat Írjunk egy Haskell-függvényt, amely kiválasztja egy lista ele-mei közül a páros számokat.
parosLs :: (Integral a) => [a] -> [a]
parosLs = filter even
> parosLs [1..20]
[2,4,6,8,10,12,14,16,18,20]
Bemenetként általunk megírt logikai függvényt is megadhatunk. Pél-dául a négyzetszámok listáját a következőképpen is kigenerálhatjuk, ahol alkalmazni fogjuk a korábban megírt negyzetVfüggvényt:
> filter negyzetV [1..100]
[1,4,9,16,25,36,49,64,81,100]
A magasabb rendű függvényeknek van egy másik fontos jellemzőjük is:
részlegesen lehet paraméterezni őket. Ezt curryzésnek is mondjuk, Haskell Brooks Curry matematikus után. A részleges paraméterezés azt jelenti, hogy a függvényhívás megengedettlátszólag kevesebb paraméterrel is.
Például ha a páratlan prímszámok listáját szeretnénk kigenerálni100 -ig, akkor afilterés a korábban megírtprimTrészleges paraméterezésével ezt egy egysoros lekérdezésben megtehetjük:
> filter (primT 3) [3,5..100]
[3,5,7,11,13,17,19,...,83,89,97]
4.25. feladat Írjunk egy Haskell-függvényt, amely az x és k bemenetekre, ahol kegész szám, meghatározza azx0, x1, . . ., xk értékeket.
fugv1 :: (Integral a, Num b) => b -> a -> [b]
fugv1 x k = map (x ^) [0..k]
> fugv1 7 6
[1,7,49,343,2401,16807,117649]
Az implementáció során a map könyvtárfüggvényt használtuk, ahol a map függvény első paraméterét a hatványozó operátort, infix formában, részle-gesen paraméterezve adtuk meg.
4.26. feladat Írjunk egy Haskell-függvényt, amely az x és k bemenetekre meghatározza az 0k, 1k, . . ., xk értékeket.
4.9. Magasabb rendű függvények 61 fugv2 :: (Integral a, Num b, Enum b) => b -> a -> [b]
fugv2 x k = map (^ k) [0..x]
> fugv2 7 6
[0,1,64,729,4096,15625,46656,117649]
Vegyük észre, hogy a feladat most különböző alapú, de ugyanolyan hat-ványkitevőn levő értékeket kell kiszámoljon. Ezért a ^ operátort megint infix formában hívtuk úgy, hogy a ^ operátor első argumentuma rendre a [0..x]lista elemei legyenek.
Megjegyezzük azt is, hogy ha módosítjuk a zárójelezést, akkor a^ ope-rátor prefix formában kerül meghívásra, ami más eredményt fog adni. A következő kód a k0, k1, . . ., kx értékeket határozza meg.
fugv2_ :: (Integral a, Num b) => a -> b -> [b]
fugv2_ x k = map ((^) k) [0..x]
> fugv2_ 7 6
[1,6,36,216,1296,7776,46656,279936]
A következőkben másképp járunk el. A beépített operátor helyett meg-írjuk a saját hatványozó függvényeinket, és amapfüggvénynek ezeket adjuk meg paraméternek.
A myPow1 függvény tulajdonképpen a gyorshatványozás algoritmusa, első paramétere az alap, második a hatványkitevő lesz, ahol a rekurzív hí-vásra a where kifejezésben kerül sor. A rekurzív hívás eredményét atemp fogja jelölni, amelyet az őrfeltételekben szorzótényezőként használunk. A szükséges szorzásokat az határozza meg, hogy a hatványkitevő páros vagy páratlan szám.
myPow1 :: (Integral a) => a -> a -> a myPow1 x n
| n < 0 = error "Negativ kitevo"
| n == 0 = 1
| even n = temp * temp
| otherwise = x * temp * temp where
temp = myPow1 x (div n 2)
A következő függvényekben háromféleképpen kerül kiértékelésre a myPow függvény. A fugvA-ban prefix formában használjuk, hogy az
x0, x1, . . ., xk értékeket tudjuk meghatározni.
fugvA :: Integral a => a -> a -> [a]
fugvA x k = map (myPow1 x) [0..k]
> fugvA 7 6
[1,7,49,343,2401,16807,117649]
AfugvB-ben infix formában alkalmazzuk amyPow1függvényt, azért, hogy
a 0k, 1k, . . ., xk értékek kerüljenek kiértékelésre.
fugvB :: Integral a => a -> a -> [a]
fugvB x k = map (‘myPow1‘ k) [0..x]
> fugvB 7 6
[0,1,64,729,4096,15625,46656,117649]
A fugvC-ben alkalmazásra kerül a beépített flip függvény, amely se-gítségével a paraméterek sorrendjét lehet megváltoztatni, így most is a
0k, 1k, . . ., xk értékeket határozza meg a kiértékelés.
fugvC :: Integral a => a -> a -> [a]
fugvC x k = map (flip myPow1 k) [0..x]
> fugvC 7 6
[0,1,64,729,4096,15625,46656,117649]
Aflipfüggvény megértéséhez figyeljük meg a következő kiértékelések ered-ményeit.
> myPow1 2 10 1024
> flip myPow1 2 10 100
A következő myPow2függvény is azxn értékét határozza meg, de algo-ritmikailag különbözik amyPow1-től. Az alap négyzetre emelését a rekurzív függvényhíváskor valósítja meg, illetve a paraméterek sorrendjét is felcserél-tük.
myPow2 :: (Integral a, Num b) => a -> b -> b myPow2 n x
| n < 0 = error "negativ kitevo"
| n == 0 = 1
| even n = temp
| otherwise = x * temp where
temp = myPow2 (div n 2) (x * x)
4.10. Függvénykompozíció 63 Az olvasóra bízzuk, hogy az előzőfugvA, fugvB, illetvefugvCfüggvények mintájára, egy map-ben részlegesen paraméterezve alkalmazza amyPow2-t.
4.27. feladat Írjunk egy Haskell-függvényt, amely kiválasztja egy lista ele-mei közül az x-szel osztható számokat.
fugv3 :: (Integral a) => a -> [a] -> [a]
fugv3 x ls = filter (oszthato x) ls where
oszthato :: (Integral a) => a -> a -> Bool oszthato x y = mod y x == 0
> fugv3 7 [1..100]
[7,14,21,28,35,42,49,56,63,70,77,84,91,98]
Az oszthatofüggvénnyel korábban is foglalkoztunk. A függvény megvizs-gálja, hogy xosztója-ey-nak, ahol a függvénytörzset a korábbi változathoz képest egysorosra módosítottuk, és felcseréltük a paraméterek szerepét.
4.10. Függvénykompozíció
A függvénykompozíció a matematikából jól ismert művelet. A Haskell-ben lehetőségünk van hasonló művelet definiálására, jelölésére a pont (.) szimbólumot használjuk. A következő példában két könyvtárfüggvény kom-pozíciójával dolgozunk, a not-tal és azeven-nel.
4.28. feladat Írjunk egy Haskell-függvényt, amely kiválasztja egy lista ele-mei közül a páratlan számokat.
paratlanLs :: (Integral a) => [a] -> [a]
paratlanLs = filter (not . even)
> paratlanLs [1..20]
[1,3,5,7,9,11,13,15,17,19]
A korábbi duplaz függvény is megadható függvénykompozíciót alkal-mazva:
duplaz_ :: (a -> a) -> a -> a duplaz_ fg = fg . fg
> duplaz_ (+1) 10 12
A következő példában két könyvtárfüggvény kompozíciójával fogunk dolgozni, alkalmazzuk az inités tailkönyvtárfüggvényeket.
4.29. feladat Írjunk egy Haskell-függvényt, amely levágja a paraméterként megadott lista első és utolsó elemét.
levag :: [a] -> [a]
levag = init . tail
> levag "gezakekazeg"
"ezakekaze"
4.30. feladat Írjunk egy Haskell-függvényt, amely a paraméterként meg-adott 1-nél nagyobb természetes számokat tartalmazó listából kiválogatja az összetett számokat.
Az összetett számok kiválogatását az osszetett függvény végzi, a notésprimTfüggvényeken függvénykompozíciót alkalmazva, ahol aprimT függvényt korábban implementáltuk:
osszetett :: (Integral a) => [a] -> [a]
osszetett = filter (not . primT 3)
> osszetett [2..20]
[4,6,8,9,10,12,14,15,16,18,20]