A Scala programozási nyelv

Mintaillesztés

Case osztályok

Mintaillesztést az ún. case osztályok példányain végezhetünk. Ilymódon a funkcionális programozási paradigmából ismert algebrai adatstruktúrákat építhetünk és dolgozhatunk fel kevés kód írásával.
Egy egyszerű példa case osztályra:

abstract class Expr case class Number(n: Int) extends Expr case class Sum(e1: Expr, e2: Expr) extends Expr

A mintaillesztés lehetősége (amelyre később lesz példa) mellett további speciális tulajdonságai vannak egy case osztálynak:

Mintaillesztés

Mintaillesztést a match kifejezéssel tudunk használni, amelynek a szintaxisa nagyban hasonlít a C stílusú nyelvek switch-case utasítására. Valójában a match a switch-case utasításnak az osztályhierarchiákra vett általánosítása. Ebből is látszik, hogy a Scala hogyan képes utánozni az egyes funkcionális nyelvi elemeket objektum orientált eszközökkel.

def eval(e: Expr): Int = e match { case Number(x) => x case Sum(l, r) => eval(l) + eval(r) }

Itt a Number(x) és a Sum(l,r) a minták, amiket az e szelektorértékre illesztünk. Number(x) felveszi az összes Number objektum értékét és az x mintaváltozónak értékül adja az adott Numberben tárolt n egész számot. Összességében a működése nagyon jól követi a Haskellben ill. bármely más funkcionális nyelvben használatos mintaillesztését, hiszen itt is fentről lefelé próbálja végig a mintákat egy adott értékre amíg nem talál illeszkedést. Ellenkező esetben dob egy MatchError kivételt.
A következőkből lehet mintákat építeni:


A mintaváltozók kisbetűvel kell kezdődjenek, és egy adott mintában csak egyszer fordulhatnak elő.

Lambda kifejezésekben is használhatunk mintaillesztést, sőt egy match kifejezés önmagában is egy névtelen függvény:

{ case P 1 => E 1 ... case P n => E n }

Ez átírható ekképp:

(x => x match { case P 1 => E 1 ... case P n => E n })

A mintaillesztés első ránézésre olyan, mintha Java-ban switch/case szerkezetet használnánk. A switch/case azonban csak primitív típusokra működik. Gyakran felmerül azonban az igény, hogy például String típusra használjuk. A Scala-ba nem került bele a switch/case helyette azonban jelen van a sokkal erősebb mintaillesztés.

def checkPrime(number:Int):Boolean = { number match { case 1 => return true case 2 => return true case 3 => return true case 5 => return true case 7 => return true case _ => return false } }

Összetettebb példa

Tekintsük azokat a bináris fákat, amelyek levelei egész számokat tartalmaznak. Legyen egy osztályunk a fáknak, és egy-egy alosztályunk a leveleknek és a csomópontoknak:

abstract class Tree; case class Branch(left: Tree, right: Tree) extends Tree; case class Leaf(x: Int) extends Tree;

A Tree osztálynak nincs extends klóza és törzse sem. Ez a következővel ekvivalens:

class Tree extends Object with {}

A Tree mindkét leszármazottjának van case módosítója. Ennek két hatása van. Egyrészt a new kulcsszó elhagyásával hozhatunk létre objektumokat:

val tree1 = Branch(Branch(Leaf(1), Leaf(2)), Branch(Leaf(3),Leaf(4)))

Másrészt a konstruktorok mintaillesztésre használhatóak:

def sumLeaves(t: Tree): Int = t match { case Branch(l, r) => sumLeaves(l) + sumLeaves(r) case Leaf(x) => x }

A sumLeaves metódus összeadja a levelekben szereplő számokat. A match metódus az Object osztályban van definiálva. Paraméterként egy választó kifejezést vár. A case ágakban a változókhoz hozzárendelés is történik. A minta változók mindig kis betűvel, a konstruktorok pedig nagy betűvel kell kezdődjenek.
A választó kifejezés hatása a következő. Az első olyan alternatíva, melynek mintája megfelel a kiválasztó értéknek, törzse lesz kiértékelve, a mintaváltozók rögzítésre kerülnek.

val l = Branch(Leaf(1), Leaf(2)), r = Branch(Leaf(3),Leaf(4))