A Scala programozási nyelv

For-comprehensionök

For-Comprehension

A listák feldolgozására nagyon hasznosak tudnak lenni a magasabbrendű függvények (ld. map, filter és társaik), de az így alkotott kifejezések könnyen olyan bonyolulttá válhatnak, hogy már túlzottan magas szintű absztrakcióra van szükség a megértésükhöz. A for-comprehensionökkel ugyanazokat a szekvenciális adatszerkezeteken végzett műveleteket lehet megoldani kényelmesebb jelölés mellett. Első látásra ezek a kifejezések a halmazkifejezéseknek, relációs adatbázis lekérdezéseknek és a for ciklusoknak az egyvelege, nem véletlenül.

Tegyük fel, hogy van egy persons típusú elemekből álló sorozatunk, ahol a persons-nak van name és age adattagja. A sorozat lehet egy tömb, egy lista, egy iterátor, vagy egyéb szekvenciális adatszerkezet. Ahhoz hogy meghatározzuk az összes 20 évnél idősebb ember nevét írhatjuk a következőt:

for { val p <- persons; p.age > 20 } yield p.name

Ez a következővel ekvivalens:

persons filter (p => p.age > 20) map(p => p.name)

Egy for-comprehension alakja a következő:

for ( s ) yield e

Itt s generátoroknak, értékadásoknak és szűrőknek a sorozata. A sorozat egy generátorral kell kezdődjön. Ha több generátor van, akkor a későbbiek értékei előbb iterálódnak.

Lássuk, hogyan feleltethetőek meg az egyes for-comprehensionök a listán alkalmazott magasabbrendű függvényekkel alktotott kifejezésekkel.
Legyen adott az n pozitív egész szám. Keressük meg az összes pozitív egész i, j párokat,ahol 1 <= j < i <= n, úgy hogy i + j prím:

for { val i <- range(1, n); val j <- range(1, i-1); isPrime(i+j) } yield (i,j)
Ugyanez a feladat listákkal megoldva:
range(1, n) .flatMap(i => range(1, i) .filter(j => isPrime(i+j)) .map(j => (i, j)))

Ezek után a for ciklusokkal való hasonlóságot is érdemes megfigyelni, illetve kihasználni, ugyanis van a for-comprehensionöknek egy külön változata ami a for ciklusok kifejezésére szolgál:

for ( s ) e
Egy példa:
for (i <- 1 to 10) { System.out.println(i) }
A 1 to 10 jelölés nem más mint a Range(1,10) kifejezésre egy szintaktikai cukorka.

A for-comprehension-ök ugyanakkor nagyon hasonlítanak az adatbázis lekérdező nyelvek szokásos műveleteihez. Tekintsük könyveknek egy sorozatát:

abstract class Book with { val title: String; val authors: List[String] } val books: List[Book] = [ new Book with { val title = "Structure and Interpretation of Computer Programs"; val authors = ["Abelson, Harald", "Sussman, Gerald J."]; }, new Book with { val title = "Principles of Compiler Design"; val authors = ["Aho, Alfred", "Ullman, Jeffrey"]; }, new Book with { val title = "Programming in Modula-2"; val authors = ["Wirth, Niklaus"]; } ];

Keressük meg azokat a könyveket, amelyeknek a szerzője az Ullman vezetéknevet viseli:

for { val b <- books; val a <- d.authors; a startsWith "Ullman" } yield b.title

Vagy azok a könyvek, melyek címében szerepel a Program szó:

for { val b <- books; (b.title indexOf "Program") >= 0 } yield b.title

Azok az emberek, akik legalább két könyvet írtak:

for { val b1 <- books; val b2 <- books; b1 != b2; val a1 <- b1.authors; val a2 <- b2.authors; a1 == a2 } yield 1

Az utolsó példában egy szerző a végeredményben többször is szerepelhet. Ez korrigálható:

def removeDuplicates[a](xs: List[a]): List[a] = if (xs.isEmpty) xs else xs.head :: removeDuplicates(xs.tail filter (x => x != xs.head));