13 - match kifejezések

Volt az inteket tároló lista adatszerkezetünk:

1
2
3
trait Lista
case class UresLista() extends Lista
case class NemuresLista(head: Int, tail: Lista) extends Lista

Most erre fogunk feldolgozó kódot írni. Funkcionális programozásban nagyon gyakori az algebrai adattípusok mintaillesztéssel való feldolgozása, amire Scalában a match kulcsszó való. Nézzünk példa kódot, ami kiszámolja a lista elemeinek az összegét:

1
2
3
4
5
def sum(list: Lista): Int = list match {
  case UresLista() => 0
  case NemuresLista(h, t) => h + sum(t)
}
println(sum(NemuresLista(1, NemuresLista(4, NemuresLista(2, UresLista())))) ) //prints 7

Mit tanulunk ebből a kódból?

  • Egy értékre való mintaillesztést a match kulcsszó vezet be (érték match alakban)
  • Ezután kapcsos zárójelen belül jönnek az esetek
  • Fentről lefelé case mintaX => kifejezésX alakban: ha az illesztett érték illik a mintaX mintára, akkor az egész match kifejezés értéke kifejezésX értéke lesz, különben illeszti a következőt, amíg nem találja meg az első illeszkedést.
  • Hogy nézhet ki egy minta?
    • lehet egy azonosító. Ez mindenre illeszkedik, rögzíti (bindeli) az azonosítóhoz az illesztett értéket, és a => jobb oldalán ezt az értéket helyettesítjük be az azonosító helyére.
    • lehet egy érték. Ez akkor illeszkedik, ha egyenlő az illesztett értékkel.
    • lehet egy C(P1,...,Pn) alakú kifejezés, ahol C egy case class neve, melynek n mezője van. Ez akkor illeszkedik, ha a bejövő érték szintén ennek a C case classnak egy példánya, az első mezője illeszkedik a P1, a második a P2,..., az n-edik (utolsó) mezője a Pn mintára. Ha minden illeszkedik és az al-mintákban volt azonosító bindelés, akkor ezek mind átmigrálnak a jobb oldalra.

Amit a fentebbi kódban láttunk, az az első és a harmadik eset kombinációjaként áll elő. Például, ha a sum függvény megkapja a list=NemuresLista(3,NemuresLista(2,UresLista())) értéket, akkor mi történik:

  • az első case nem illeszkedik, hiszen az egy UresLista case class példányra illeszkedne, list pedig egy NemuresLista
  • a második case addig biztosan illeszkedik, hogy NemuresLista a minta is, meg az érték is, amire illesztjük. Ekkor tehát a minta akkor illeszkedik, ha stimmelnek a mezők is: azaz, ha a h (azonosító) illeszkedik a 3, a t (azonosító) pedig a NemuresLista(2,UresLista()) értékre. Ezek illeszkednek, hiszen változók, tehát a match kifejezés értékét úgy kapjuk, hogy a h+sum(t) kifejezésben h helyébe a 3, t helyébe pedig a NemuresLista(2,UresLista()) értéket helyettesítjük és ezt a kapott kifejezést kiértékeljük, tehát: 3+sum(NemuresLista(2,UresLista())) értéke (amit aztán a subst modell szerint tovább értékelünk, kifejtjük a sum függvényt).

Visszanézve az összeg típus példájára, ott a ,,ha kör, írd ki az adatait, ha háromszög, akkor is'' match kifejezés is ilyen mintákból építkezett.

Vagy nézzük ezt a kicsit bonyolultabb kódot:

1
2
3
4
5
6
7
8
9
def vesszok(list: Lista) = {
  def vesszokR(list: Lista): String = list match {
    case UresLista() => ""
    case NemuresLista(head, UresLista()) => head.toString
    case NemuresLista(head, tail) => head + "," + vesszokR(tail)
  }
  "(" + vesszokR(list) + ")"
}
println(vesszok(NemuresLista(3, NemuresLista(7, UresLista()))) ) // prints (3,7)

Ez ha kap egy listát, akkor kiírja kerek zárójelek közt (ezután jön a rekurzív hívás) vesszőkkel elválasztva az elemeit. Itt arra kell figyelni, hogy ha a lista üres, akkor ne írjunk ki semmit, ha egyelemű (vagyis: olyan nemüres lista, melynek a farka üres), akkor csak azt az egy elemet, egyébként pedig a fejét, vessző, rekurzió a farkára.

Még néhány minta-formátum:

  • A _ (underscore) minta mindenre illeszkedik (ugyanúgy, mint egy azonosító), de nem bindeli az értéket változóhoz. Ezt praktice akkor használjuk, ha a jobb oldali kifejezésben az itt szereplő értéket nem használnánk semmire.
  • Ha az eddigi minták után kettősponttal teszünk egy típust, tehát P:T formátumú lesz a mintánk, ahol T egy típus, akkor ez akkor illeszkedik, ha P illeszkedik és az érték (futásidejű) típusa T
  • Ha a mintánk után (lehet benne típusmegjelölés is) egy if B alakú ún. if guardot teszünk, ahol B egy Boolean típusú kifejezés, akkor ez a minta akkor illeszkedik, ha maga a minta illeszkedik és B értéke igaz.

Kérdések, feladatok

  • Gondoljuk végig, hogy a következő két mintaillesztő kód miért is azt valósítja meg, amit állítunk róla!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def length(list: Lista): Int = list match { //visszaadja a list elemszámát
  case UresLista() => 0
  case NemuresLista(_, tail) => 1 + length(tail)
}

def countOdd(list: Lista) : Int = list match { //visszaadja a páratlan elemek számát
  case UresLista() => 0
  case NemuresLista(h, t) if ( h%2 == 0 ) => countOdd(t)
  case NemuresLista(_, t) => 1 + countOdd(t)
}
  • Írjuk meg a fenti mintaillesztős függvényeket tail rekurzívan.

Utolsó frissítés: 2021-02-07 23:06:47