Saturday, February 15, 2020

05 - Függvények és a Unit típus

Azért is hívják a Scalát funkcionális programozási nyelvnek (is - meg OOP is), mert a függvények "first-class citizenek" a nyelvben: igaz, hogy a legtöbb imperatív nyelvben is van rá mód, hogy függvényeket adjunk oda egy másik függvénynek argumentumként, de Scalában ez nyelvi szinten úgy támogatott, hogy nagyon egyszerű a szintaxisa.

Láttuk eddig, hogy az $\mathrm{Int}$, $\mathrm{Boolean}$, $\mathrm{String}$ stb típusok léteznek Scalában. Ha pedig $\sigma$ és $\tau$ már típusok, akkor $\sigma=>\tau$ az olyan függvények típusa, melyek $\sigma$ típusú inputot várnak és $\tau$ típusú az outputjuk. Pl. egy $\mathrm{def}~ \mathrm{strlen}(~s:\mathrm{String}):\mathrm{Int}=\ldots$ szignatúrájú függvény típusa $\mathrm{String}=>\mathrm{Int}$ (a továbbiakban a szövegben összeolvasztom nyíllá ezt a két karaktert: $\mathrm{String}\Rightarrow\mathrm{Int}$). És ilyen típusú értékeket is létrehozhatunk, vagy átadhatjuk paraméterként:


def pluszHat(n: Int) = n + 6
println(pluszHat(4)) // prints 10

val f: Int => Int = pluszHat
println(f(4)) //also prints 10

val g = pluszHat _ //inferred type
println(g(4)) //also prints 10

val h = { x: Int => x + 6 }
println(h(4)) //also prints 10

val i: Int => Int = { x => x + 6 }
println(i(4)) //also prints 10

val j: Int => Int = { _ + 6 }
println(j(4)) //also prints 10


