A Scalában egy függvény első osztályú érték, vagyis mint bármilyen értéket, átadhatjuk paraméterként vagy visszatérhetünk vele. Az olyan függvényeket melyek más függvényeket fogadnak el paraméterül, vagy függvény a visszatérési értékük, magasabbrendű függvénynek nevezzük.
A lenti példában a sum
egy magasabbrendű függvény:
A fenti példában a square függvényt csak egyvalamire használtuk, hogy paramétere legyen a sumnak. Ha már ennél bonyolultabb magasabbrendű függvényeket is szeretnénk használni, akkor egy idő után fárasztó lesz a square-hez hasonló egyszer használatos függvényeknek nevet adni. A névtelen függvények, azaz a lambda kifejezések használatával ez a probléma orvosolható. Sokkal olvashatóbb és könnyebben is írható kódot eredményez, amellett hogy a funkcionális paradigmának alapköve a lambda kifejezések használata.
Scalában a következőképp írható fel egy ilyen függvény:
Egy ehhez hasonló kifejezésnek a felhasználásával viszont egy sorban is definiálható egy másik függvény pl. a korábbi példában szereplő sum
segítségével. Érdemes kihasználni a típuskikövetkeztetést hogy a kódunk még olvashatóbb legyen:
Mint oly sok más jelölés a Scalában, a névtelen függvények jelölése is szintaktikus cukorka, ami a következőnek felel meg:
f
láthatóságának köszönhetően a blokkon kívül nem is interferál semmivel, miután a blokk végén egyszer végrehajtjuk.
A sumInts
és sumSquaresnél
feltűnő hogy az a
és b
paraméterek látszólag csak foglalják a helyet, mivel azon kívül hogy közvetlen átadódnak a sum függvénynek nincsen semmi hasznuk. Curryzéssel tovább lehetne rövidíteni a két függvény felírását, ha a sum
függvényt egy kicsit másképp definiáljuk:
Tehát Scalában egy függvény curryzhetőségét külön jelölni kell annak létrehozásakor. Most már ennek az új sumnak a segítségével
sum(x => x*x)(1,2)
kiértékelése a következőképpen történik: először a névtelen függvényt applikálódik a sum
ra, majd az így kapott parciális függvény kapja meg az (1,2)
paramétereket. Látszik ahogy ezzel a módszerrel képesek vagyunk bármely többparaméteres függvényt sok egyparaméteres függvény applikációjának sorozatára felbontani.
Aki programozott már Java-ban valószínűleg írt ehhez hasonló kódot:
Tegyük fel, hogy újraírhatnánk a Swing-et felhasználva a Scala minden előnyét. Ekkor megváltoztathatnánk az addActionListener()
metódust magasabb rendű függvények használatával.
Egy névtelen metódust adunk át az előbbi függvénynek. Ez a metódus egyetlen ActionEvent
típusú paramétert vár és meghívásakor egy egyszerű println()
-t hajt végre. A hatása ugyanaz mint, az előbbi Java kódnak azonban jóval egyszerűbb és átláthatóbb.
Ezt a példát még akár tovább is egyszerűsíthetjük a felesleges zárójelek elhagyásával:
Készítsünk egy elemzőt a következő nyelvtanhoz:
Az elemzőket leírhatjuk az alapján, hogy mely inputokat fogadják el. Au &&& jel két elemző szekvenciális kompozícióját, a ||| pedig alternatívát jelent. Ezek a műveletek a Parser
osztály metódusaiként lesznek megvalósítva. A következő elemzőkhöz definiálunk konstruktort:
Lesz két magasabb rendű függvény is. Ezek az opt
és a rep
. Az opt(p)
egy olyan elemző, mely a p
által elfogadott, vagy az üres stringet fogadja el. A rep(p)
a p
által elfogadott stringek tetszőleges sorozatát fogadja el.
A fentieket figyelembe véve a nyelvtanból a következőt kapjuk:
Most már csak az kérdés, hogy a fenti metódusokat hogyan valósítjuk meg. Ezeket egy modulban helyezzük el, az elemzők karakterek egy listáját kapják, abból állítják elő az eredményt:
Egy elemző vagy a None
konstanst adja vissza, ami azt jelenti, hogy nem volt legális az input, vagy egy Some(in1)
értéket, ahol in1
jelenti azt a részét az inputnak, amit az elemző nem tudott feldolgozni.Az elemzők olyan függvények, melyek a List[Char]
típusú adatból Parse.Result
értéket állítanak elő. Ezt a Parser
osztály fejezi ki, ami a megfelelő Function
osztály leszármazottja. A Function
osztályok definiálják az apply
metódust, a Parser
ezen kívül az &&&-t és a |||-t is:
A primitív elemzők a következők:
A magasabb rendű függvények:
A magasabb rendű függvények felhasználhatók saját vezérlési absztrakció írására. Programozási nyelvekben alprogramok, illetve függvények hasznos segítsége, hogy az ismétlődő forráskód-részletet, azaz a függvény törzsét csak egyszer kell megírni, hiszen hívásonként megegyező utasításokat kell végrehajtani. Természetesen ennek következménye, hogy a függvényeknek van egy hívásonként különböző része is, a paraméterlista. Ha a paraméterlistában forráskód-részletet (függvényt) is megadhatunk, akkor hívásonként más utasításokat használhatunk. A Scala nyelvben használhatunk magasabb rendű függvényeket, amelyek extra lehetőséget nyújtanak a forráskódunk egyszerűsítésére és tömörebbé formálására, ezáltal csökkenthető a forráskód redundanciája. Nézzünk egy példát egy fájlböngésző alkalmazásban. A felhasználónak keresni/kiválasztani kell a fájlok között valamely kritériumok szerint. Ilyen lehet mondjuk a kiterjesztés vizsgálata. Nézzük meg először, hogyan lehet a feladatot megvalósítani Java-ban.
Ahhoz, hogy ezt megvalósítsuk, implementálnunk kell a FilenameFilter interfészt, ismernünk kell működését, meg kell benne valósítani az accept() metódust, amelynek paraméterei automatikusan átadásra kerülnek. A paraméterként kapott String bejegyzésnévre meg kell fogalmazni az elfogadás feltételét (ezek hagyományos szövegműveletek lehetnek, például a név nagybetűssé alakítva, végződik-e valamire, illetve tartalmaz-e valamit). Mindezt úgy dolgozza fel a File osztály list() metódusa, hogy paraméterként egy olyan névtelen objektumot kap (belső osztályból létrehozva) amely implementálja a FilenameFilter interfészt. Mindezek megvalósítása magasabb absztrakciós szintet követel a programozótól, a forrásfájl nehezebben áttekinthető ráadásul fordításkor több forrásfájl keletkezik a belső osztályok miatt. Ezzel szemben Scala-ban ennek a feladatnak megvalósítása kevésbé absztrakt, szinte utasítás szintre viszi le a gondolkodást. Célszerű definiálni egy Scala singleton objektumban egy függvényt, amelynek paraméterül adhatjuk a kiterjesztést és a String osztály endsWith() függvényével könnyedén vizsgálhatjuk a fájlneveket.
A további igény, hogy a felhasználók bármilyen fájlnév darabra kereshessenek ekkor, már a contains() függvényt kell használnunk.
A két függvény egyformán dolgozik, megvizsgálják a filesHere összes elemének nevét és visszaadják a fájlt, ha igaz a feltétel. Az egyetlen különbség csak a felhasznált logikai függvényben van. A következő igény, hogy reguláris kifejezésre is lehessen keresni, amelyhez a String osztály matches() függvényét használhatjuk. Három hasonló metódushoz célszerű lehet bevezetni egy magasabb rendű segéd függvényt, amely képes fogadni paraméterül egy függvényt is, hogy csökkentsük az ismétlések számát.
Itt az első paraméter egy String (query), a második egy logikai függvény (matcher()), amelynek két String paramétere van. A filesMatching() függvényben meghívott matcher() függvény egyetlen célja ellenőrizni a fájlnevet a külső függvényben megadott első paraméterrel. Az így megírt függvényt felhasználva a következő kereső függvényeket használhatjuk:
A filesMatching() belső függvény két String paramétert vár, ezért használható a _ helyettesítő paraméter, ahol nem kell megadni a paraméter típusát. A rövidebb szintaktika teljesen ekvivalens a következő két sorral: