Nézzük ezt a függvényt (mostantól már rendszerint nem fogom kiírni a Main objectet, ami eddig minden példában szerepelt) és egy meghívását:
def parosE(n: Int) = {
if (n % 2 == 0) "Páros" else "Páratlan"
}
println(parosE(2))
Ha futtatjuk, kiíródik, hogy "Páros". Amit a függvény törzsében látunk: egy if-else kifejezés, szintaxisa if (B) E else F, ahol E és F kifejezések, B pedig egy Boolean típusú kifejezés.
C-ben megszokhattuk, hogy az intek is jók ifbe, 0 hamis, többi igaz -- Scalában nem így megy, az if feltételébe Boolean kell menjen, az if(n%2) nem fordul. A százalék a C-ben megszokott módon a maradékos osztás maradékát adja eredményül, tehát pl. n%2 a páros számokra 0, a páratlanokra pedig 1 lesz. Az if-else kifejezés kiértékelésének a szabályai:
- először kiértékeljük a feltételt, lesz belőle true vagy false,
- ha true, akkor a kifejezést E-re írjuk át, ha false, akkor F-re,
- és folytatjuk a kiértékelést.
Tehát pl. most a parosE(2) hívásakor a substitution model szerint egy if(2%2==0) "Páros" else "Páratlan", majd egy if(0==0) "Páros" else "Páratlan", majd egy if(true) "Páros" else "Páratlan", végül a "Páros" string lesz a kifejezésünk, ez a legutóbbi lesz az értéke.
A típusról: az if(B) E else F kifejezés típusa az E és F kifejezések típusának "legspecifikusabb közös őse" lesz - erre még visszatérünk később, mindenesetre ha E és F típusa ugyanaz (mint most, mindkettő String), akkor ez a közös típus lesz az if-else kifejezés típusa is. Mivel pedig emiatt a fordító ki tudja következteni, hogy String a típusa a függvényünk törzsének, ezért nem kell megadnunk, hogy a függvény visszatérési értékének a típusa String lesz.
Amikor a fordító biztos, hogy nem tudja kikövetkeztetni a típust: ha rekurzív függvényt írunk (ami saját magát hívja meg, remélhetőleg más argumentumokkal). Erre a klasszikus példa a faktoriálist számító függvény szokott lenni, amit Scalában pl. így is implementálhatunk:
def fakt(n: Int ): Int = {
if (n <= 1) 1 else n * fakt(n - 1)
}
Lássuk, mi történik, ha pl. kiértékeljük a fakt(5) kifejezést. Mostantól használni fogjuk a ▹ jelet is, amikor példa kiértékelünk: E▹F jelenti azt, hogy az E kifejezést egy lépésben az F kifejezésre írjuk át. Tehát a fakt(5) kifejezés kiértékelése:
fakt(5)▹if(5≤1) 1 else 5⋅fakt(5−1)▹if(false) 1 else 5⋅fakt(5−1)▹5⋅fakt(5−1)▹5⋅fakt(4)▹5⋅(if(4≤1) 1 else 4⋅fakt(4−1))▹5⋅(if(false) 1 else 4⋅fakt(4−1))▹5⋅(4⋅fakt(4−1))▹5⋅(4⋅fakt(3))▹5⋅(4⋅if(3≤1) 1 else 3⋅fakt(3−1))▹5⋅(4⋅if(false) 1 else 3⋅fakt(3−1))▹5⋅(4⋅(3⋅fakt(3−1)))▹5⋅(4⋅(3⋅fakt(2)))▹5⋅(4⋅(3⋅if(2≤1) 1 else 2⋅fakt(2−1)))▹5⋅(4⋅(3⋅if(false) 1 else 2⋅fakt(2−1)))▹5⋅(4⋅(3⋅(2⋅fakt(2−1))))▹5⋅(4⋅(3⋅(2⋅fakt(1))))▹5⋅(4⋅(3⋅(2⋅if(1≤1) 1 else 1⋅fakt(1−1))))▹5⋅(4⋅(3⋅(2⋅if(true) 1 else 1⋅fakt(1−1))))▹5⋅(4⋅(3⋅(2⋅1)))▹5⋅(4⋅(3⋅2))▹5⋅(4⋅6)▹5⋅24▹120
és így kapjuk meg végül, hogy 5! az bizony 120.
Amire mindenképp fel kell figyelnünk ennél a kiértékelésnél: a kifejezés hossza egyre csak növekszik, az utolsó rekurzív hívásnál, ha fakt(n)-t hívunk, egy n-nel arányos méretű kifejezéssel dolgozunk. Valójában persze a Scala ugyanúgy, mint a C vagy a Java, a függvényhívásokkor a "hívási verembe" (call stack) pusholja le az argumentumokat, és hogy hova kell majd visszaugrani a vezérlésnek, aztán átugrik a meghívott függvény belépési pontjára. Ha ott lesz még egy rekurzív hívás, további argumentumokat pushol a call stackre, majd még továbbiakat, így egy n mélységű rekurziónál a call stack is n-nel arányos ütemben telik be - és ez azért rossz hír, mert a call stack alapból elég kicsi, néhány tízezernél nem fog tudni mélyebb hívást managelni, és ekkor dob egy StackOverflowErrort, ami miatt a JVM kilövi a programunk futását.
Ezzel valamit kezdenünk kell, mert a funkcionális programozás egyik alap építőköve az, hogy ciklusok helyett rekurzióban gondolkodunk (azt lehet immutable értékek mellett is, a ciklushoz meg well, kellene ciklusváltozó). Szerencsére van a rekurziónak egy olyan fajtája, amit "farokrekurziónak" (tail recursion) hívnak és ha sikerül így írjunk programot, akkor attól nem telik a call stack. Ez lesz a következő poszt témája.
folytköv
No comments:
Post a Comment