34 - a Future monád¶
A következő programozási technikával JavaScriptben is nagyon gyakran találkozunk.
prímtényezőkre bontás¶
Hogy megnézzük, mi is a jelenség, először is írjunk egy metódust, ami a naiv,
kettőtől négyzetgyökig forciklussal felfelé osztogatással egy Vector
ba teszi
az input Long
típusú szám prímtényezőit!
1 2 3 4 5 6 7 8 9 |
|
Válasz mutatása
konzollal beszélgetés¶
Első nekifutásra a tervünk egy olyan app, aminek a konzolban be tudunk gépelni (mondjuk pozitív egész) számokat és kiírja nekünk a prímtényezős felbontását, a tényezők közt csillagokkal.
Először írjunk egy showResult: (Long,Vector[Long]) => String
függvényt, ami visszaadja
az argumentumban kapott longot, egyenlőségjel, majd a vektorban lévő számokat,
csillaggal szeparálva őket! Ehhez érdemes a Vector
osztály mkString
metódusát
használnunk.
1 2 |
|
Válasz mutatása
Írjunk most egy processInput: String => String
függvényt, mely kap egy
stringet (ahogy a felhasználó begépelte), ezt megpróbálja Longgá alakítani,
ha sikerült, akkor visszaadja a prímtényezőkre felbontás eredményét az előző
függvény szerint formázva, ha nem, akkor azt adja vissza, hogy "Not an integer: "
és
magát a kapott inputot!
1 2 3 4 5 |
|
Válasz mutatása
Most pedig írjunk egy mainLoop: Unit
függvényt, mely rendre
- kiírja, hogy írj be egy számot, vagy Q-t a kilépéshez,
- vár a usertől egy szövegbevitelt (erre a
scala.io.StdIn
objektumreadLine()
metódusa alkalmas), - (hibatűrés jelleggel) levágja a kapott string elejéről-végéről a whitespace-eket, majd nagybetűssé alakítja,
- ha így a
"Q"
stringet kapta, akkor terminál a függvény, - ha nem, akkor az előző metódussal feldolgozza az kapott stringet, kiírja a képernyőre az eredményt és várja a következő inputot!
1 2 3 4 5 6 7 8 9 |
|
Válasz mutatása
a probléma¶
A baj az, hogy ez a faktorizáló algoritmus elég lassú, ha nagy inputot kap és ilyenkor, mivel minden
számítás a fő szálon megy, ha egy olyan longot adunk be neki, mellyel sokáig elfoglalkozik a kódunk,
mint pl. a 4611686018427387847
(ez egy prímszám, ezért ezt végigosztja a szintén milliárdos
nagyságrendű négyzetgyökéig, eltart egy darabig), egész addig blokkolva lesz minden, amíg ezzel nem végzünk.
(Próbáljuk ki!)
a megoldás¶
A legritkább esetben akarjuk azt, hogy egy esetleg hosszabb művelet (ami lehet egy adatbázis-lekérdezés, valami internetes erőforrás lekérése, egy komplexebb objektum elkódolása és mentése, stb.) blokkolja a fő szálunkat, elég bután nézne ki egy olyan böngésző is, amivel semmit nem tudnánk kezdeni egészen addig, amíg be nem tölti az aktuális böngészőfület teljesen.
Erre egy szokásos eljárás, hogy egy másik szálon elindítjuk a számításigényes folyamatot, és azonnal visszaadjuk a vezérlést a fő szálra.
Ezt a célt szolgálja a scala.concurrent.Future
monád: alapvetően a Future[T]
-nek a unit
ja
(ami persze megint a companion object apply
metódusa) megkapja név szerint a T
típusú
kifejezést, és pontosan ezt teszi: egy másik szálon elkezdi kiértékelni a kapott kifejezést, a hívó
szál pedig tudja folytatni a futást, a kifejezés értéke pedig persze egy Future[T]
objektum lesz,
amiről mindjárt kicsit többet fogunk tudni.
Egy apróság van, amiben különbözik attól, amit eddig láttunk: valójában a Future.apply[T]
nem egy
Future[T]
-t ad vissza, hanem egy ExecutionContext => Future[T]
függvényt, tehát hogy ténylegesen
el is indítsa a számítást, át kell adnunk neki egy végrehajtási környezetet is - ebbe most részleteiben
nem megyünk bele, ahogy abba sem, hogy ez egy implicit paraméter, ami azt jelenti, hogy ha nem
szállítunk neki ilyen környezetet, akkor próbál keresni egy alapértelmezettet és azzal elkészíteni
a Future[T]
objektumot. Az implicit paraméterek a Scalának egy eléggé kontroverziális része és
talán annyira nem is core element a funkcionális programozásban, ezért ennek a kurzusnak a keretein
belül nem mélyedünk el benne jobban, csak annyira, hogy tudjunk dolgozni egy Future
objektummal:
1 2 3 4 5 6 |
|
App
osztályunk törzsébe, akkor mindössze ennyit fogunk látni:
Mi is történik itt?
- először a fő szál kiírja, hogy
Hello!
- majd egy másik szálon elindul annak a nagy számnak a hosszadalmas művelete, de a fő szál visszakapja a vezérlést,
- a fő szál kiírja szinte azonnal ezután, hogy
Bye!
- és ami fontos: ezután a főszál ahogy terminál, kilövi az összes többi szálat is, amit ő elindított, így a számítás nem fog végig lefutni.
Érdemes megjegyeznünk, hogy ha akkor terminálhat a programunk, mikor egy Future
számítás még zajlik, akkor nincs
semmi garancia arra, hogy a future kiértékelés is befejeződik.
De ha ezt a mainLoop
unkba tesszük bele:
1 2 3 4 5 6 7 8 9 |
|
A Future[T]
majdani eredményének további felhasználása¶
Ez eddig nem rossz, akkor, ha tényleg csak annyit szeretnénk csinálni, hogy egy Unit
típusú kifejezést
kiértékelni. Ilyen szerencsénk ritkán van: inkább a ha kiértékelted a kifejezést, akkor csináld az eredménnyel
ezt-és-ezt a jellemző, amit tennünk kell. Sok programozási nyelven ezt úgynevezett callback függvények
átadásával érjük el: egy aszinkron futó függvény az inputján kívül megkap egy függvényt is, amit akkor kell
lefuttasson, ha az input kiértékelése már lezajlott.
Ha a Future[T]
-t használjuk, akkor ehelyett a szokásos monadikus metódusokat javallott alkalmazni:
-
Future[T].map[U]( f: T=>U ) : Future[U]
- amap
metódus kap argumentumként egy függvényt. Ha egyszer végez aT
kiszámításával aFuture
dobozunk, akkor a kapott értéket behelyettesíti a kapottf
függvénybe és kiértékeli. Ennek az eredménye egyFuture[U]
típusú Future doboz lesz. -
Future[T].foreach[U]( f: T=>U ) : Unit
aforeach
metódus szintén a szokásos módon kap egy függvényt, melyet ha sikerült kiértékelni aFuture
dobozunkban a kifejezést, behelyettesíti azf
metódusba. Itt, ahogy máskor is szoktunk,Unit
ot kapunk, így ezeket azf
-eket rendszerint a mellékhatásuk miatt szoktuk hívni. -
Future[T].flatMap[U]( f: T => Future[U] ): Future[U]
aflatMap
pedig, hasonlóan amap
hez, ha sikerül kiértékelnie a kapott kifejezést, akkor ezzel példányosít egyFuture[U]
-t.
Mindhárom metódus azonnal visszatér és megkapjuk a Future
dobozunkat.
Hogyan írhatnánk át a fenti Future( println( processInput( line ) ) )
hívást ezek valamelyikének használatával?
1 |
|
1 |
|