Amit fent látunk: van egy függvényünk, az "adj hozzá hatot az inputhoz" függvény, $\mathrm{pluszHat}$ a neve, nyilván $\mathrm{Int}=>\mathrm{Int}$ típusú.
  • Az $f$ értéket úgy hoztuk létre, hogy explicit deklaráltuk a típusát, majd megmondtuk, hogy ez is a $\mathrm{pluszHat}$ függvény legyen. Innentől kezdve őt is teljes jogú függvényként kezelhetjük, pl. kiértékelhetjük egy int helyen, mondjuk ezt is $4$-re, és az érték $10$ lesz, hiszen ez a függvény lényegében egy aliasa a $\mathrm{pluszHat}$ függvényünknek.
  • Ötletként felmerülhet, hogy vajon a fordító ki tudja-e következtetni a típusát, ha nem adjuk meg. A $\textrm{val}~g=\mathrm{pluszHat}$ értékadás fordítási hibát dob, a fordító nem jön rá, hogy most a $\mathrm{pluszHat}$ra mint függvény értékként akarunk hivatkozni, nem pedig meghívni valami argumentummal, ezt hiányolja. (Az előbb a környezetből rájött arra, hogy $\mathrm{Int}\Rightarrow\mathrm{Int}$ értékként akarjuk kezelni.) Ezt a speciális wildcard $\_$ karakterrel oldhatjuk fel: ha $f$ egy $\sigma\Rightarrow\tau$ típusú függvény neve, akkor $f~\_$ mint kifejezés magát az $f$ függvényt jelöli és $\sigma\Rightarrow\tau$ típusú. Ezért a $\mathrm{val}~g=\mathrm{pluszHat}~\_$ értékdeklarációnál már ki tudja következtetni a fordító, hogy akkor a $g$-nek is $\mathrm{Int}\Rightarrow\mathrm{Int}$ típusú értéknek kell lennie.
  • A további három érték deklarálásánál arra látunk példát, hogy anonim, avagy lambda függvényt pl. hogy adhatunk meg. Mindháromban közös, hogy $\{\}$ zárójeleken belül adjuk meg a függvényt.
    • A $h$ érték esetében: explicit típusdeklarációval megadtuk, hogy van egy $x$ formális paramétere a függvénynek, ami $\mathrm{Int}$ típusú, az értéke pedig az $x+6$ kifejezés értéke lesz. (Ugyanaz, mintha $\mathrm{def}~\mathrm{sumthin}(x:\mathrm{Int})=x+6$ deklaráltuk volna a függvényt, azzal, hogy nem kell ezt korábban tegyük, sem nevet kitalálnunk neki. Itt egyértelmű, hogy a kimenet típusa is $\mathrm{Int}$ lesz: ha ismert típusú az input, akkor az output típusa kiszámítható, kivéve, ha a függvény rekurzív - de anonim függvény nem lesz rekurzív, hiszen nincs neve, amivel hívni tudná magát.
    • Az $i$ érték esetében: az értéknek deklaráltuk explicit, hogy $\mathrm{Int}\Rightarrow\mathrm{Int}$ típusú legyen, a kapcsoson belül, magában a lambda függvényben nem. De nem baj: a fordító rájön, hogy ha az $i$-nek egy $\mathrm{Int}\Rightarrow\mathrm{Int}$ típusú függvény az értéke, akkor ott a kapcsosban az az $x$ csak $\mathrm{Int}$ lehet. Eztán még leellenőrzi, hogy ekkor a kapcsoson belül deklarált függvény kiértékeléskor tényleg $\mathrm{Int}$et ad-e vissza és ha igen (most igen), akkor rendben, egyébként fordítási hibát kapunk.
    • A harmadik esetben a $\_$ wildcarddal, $\Rightarrow$ nélkül deklaráljuk ugyanezt a függvényt. Ha így deklarálunk egy függvényt, az azt jelenti, hogy annyi paraméteres a függvényünk, ahányszor előfordul benne a $\_$ jel, az első paraméter az első $\_$ helyére, a második a másodikéra stb. helyettesítődik be. Most csak egy van, tehát ez egy egyváltozós függvény; mivel $j$ típusa explicit deklarált, ebből kijön, hogy a paraméter $\mathrm{Int}$ kell legyen.
 Persze többváltozós függvényeket is tudunk kezelni ilyen módon: ha például a függvényünk maga az összeadás, ugyenezek a módik a következőképp néznek ki:


def plusz(x: Int, y: Int) = x + y
println(plusz(4,6)) // prints 10

val f : (Int,Int) => Int = plusz
println(f(4,6)) //also prints 10

val g = plusz _ //inferred type. csak egy wildcard kell!
println(g(4,6)) //also prints 10

val h = { (x: Int, y: Int) => x + y }
println(h(4,6)) //also prints 10

val i: (Int,Int) => Int = { (x,y) => x + y }
println(i(4,6)) //also prints 10

val j: (Int,Int) => Int = { _ + _ }
println(j(4,6)) //also prints 10

Tehát a függvény formális paraméterlistájának a típus-sorozatát kerek zárójelekbe kell tenni. Erre még visszatérünk, ez a típusok "szorzata" címen fog később futni. Egy pontra érdemes figyelni: a $g$ megadásakor csak egy wildcardra van szükség, ez kell a fordítónak ahhoz, hogy értse, most a függvényről mint $(\mathrm{Int},\mathrm{Int})\Rightarrow\mathrm{Int}$ értékről akarunk beszélni és nem meghívni akarjuk.

Scalában minden kifejezés, nincs megkülönböztetve "eljárás" és "függvény". Ami egy "eljárás"hoz legközelebb áll, az egy $\mathrm{Unit}$ típusú kifejezés. A $\mathrm{Unit}$ egy elég kis értéktartományú típus, eddig a $\mathrm{Boolean}$ volt a rekorder a kételemű értéktartományával (lehet $\mathrm{true}$ vagy $\mathrm{false}$), a $\mathrm{Unit}$ ennél kisebb: egyetlen $\mathrm{Unit}$ típusú érték van, a $()$ (üres zárójelpár). A $\mathrm{println}$ függvény például $\mathrm{String}\Rightarrow\mathrm{Unit}$ típusú: kap egy $\mathrm{String}$et és garantáltan a $()$ értékre értékelődik ki.
Persze ha egy kifejezés típusa $\mathrm{Unit}$, akkor az értékét már ezek szerint kiértékelés nélkül is tudjuk (feltéve, hogy terminál a kiértékelés és nem esik végtelen ciklusba) - egy $\mathrm{Unit}$ típusú kifejezés kiértékelése vélhetően azért történik, mert van mellékhatása, amit szeretnénk. Ilyen pl. a $\mathrm{println}$ függvény részéről a konzolra kiírás.

Függvényt paraméterként is várhatunk, ezek után talán nem meglepő módon:

  def kiertekelNullaban(f: Int => Int) = f(0)

  println(kiertekelNullaban({ _ + 5 })) //prints 5

  def egymasUtan(f: Int => Int, g: Int => Int): Int => Int = {
    x => g(f(x))
  }

  val megegy_szorketto = egymasUtan({ _+1 }, { _*2 })
  println(megegy_szorketto(3)) //prints (3+1)*2 = 8

  def plusz(x: Int ): Int => Int = {
    y => x + y
  }
  //plusz kap egy x-et és visszaad egy függvényt,
  //ami ha aztán kap egy y-t, akkor visszaadja x+y-t

  val pluszHat = plusz(6)
  println(pluszHat(4)) //prints 10
az első hívásnál  kapunk egy $\mathrm{Int}\Rightarrow\mathrm{Int}$ függvényt, és behelyettesítjük a nullát, visszaadjuk az értéket; a második esetben kapunk két $\mathrm{Int}\Rightarrow\mathrm{Int}$ függvényt, $f$-et és $g$-t és visszaadunk egy harmadik függvényt, a kettő kompozícióját: ami egy input $x$-re visszaadja $g(f(x))$-et, azaz először alkalmazza rajta $f$-et, majd az eredményen $g$-t és ezt az értéket adja vissza. A harmadik esetben pedig egy $x$ számot kapunk, és visszaadjuk azt a függvényt, ami ha megkap egy $y$ számot, akkor adja vissza az $x+y$ értéket. Itt talán érdemes megfigyelni, hogy ez a kétváltozós összeadás függvény "szétbontva" két egyváltozós függvénnyé, vagy mondhatjuk úgy, hogy egy $(\mathrm{Int},\mathrm{Int})\Rightarrow\mathrm{Int}$ függvényt írtunk át $\mathrm{Int}\Rightarrow(\mathrm{Int}\Rightarrow\mathrm{Int})$ alakba. Ezt a műveletet (amit persze akárhány paraméteres függvényre el lehet végezni, végeredményben csupa egyparaméteres függvényt kapva) curryingnek nevezik (és Brooke Haskell Curry után nevezték el így).

No comments:

Post a Comment