A Scala programozási nyelv

Példaprogramok

Minden objektum

Ahogy már a nyelv részletes leírásánál említettük, Scalaban minden objektum. Nézzünk egy ezt bemutató példát:

object UnifiedTypes { def main(args: Array[String]) { val set = new scala.collection.mutable.HashSet[Any] set += "This is a string" // add a string set += 732 // add a number set += 'c' // add a character set += true // add a boolean value set += main _ // add the main function val iter: Iterator[Any] = set.elements while (iter.hasNext) { println(iter.next.toString()) } } }

Minden osztály ősosztálya a nyelvben a scala.Any osztály, két közvetlen leszármazottal: a scala.AnyVal -lal, illetve a scala.AnyRef -fel; az elöbbi az értékosztályokat, míg az utóbbi a referenciaosztályokat reprezentálja. Az osztályhierarchia alján a scala.Nothing áll. A Javas java.lang.Object megfelelője itt a scala.AnyRef.

A program működése egyszerű: létrehoz egy olyan generikus halmazt, aminek a típusparamétere Any, azaz bármit tartalmazhat. Ezek után hozzáadjuk a különböző típusoknak megfelelő értékeket illetve változókat, majd végigmenve a halmazok elemeink, kiiratjuk azok toString() metódusát.

A program kimenete a következő:

c true <function> 732 This is a string

Operátorok definiálása

Ahogy a nyelv részletes leírásában már említettük, a nyelv nem definiál kifejezett operátorokat, tetszőleges literál betöltheti ezt a szerepet (ésszerű keretek között). Nézzünk egy példát, amelyben megvalósíjuk a komplex számok kezelését, majd az így bevezetett operátorokat gond nélkül felhasználjuk:

object Complex extends App { class Complex(val re: Double, val im: Double) { def + (that: Complex) = new Complex(re + that.re, im + that.im) def - (that: Complex) = new Complex(re - that.re, im - that.im) def * (that: Complex) = new Complex(re * that.re - im * that.im, re * that.im + im * that.re) def / (that: Complex) = { val denom = that.re * that.re + that.im * that.im new Complex((re * that.re + im * that.im) / denom, (im * that.re - re * that.im) / denom) } override def toString = re + (if (im < 0) "-" + (-im) else "+" + im) + "*i" } val x = new Complex(2, 1); val y = new Complex(1, 3) println((x + y)*y); }

Definiáljuk az osztályt, és a rajta végezhető műveleteket, felülírjuk a toString metódust, majd kiszámoljuk egy egyszerű kifejezés értékét.

Mintaillesztés

A Scalaban beépített mintaillesztési lehetőség található, amely szinte bármilyen adatra alkalmazható; a feldolgozás a legelső mintaegyezésig történik meg.

object MatchTest2 extends App { def matchTest(x: Any): Any = x match { case 1 => "one" case "two" => 2 case y: Int => "scala.Int" } println(matchTest("two")) }

A fenti példában a matchTest() egy olyan metódus, ami a paramétert elösször összeveti az 1 -el, mint egész számmal, és ha egyezik, a "one" karakterlánccal tér vissza. Amennyiben a paraméter a "two" karakterlánc, úgy viszont a visszatérési érték a 2 egész szám. A harmadik illesztés úgynevezett típusos illesztés, ami tetszőleges Int típusú paraméterre illeszkedik, és ez esetben a "scala.Int" karakterlánccal tér vissza.

Beépített kollekciók párhuzamos használata

A Scala 2.9 -es kiadásának egyik nagy ujdonsága volt, hogy az alapnyelv részévé tette a másik fejezetben már tárgyalt párhuzamos feldolgozást a beépített tárolókra. Nézzünk egy példát!

object PCollDemo extends App { def isPrime(number: Int): Boolean = { for (i <- 2 until number) if (number % i == 0) return false; return true; } def numOfPrimes(limit: Int): Int = { var res: Int = 0; for (i <- 2 until limit) if (isPrime(i)) res = res + 1; return res; } var limits = Array(10000, 25000, 50000, 75000, 100000, 150000, 200000) limits.map(n => (n,numOfPrimes(n))).foreach(println) }

A fenti példában az isPrime() egy olyan metódus, ami eldönti egy számról, hogy prim-e. Azért választottuk ezt a naív implementációt, hogy be tudjuk mutatni a szekvenciális és párhuzamos végrehajtásból adódó különbségeket. A numOfPrimes() metódus megmondja, hogy egy adott felső határ alatt hány darab prímszám található. Ha a fenti programot lefuttatjuk, a futásidő egy 24 magos Intel Xeon E7530 processzoros szerveren nagyjából 20 másodperc (5 futás átlagolt értéke). Annak, hogy közvetlen a nyelvbe építették be a párhuzamos feldolgozást, egy következménye, hogy nagyon kényelmes használni. Ha a legutolsó sort lecseréljük erre a sorra:

limits.par.map(n => (n,numOfPrimes(n))).foreach(println)

A program máris párhuzamosan fogja kiértékelni a map metódust (ugyanez igaz az összes, eddig szekvenciális adatfeldolgozást használó metódusra. További információk erről a bejelentésben. Az így átalakított kód futásideje jóval jobb, 10 másodperc, ami 100%os sebességnövelést jelent.

Case osztályok

Scalaban lehetőség van úgynevezett case osztályok létrehozására. Ezen osztályok explicit léteznek, nem kell őket a new operátorral létrehozni. A többi különbség a normális osztályokhoz képest itt olvasható. Ezeknek az osztályoknak a legfőbb vonzereje az, hogy mintailleszthetünk rajtuk.

Nézzünk meg egy konkrét példát, amiben egy kitalált játéknyelv szintaxisfáját akarjuk feldolgozni (és ehez általánosított algebrai típusokat használunk):

object GADTS extends App { /* A játéknyelvünk szintaxisfája */ abstract class Term[T] /* Integer-literál */ case class Lit(x: Int) extends Term[Int] /* Egy szám rákövetkezője */ case class Succ(t: Term[Int]) extends Term[Int] /* Nulla-e t? */ case class IsZero(t: Term[Int]) extends Term[Boolean] /* Az if kifejezés */ case class If[T](c: Term[Boolean], t1: Term[T], t2: Term[T]) extends Term[T] /** Egy típushelyes kiértékelő függvény. A jobboldali kifejezés kihasználhatja, hogy * T milyen konkrét nyelvi elem, mintaillesztés segítségével. */ def eval[T](t: Term[T]): T = t match { case Lit(n) => n // itt kihasználja a jobboldali kifejezés, hogy T = Int, igy // használható a + operátor case Succ(u) => eval(u) + 1 case IsZero(u) => eval(u) == 0 case If(c, u1, u2) => eval(if (eval(c)) u1 else u2) } println(eval(If(IsZero(Lit(1)), Lit(41), Succ(Lit(41))))) }

Az alap term a fenti példa alapján lehet integer-literál, egy szam rákövetkezője, egy if kifejezés, illetve egy ellenőrzés, hogy egy term nulla-e. Az eval metódus mintaillesztés felhasználásával ki tudja értékelni a kapott termet. A program futtatásakor az eredmény 42, ugyanis az 1 integer-literál nem nulla, így a Succ(Lit(41)) lesz a kiértékelt érték.

Quicksort

Első példaként lássuk a gyorsrendezést Scala-ban:

def sort(a: Array[Int]): Unit = { def swap(i: Int, j: Int): Unit = { val t = a(i); a(i) = a(j); a(j) = t } def sort1(l: Int, r: Int): Unit = { val pivot = a((l + r) / 2) var i = l var j = r while (i <= j) { while (a(i) < pivot) { i = i + 1 } while (a(j) > pivot) { j = j - 1 } if (i <= j) { swap(i, j) i = i + 1 j = j - 1 } } if (l < j) sort1(l, j) if (j < r) sort1(i, r) } if (a.length > 0) sort1(0, a.length - 1) }

Ránézésre ez nagyon hasonlít a Java-ban vagy C-ben írt rendezésre. Ugyanazokat az operátorokat és hasonló struktúrákat használunk, azonban van néhány fontos különbség:

Mint látható, Scala-ban lehetőség van "egyszerű" imperatív vagy objektum-orientált programok írására. Azonban lehet funkcionális stílusban is programokat írni:

def sort(a: Array[Int]): Array[Int] = { val pivot = a(a.length / 2); sort(a.filter(x => x < pivot)) append a.filter(x => x == pivot); append sort(a.filter(x => x > pivot)) }

Ez a megoldás sokkal jobban tükrözi a gyorsrendezés alapötletét:

  1. Válasszunk egy elemet a tömb közepéről.
  2. Osszuk szét a tömböt három résztömbbe. Az elsőben a választott elemnél kisebb, a másodikban annál nagyobb, a harmadikban a vele egyenlő elemek legyenek.
  3. Rendezzük az első két tömböt rekurzív módon.
  4. Az eredményt a résztömbök összefűzésével kapjuk.

Mint látható az imperatív implementáció az argumentumként kapott tömbön dolgozik, a funkcionális változat egy teljesen új tömböt állít elő.
A fenti példában az Array[t] osztály metódusait használtuk (filter, append, sort, ...).
Ebben az osztályban van például egy filter metódus, mely paraméterként egy predikációs függvényt kap, ami a tömb elemeihez logikai értékeket rendel. A filter alkalmazásának eredménye egy olyan tömb, mely azokat az elemeket tartalmazza az eredeti tömbből, melyekre a a függvény igazat ad. Vagyis egy Array[t] típusú objektum filter metódusának a szignatúrája:

def filter(p: (t)Boolean): Array[t]

Itt a (t)Boolean olyan függvénytípus, amely egy t típusú elemet kap és logikai értéket ad vissza. A filterhez hasonló olyan függvényeket, melyek egy másik függvényt kapnak paraméterül, vagy egy függvényt adnak vissza, magasabb-rendű függvényeknek nevezzük.
A gyorsrendezés esetében a filtert háromszor alkalmaztuk egy-egy névtelen függvény argumentummal. Az első argumentum azt a függvényt reprezentálja, ami a paraméterét a megfelelő összehasonlítás értékére képezi le. Az x típusát elhagytuk, mert a fordító képes arra, hogy meghatározza azt.
Természetesen a filter-t és a hasonló függvényeket névvel ellátott függvényekre is alkalmazhatjuk:

def sort(a: Array[Int]): Array[Int] = { val pivot = a(a.length / 2); def leqPivot(x: Int) = x <= pivot; def gtPivot(x: Int) = x > pivot; def eqPivot(x: Int) = x == pivot; sort(a filter leqPivot) append sort(a filter eqPivot) append sort(a filter gtPivot) }

Egy Array[t] típusú objektumnak van append metódusa is, mely egy másik tömböt kap paraméterként és visszaadja a két tömb összefűzését. Ennek a szignatúrája:

def append(that: Array[t]): Array[t]

Ezt mi a fenti példában, mint infix operátort alkalmaztuk. Valójában minden metódus operátorként is alkalmazható a Scala-ban. Egy E op E' bináris operátor megfelel egy E.op(E') metódushívásnak. Tehát a fenti esettel ekvivalens a következő:

sort(a.filter(leqPivot)) .append(sort(a.filter(eqPivot))) .append(sort(a.filter(gtPivot)))

A „hagyományos” operátorok is az appendhez hasonlóan kezelhetőek. Például az i + 1 kifejezés ekvivalens az i.+(1) metódushívással, ahol a + az x egész érték metódusa.
A vezérlési szerkezetek is előredefiniált függvények. Például a while:

def while (def p: Boolean)(def s: Unit): Unit = if(p) { s; while(p)(s) }

Ennek első paramétere egy teszt függvény, második paramétere pedig egy akció függvény. A while addig hívja az akció függvényt, amíg a teszt függvény igaz értéket ad vissza.

Aukciósház

Jöjjön egy példa, mely mutat egy olyan területet, ahol a Scala különösen jól alkalmazható. Tegyük fel, hogy egy elektronikus aukciót kell implementálnunk. Ehhez aktorokat használunk, vagyis olyan objektumokat, amelyek üzenetek fogadására képesek. Minden folyamatnak van egy levelesládája, melyben a bejövő üzenetek vannak (sor). A levelesládából nem csak sorban lehet kivenni a leveleket, hanem adott tulajdonság alapján is.
Visszatérve a példához legyen egy árverező folyamatunk, amely egy adott áruról információkat szolgáltat, fogadja az ajánlatokat a kliensektől és biztosítja a kommunikációt az eladó és a győztes ajánló között az aukció végén.
Először is határozzuk meg a lehetséges üzeneteket. Két alaposztályunk van aszerint, hogy az üzenet honnan hova megy. Az AuctionMessage üzenetek a kliensektől mennek az árverező felé, az AuctionReply üzenetek pedig vissza.

import scala.actors.Actor abstract class AuctionMessage case class Offer(bid:Int,client:Actor) extends AuctionMessage case class Inquire(client:Actor) extends AuctionMessage abstract class AuctionReply case class Status(asked:Int,expire:Date) extends AuctionReply case object BestOffer extends AuctionReply case class BeatenOffer(maxBid:Int) extends AuctionReply case class AuctionConcluded(seller:Actor,client:Actor) extends AuctionReply case object AuctionFailed extends AuctionReply case object AuctionOver extends AuctionReply

Az árverező folyamat egy lehetséges implementációja a következő:

class Auction(seller: Actor, minBid: Int, closing: Date) extends Actor { val timeToShutdown = 36000000 //msec val bidIncrement=10 def act() { var maxBid = minBid - bidIncrement var maxBidder: Actor = null var running = true while (running) { receiveWithin((closing.getTime()- new Date().getTime())) { case Offer(bid, client) => if (bid >= maxBid + bidIncrement) { if (maxBid >= minBid) maxBidder ! BeatenOffer(bid) maxBid = bid; maxBidder = client; client ! BestOffer } else { client ! BeatenOffer(maxBid) } case Inquire(client) => client ! Status(maxBid, closing) case TIMEOUT => if (maxBid >= minBid) { val reply = AuctionConcluded(seller, maxBidder) maxBidder ! reply; seller ! reply } else { seller ! AuctionFailed } receiveWithin(timeToShutdown) { case Offer(_, client) => client ! AuctionOver case TIMEOUT => running = false } } } } }

Az objektum létrehozásakor meg kell adni:

A folyamat működése a run metódusában van definiálva. Ez mindig kiválaszt egy üzenetet a receiveWithin metódussal, és arra reagál, egészen addig, míg a megadott idő le nem jár. Az utóbbi esetet a TIMEOUT üzenet jelzi. Terminálás előtt a folyamat még egy ideig fogadja az üzeneteket, és azokra válaszol. Néhány további magyarázat a programhoz:

A fentiekből úgy tűnhet, hogy a Scala-ban igen sok, az elosztott programozást támogató nyelvi konstrukció van. Valójában arról van szó, hogy az Actor osztály metódusait használtuk.

Supermarket

Ebben a példaprogramban egy aktorokkal megvalósított párhuzamos rendszert mutatunk be és lehetőség nyílik a Scala gyakorlatias szemléletű megismerésére.

Készítette: Sárkány András 2011
Fordítóprogram: Scala 2.9.0
Fejlesztőkörnyezet: Eclipse

A program szupermarketben vásároló és fizető vevőket (Customer) szimulál, ahol a legtöbb dolog párhuzamosan történik. A vevők kiválasztják azokat a termékeket amiket meg akarnak vásárolni, majd véletlenszerűen beállnak egy kassza előtti sorba, amikor sorra kerülnek a vásárolt áruk árát a pénztáros kikéri a nyílvántartásból, majd a vevő fizet és távozik.

Aktorok és osztályok

Három különböző típusú Aktorral és egy statikus Singleton objektummal oldjuk meg a feladatot.

class Customer(store : Store) extends Actor{... class Counter extends Actor {... class Line extends Actor {... object Supermarket extends Store{...

A Customer aktor testesíti meg a vásárlót, aki miután bevásárolt kiválaszt egy pénztárat (Counter) és beáll a neki megfelelő sor(Line) végére. Mivel mind Aktorok ezért üzenetekkel kommunikálnak, tehát például a Customer üzen a Line-nak, hogy ő most beáll a sor végére, a Counter pedig üzen a Line-nak ha jöhet oda egy ember mert épp távozott valaki. A Supermarket mint globális objektum adja a szimuláció környezetét, ő kezeli a nyílvántartást és hozzá tartoznak a pénztárak is

Supermarket

Nézzük először a Supermarket forráskódját.

trait Store { def buy() : Array[Item] def enter: Unit def leave : Unit }
A Supermarket örökli a Store trait-et ahol alapvető metódusok vannak deklarálva definíció nélkül

val random = new Random() var nextid = 0 var items = List[(Item,Price)]() private def assignNextId() : Int = { val id = nextid nextid += 1 id }
Mivel ez egy szimuláció a bolt raktára nincs előre elkészítve, hanem ahogy egy-egy bevásárlókocsi tartalma legenerálódik, úgy generálódnak új elemek a globális nyílvántartásba is. A nextid a soron következő új áru azonosítóját tartalmazza. Az items listában tárolódik tulajdonképpen a raktár Áru ás Ár rendezett párok formájában.

Az assignNextId() függvény visszaadja a soron következő azonosítót és elvégzi a léptetést is. Szépen látszik a különleges Scala szintaxis, ami egyszerre imperatív és funkcionális: ha több utasítást írunk egymás után akkor azok szekvenciaként hajtódnak végre és az utolsó lesz a blokk visszatérési értéke

var counters = List[Counter]() def initCounters(c : Int) = { for (i<-List.range(0,c)) counters ::= new Counter() } def startCounters=( for (c<-counters) c.start)

Ebben a kódrészletben az áruház alá tartozó pénztárak kezelését láthatjuk. Az initCounters függvény hatására létrejönnek a pénztár objektumok egy listában (counters), a startCounter függvény hatására pedig elindulnak a pénztárosok futó szálai

def buy = synchronized( { val quantity = random.nextInt(10) val a = new Array[Item](quantity) for (i<-List.range(0,quantity)){ val genitem = new Item(assignNextId) a.update(i,genitem) items ::= (genitem, Price(random.nextInt(100)+1)) } a })

Ez a buy metódus szimulálja a bevásárlókocsi megpakolását. Mindenki véletlenszámú árut vesz (0-10) és minden áru kap egy véletlenszerű árat (0-100). Az így generált áru aztán bekerül az "adatbázisba". Ez a függvény visszadja a kiválasztott áruk listáját, melyet a változatosság kedvéért tömbbel implementáltunk. A függvény visszatérési értéke az utolsó sorban található "a", vagyis a tömb maga

Üzenetek

Nézzük meg milyen üzenetváltásokra számítunk:
Ha egy Customer befejezte a válogatást választ véletlenszerűen egy sort és küld neki egy üzenetet, jelezve, hogy beállt a végére(StandInLine). A sor elvégzi ennek az akciónak a nyílvántartását. Ha egy kassza megüresedik, akkor küldd egy üzenetet a hozzá tartozó sornak, miszerint jöhet valaki(NextPlease). Ekkor a sor rögtön jelez a vásárló felé, hogy a kassza szabaddá vált (YouAreNext).
A kassza és a vásárló közti kommunikáció egy párbeszédként írható le: a vásárló jelzi hogy jelen van (ImHere), mire a kassza kéri a vásárolandó elemeket (GiveMeYourItems). A vásárló ezeket odaadja (Items), mire a kassza mond egy összeget (Price). A vásárló ezután kifizeti az összeget(Money) és távozik

//Customer messages case class ImHere case class Items( items : Array[Item]) case class Money(m : Int) //Counter messages case class GiveMeYourItems case class Price(p: Int) { val price = p} case class GoodBye //Line messages case class StandInLine case class NextPlease case class YouAreNext

Customer

A forráskód

val random = new Random() var items : Array[Item] = new Array[Item](0) store.enter def buy(): Unit = { items = store.buy() } def act(){ buy() val myCounter = Supermarket.getCounter myCounter.line ! StandInLine while (true) { receive { case YouAreNext => myCounter ! ImHere case GiveMeYourItems => myCounter ! Items(items) case Price(p) => myCounter ! Money(p) case GoodBye => { store.leave exit() } } } }

A vásárló saját bevásárlókosarának tartalmát az items tömbben tárolja. A vásárlás szimulálására meghívja a szupermarket buy függvényét. Az Aktor élete kezdetén szimulálja a vásárlást (feltölti a kosarat), kér egy véletlenszerű kasszát és beáll az ő sorába. Amikor sorra kerül lefolytatja a párbeszédet a kasszával és távozik

Counter

A forráskód

class Counter extends Actor { val line = new Line() line.start() def act() = { line ! NextPlease while (true) { receive { case ImHere => sender ! GiveMeYourItems case Items(items) => sender ! Price((for (i<-items) yield Supermarket.getPrice(i).price) sum) case Money(m) => { Supermarket.book(m) sender ! GoodBye Thread.sleep(200) line ! NextPlease } case Close => { line ! Close exit() } } } } }

A Counter létrehozza a saját sorát és mivel az is egy külön futó aktor, el is indítja. Ezután jelzi a sornak, hogy nincs nála vevő és vár a további üzenetekre. Amikor megkapja a vásárolt elemek listáját (Items(items)) kiszámolja a teljes vásárlás összegét. Felhívjuk a figyelmet, hogy a vásárlónál lévő áruk nem tárolják az árukat, ezt a központi raktárból kell lekérdezni.

sender ! Price((for (i<-items) yield Supermarket.getPrice(i).price) sum)

Ezt a sort elemezzük egy kicsit jobban. A belső for kifejezés minden i item-re lekéri az árát, és a yield segítségével ebből előáll egy új lista, ami az árakat tartalmazza már. Ennek az új listának a sum függvénye kerül meghívásra, és az összeg a Price konstruktorparamétere lesz, majd üzenetben távozik.

Line

Végül nézzük meg a várakozási sort megvalósító Line aktort

class Line extends Actor { var inline = List[Customer]() var nobodyatcounter = false def act() = { while (true) { receive { case StandInLine => { inline :+= sender.asInstanceOf[Customer] if (nobodyatcounter) this ! NextPlease } case NextPlease => inline match { case (h :: t) => { inline = t h ! YouAreNext nobodyatcounter = false } case Nil => { nobodyatcounter = true sender ! None } } case Close => exit() } } } }

A Line osztály tárolja a várakozó vásárlók listáját (inline), valamint egy külön logikai értéket arra az esetre, ha nincs senki a kasszánál. Erre azért van szükség mert ha akkor jelzi egy kassza, hogy üres amikor a várakozási sor is üres, akkor egy újonnan érkező vásárló magától nem tudná, hogy mehet a kasszához, a kassza meg nem jelezne újra.
Kétfajta üzenetet fogad az osztály: StandInLine és NextPlease.
A StandInLine-t biztosan vásárlótól kaptuk ezért cast-oljuk a küldő objektumot és betesszük a sor végére. Ha nincs senki a kasszánál saját magunknak küldünk egy üzenetet amivel kiváltjuk az újonnan érkezett vevő áthaladását a kasszához.

Ha NextPlease üzenet érkezik, akkor a kassza készen áll egy vásárló fogadására. Mintaillesztéssel leválasztjuk a lista fejét, és a (volt) fejelemnek elküldjük a YouAreNext üzenetet. Ha nem tudjuk a fej-farok mintaillesztéssel szétválasztani a listát akkor üres, ekkor beállítjuk a flag-et a megfelelő értékre.

Összefoglalás

A teljes forráskód letölthető itt. Ez a példa jól rámutat arra, hogy a Scala aktorainak üzenetküldése nagyon hasonlít az egyzserű függvényhíváshoz. Egyes tervezési döntések meghozatalakor (például a Line osztály esetében) végig érezhető, hogy egy másik implementációban, külön futási szál nélkül a Counter osztály részeként kezelhető lenne. A funkcionális paradigmával rengeteg kódrészlet rövidíthető le pársorosra és az immutable elemek használatával a programozás nagyobbrészt fordítási idejű és csak kisebbrészt futásidejű problémák megoldásává alakul.

Bankár

A Scala nyelv elosztott programok készítésére nyújtott magasszintű eszközeinek szemléltetésére egy kliens-szerver felépítésű programot válaszottam. A programban egy bankár szerverként fut, hálózaton elérhető, kliensektől hiteligyénylések érkeznek hozzá. A bankár bizonyos feltételek mentén eldönti, hogy elfogadja-e az igénylést, majd ennek megfelelően vagy elutasítja az igényt, vagy megkötik a hitelszerződést, majd rögtön meg is kezdi az ügyfélnek a folyósítást. A bankár adott cikluson ként kéri az ügyfeleit a törlesztő befizetésére, majd a futamidő leteltével felbontják a kapcsolatot.

Bankár – szerver

A bankár egy aktorral van megvalósítva. Egy bankár létrehozásához meg kell adnunk, milyen indulótőkével rendelkezik. Ez az, amiből tud majd az ügyfeleknek hitelt adni. Egy korlát meg van adva, hogy mekkora részét adhatja ki tőkéjének maximálisan. Ezt hiteligénylések elbírálásánál figyelembe fogja venni az aktuális tőkére. Ezenkívül meg kell adni, milyen porton akarjuk majd a bankár programot elindítani. A külvilág számára az adott hálózaton, ezen a porton lesz elérhető.

A bankár viselkedését az act metódusban a következőképp írhatjuk le.

def act() { alive(port) register('banker, self) var timer = new Timer(6000, notifyClients) timer.start() println("Bankar elindult") loop { receive { case Application(client, amount) => println("Igeny erkezett: " + client.uid + ", osszeg: " + amount) processRequest(client, amount) case Amortization(client) => println("Megkaptam a penzt! Toke: " + stock ) amortize(client) case Stop => exit() case msg => println(msg) sender ! msg } } }
Az aktort rögzítjük a megadott porton banker néven. Ezután elindítünk egy Timer-t, ami jelezni fogja, mikor kell az ügyfelektől a törlesztőrészletet kérni. Az üzeneteket feldolgozó ciklusban látható a tényleges kommunikáció lényegi része.

Üzenetek

Application

Egy hiteligény bejelentését jelző üzenet. Tartalmazza az igénylő ügyfelet, valamint az igényelt összeget.

Rejection

Az igénylés elutasítása esetén küldött üzenet.

Amortization

Törlesztés teljesítését jelző üzenet.

Notification

Törlesztésre figyelmeztető üzenet.

Credit

Hiteligénylés esetén a megadott hitel adatait tartalmazó üzenet. Leírja a futamidőt, kamatot, a felvett összeget, a lefoglalt tulajdont, a törlesztésből hátralevő időt, a törlesztőrészlet összegét, az ügyfél azonosítóját.

Client

Az ügyfél logikai működését megvalósító osztály. Tartalmazza adott összegű igénylés elindítását, törlesztőrészlet fizetését, az igényelt összeget, az egyedi ügyfélazonosítót, és elfogadott igénylés esetén a felvett hitelt.

ClientActor

A kliens kommunikációs részét megvalósító osztály, ami egy aktorba „csomagolt” Client objektumot jelent. A kommunikáció szintén az act metódusban van leírva. Induláskor meg kell adni a bankár elérhetőségét (hálózaton), amelyiktől hitelt akar igényelni, valamint meg kell adni az ügyfél adatait: fizetést, a vagyont, amire a hitelt fel akarja venni, életkorát.
Hitel igénylésekor történik a kliens aktor tényleges elindítása, amely első lépésként szintén figyelni kezd egy megadott porton, majd felveszi a kapcsolatot a bankár szerverrel és elküld neki egy üzenetet (Application) a hiteligénylésről. Elutasítás esetén abbahagyja működését, elfogadás esetén rögzíti magának a hitel adatait, a megjelölt tulajdona lefoglalásra kerül. A bankár által jelzett időben mindig fizeti az aktuális törlesztőt, a futamidő lejártával pedig befejezi működését.

Substance

A kliens által birtokolható vagyon absztrakt ősosztálya. Tartalmazza a tulajdonos klienst, van egy „reál-értéke”, meg van adva, hogy legfeljebb mekkora részét fedheti a hitel az értékének, mekkora kamata lesz az erre felvett hitelnek, maximálisan mekkora futamidejű hitelt lehet rá felvenni. Egy logikai érték jelzi, hogy le van-e már foglalva egy hitellel.

Lehetséges megvalósítások: ingatlan, arany, gyémánt, autó, értékpapír.

Hiteligénylés folyamata

Mikor egy ügyfél elküldi az igénylési üzenetét a bankárnak az feldolgozza a kérelmet. Ez a következő feltételek mentén történik: a bankár tőkéjének egy megadott százalékát nem haladhatja meg az igényelt összeg; fedezi-e az ügyfél tulajdonának értéke az összeget; nincs-e már lefoglalva a tulajdon. Ha ezek teljesülnek, kiszámtja a maximális futamidőt, a törlesztőrészletet és megvizsgálja, hogy ez nem haladja-e meg az ügyfél fizetésének harmadát. Ennek megfelelően vagy elutasítja a kérelmet, vagy megkezdi a folyósítást.

Forrás

A forrás itt található.

Topologikus rendezés

Készítette: Torma Balázs, 2012
Fordítóprogram: Scala 2.8.1
Fejlesztőkörnyezet: NetBeans 7.0

A Scala funkcionális nyelvi elemeit szándékoztam bemutatni egy mélységi keresés funkcionálisan megírt implementációjával. A main függvény az ami egyedül imperatívan van megírva.

Main

A main függvény pusztán az IO kezelésre és a mélységi keresés elindítására szolgál, illetve a mélységi bejárás által eredményezett mélységi számok szerint itt lesznek sorba rendezve a csúcsok.
A program egyetlen paramétert fogad el, amelynek soronként egy csúcsnév párt kell tartalmaznia. Például így:

egy ketto egy negy ketto harom harom negy

Az eredményt a standard outputra írja ki.

Graph

Ez az osztály tartalmazza a gráf reprezentációját miszerint a csúcsok szomszédjainak listáit egy vektorban tároljuk, egy másik vektorban pedig a csúcsok címkéit. Ami érdekes lehet az a külső konstruktor megoldása: Scalában a default konstruktort nem lehet akárhogy túlterhelni, ezért egy az osztállyal megegyező nevű singletonnal és annak apply() metódusával kellett egy külső konstruktort mímelni (bővebben ld. forrásban a kommenteket).

DepthFirst

A mélységi bejárás funkcionális implementációja, bőségesen ellátva kommentekkel. Értelemszerűen a ciklusokat ki kellett váltani rekurziókkal, így összesen három rekurzív függvény hívogatja egymást, ami nem túl szép, de működik.


A becsomagolt NetBeans projekt innen letölthető.

Amőba játék


Fordítóprogram: Scala 2.10.1
Fejlesztőkörnyezet: Eclipse Indigo (3.7)

Egy egyszerű, egy gépen, két játékossal játszható amőba játék.

Megjelenítés

A megjelenítésért és egyben a program futásáért az Application singleton objektum felelős. Ebben van egy MainFrame, amely az alkalmazás ablakát szolgáltatja. Ebben az ablakban egyetlek GUI elem van: a Panel. Erre vannak definiálva az eseménykezelők és a kirajzoló függvények.

Játékmenet

A játékmenet a Game osztályban van implementálva. Minden lépés után itt ellenőrizzük, hogy összejött-e az 5 egymás melletti mező egy játékosnak. Ha vége a játéknak, a konzolra kiírjuk, hogy melyik játékos nyert.
A becsomagolt Eclipse projekt innen letölthető.

Példaprogram adatbázis lekérdezésre

A következő példaprogramban adatbázis lekérdezéseket végzünk (az Oracle XE mintaadatbázis HR sémáját felhasználva), hogy szemléltessük, hogyan dolgoznak magasabb rendű vezérlő struktúrák, valamint példát látunk case osztályokra, amelyeken mintaillesztést végzünk. Első lépésben definiálunk egy using metódust mely két paramétert vár. Ezeknek típusai A és B. B típusú paraméternek nincs semmilyen megkötése, viszont A típus paraméter megszorítására a strukturális típusparaméter módszerét alkalmazzuk (duck typing). Ezzel a módszerrel megkövetelhetjük, hogy az első paraméter olyan típus legyen, ami megvalósít egy close() nevű eljárást. A második paraméter egy függvény, amely A típust leképezi B típusúra, mivel Scala-ban a try szerkezet visszaadja a blokkban található kifejezést, így esetünkben a függvényünk visszatérési értékét a try blokkban megkapjuk. A finally ág biztosítja, hogy param argumentum mindenkor lezárásra kerüljön.

def using[A <: {def close():Unit},B](param:A)(f:A => B): B = try { f(param) } finally { param.close() }

Nézzünk példát a metódus használatára:

val bfr = new BufferedReader(new InputStreamreader(System.in)) println(using(bfr {bfr => bfr.readLine})

A következő vezérlő utasításban készítünk egy ciklust, amely addig fut, amíg a test változónk igaz. A ciklusmag minden végrehajtásakor a metódus összegyűjti a kimenetre az átadott név értékét és bővít vele egy listát. Ehhez deklarálunk egy bmap (Boolean Map) nevű metódust. Ez a metódus két paramétert vár: egy logikai függvényt test néven, a második paraméter egy kódblokk, amely leképez egy T típusú objektumot és hozzáfűzi a ret-hez. A függvény végül visszaadja a listát.

def bmap[T](test: => Boolean)(block: => T): List[T] = { val ret = new ListBuffer[T] while(test) ret += block ret.toList } }

Ezek után definiáljuk a case osztályainkat, amelyek egy közös absztrakt őse van, az Employee. A case osztályok használata nagyban leegyszerűsíti a saját adattípusok (osztályok) írását, hiszen a fontosabb metódusokat és konstruktort implicit kapunk hozzájuk. A példában két típusra bontottuk a dolgozókat (vezetőség és dolgozók), hogy később mintaillesztést végezhessünk rajtuk.

abstract class Employee case class Executive(NAME: String, SALARY: Int) extends Employee case class Worker(FIRST_NAME: String, LAST_NAME: String, SALARY: Int) extends Employee

A findPeopleExecutive függvény paraméterként megkapja a Connection String-et. Ebből tudja, hogy melyik adatbázishoz, milyen driverrel, melyik felhasználó nevében azonosítva kell belépve lekérdezést végrehajtva Employee típusú listát előállítva visszaadni. A külső using() függvény paramétere a végrehajtandó SQL utasítást reprezentáló objektum. Ehhez hozzárendeli a belső using() függvény által visszaadott értéket. A belső using() függvény végrehajtja az SQL lekérdező utasítást, amely egy kétoszlopos eredménytáblát ad vissza, kiválogatva a minta-adatbázisból az emberek összekonkatentált nevét és fizetését. Ezt az eredménytáblát röptében feldolgozva, lefuttatja rajta a bmap() függvényt, amely bejárja és feldolgozza az eredménytáblát. Amíg az rs.next() teszt feltétel igaz, addig az eredménytábla rekordjaiból egyesével példányosít egy-egy Executive osztályú objektumot, amelyeket végül listává konvertálva visszaad. A dolgozókat összegyűjtő függvényben csak az SQL lekérdezés különbözik.

def findPeopleExecutive(conn: Connection): List[Employee] = using(conn.createStatement){st => using (st.executeQuery("SELECT FIRST_NAME || ' ' || LAST_NAME AS NAME, SALARY " + "FROM EMPLOYEES e, DEPARTMENTS d "+ "WHERE e.DEPARTMENT_ID = d.DEPARTMENT_ID AND "+ "d.DEPARTMENT_NAME = 'Executive' "+ "ORDER BY NAME")){rs => bmap(rs.next){ new Executive(rs.getString("NAME"), rs.getInt("SALARY")) } } }

A példaprogram utolsó függvénye a mintaillesztést mutatja be, amely a kiválogatás programozási tételt valósítja meg, ahol egy sorozatból feltételtől függően egy kiválogatott sorozatot kapunk vissza. A mintaillesztésben a dolgozók osztályaira és azon belül a fizetésre, mint attribútum értékre is illeszthető a minta. Mivel nem minden objektum illeszthető, ezért alkalmazni kell, az _ joker mintát különben kivétel keletkezik.

def salary(e: Employee) = e match { case Executive(name,salary) if salary<20000 => println(name+" "+salary) case Worker(fname,lname,salary) if salary<17000 && salary>5000 => println(fname+" "+lname+" "+salary) case Worker(fname,lname,salary) if salary<=5000 => println(fname+" "+lname+" "+salary) case _ => None }

Xml adatok feldolgozása

A Scala nyelv speciális lehetőséget kínál xml adatok feldolgozására úgy, mint xml fájlok beolvasása, vagy mentése, lekérdezés xml fájlokból, valamint xml adatokon mintaillesztést is végezhetünk. Scala-ban az xml fájlokat a scala.xml.Elem osztály definiálja. Létrehozhatunk xml adatot literálként is, ha a kifejezés érvényes xml.

val xml = <a>This is some XML.</a>

Kapcsos zárójelek között Scala forráskódot is elhelyezhetünk.

<bait> { if (yearMade < 2000) <old>{yearMade}</old> else xml.NodeSeq.Empty } </bait>

Ezek után könnyedén hozhatunk létre például egy toXml függvényt egy osztályban, ha visszatérési értéknek xml literált adunk meg és a megfelelő xml tag-ek között kapcsos zárójelekkel megadjuk az adattagokat.

abstract class CCTherm { ... def toXML = <cctherm> <description>{description}</description> <yearMade>{yearMade}</yearMade> <bookPrice>{bookPrice}</bookPrice> </cctherm> }

Xml adatok feldolgozására Xpath lekérdezőnyelven alapuló metódusokat használhatunk, amelyekkel könnyedén tudunk deszerializálni xml adatokat (egy apró különbség a Xpath / és // jeleitől, hogy Scala-ban a \ és \\ jelet kell használni a megjegyzések miatt).

println(xml.\\("@lang"))

Az XML osztály save metódusával tudunk menteni xml állományt, illetve a loadFile függvénnyel betölteni.

scala.xml.XML.save("therm.xml", node)

Xml adatokon a mintaillesztést is használhatunk, ahol is az xml tag-eket használhatjuk fel az illesztésre. A behelyettesítendő mintát blokk között kell megadni hasonlóan az xml literálokhoz.

node match { case <a>{contents}</a> => "It's an a: "+ contents case <b>{contents}</b> => "It's a b: "+ contents case _ => "It's something else." }

N királynő

A következő néhány sor egy egyszerű megoldást nyújt egy nem is olyan egyszerű problémára. Egy NxN-es sakktáblán szeretnénk elhelyezni N db királynőt úgy, hogy azok ne üssék egymást. Ez az N királynő probléma, amely a 8 királynő probléma általánosítása.
Ebben a modellben a királynőket koordinátapárokkal jelöljük, ami egyértelműen meghatározza egy adott királynő pozícióját a táblán. (a koordináták 1 és N közé esnek)

def queens(n: Int): List[List[(Int, Int)]] = { def placeQueens(k: Int): List[List[(Int, Int)]] = { if (k == 0) List(List()) else for { queens <- placeQueens(k - 1) column <- 1 to n newQueen = (k, column) if isSafe(newQueen, queens) } yield newQueen :: queens } placeQueens(n) } def isSafe(newQueen: (Int, Int), queens: List[(Int, Int)]) = queens forall (q => !inCheck(newQueen, q)) def inCheck(queenA: (Int, Int), queenB: (Int, Int)) = queenA._1 == queenB._1 || queenA._2 == queenB._2 || (queenA._1 - queenB._1).abs == (queenA._2 - queenB._2).abs

A queens(n) függvény egyetlen paramétere a sakktábla mérete (ami egyben a királynők száma is), visszatérési értéke pedig a megoldások listája. Ennek a listának minden eleme egy számpárokból álló lista, amely leírja mely koordinátájú mezőkre kell állítanunk a királynőket. Magát a problémát ez az egy függvény oldja meg, de ehhez szükség van a következő két segédfüggvényre is.
Az isSafe(newQueen, queens) függvény newQueen paramétere az újonnan a táblára helyezendő királynő koordinátáit jelölő számpár, queens paramétere pedig a táblán már elhelyezett királynők koordinátáinak listája. A függvénynek true a visszatérési értéke, ha a táblán már elhelyezett királynőket figyelembe véve az újonnan felhelyezendő királynő biztonságban van, azaz egyetlen királynő sem üti azt, minden más esetben false a visszatérési értéke.
Az inCheck(queenA, queenB) függvény queenA és queenB paraméterei egy-egy királynő koordinátái. Ez a függvény is egy logikai értékkel tér vissza attól függően, hogy a paraméterként kapott királynők ütik egymást vagy sem.

A fenti megoldás egy egyszerű trial-and-error módszert valósít meg, oszlopról oszlopra haladva próbál felhelyezni egy újabb királynőt és ha sikerült felhelyezni N darabot, akkor ezt tekinti egy lehetséges megoldásnak, majd tovább próbálkozik. Ha a megoldás egyszerűségét szembeállítjuk a probléma bonyolultságával, jól látszik mi mindenre képes a Scala nyelv.

Számológép

A következő program egy egyszerű számológépet mutat be, amelyben az alapvető műveletek végezhetőek el (összeadás, kivonás, osztás, szorzás, gyökvonás és reciprok műveletek). A feladat megoldásához és megjelenítéséhez a scala.swing csomag nyújtott segítséget. Az alkalmazás a SimpleSwingApplication osztályra épül.

object Calculator extends SimpleSwingApplication { def top = new UI }

Maga a feület egy szövegmezőből és gombokból épül fel. A megjelenítést elrendezéséről a GridBagPanel gondoskodik. Az egyes elemeket ezen a felületen úgynevezett megkötésekkel lehet elhelyezni:

def constraints(x: Int, y: Int, gridWidth: Int = 1, gridHeight: Int = 1, weightX: Double = 0.0, weightY: Double = 0.0, fill: GridBagPanel.Fill.Value = GridBagPanel.Fill.Both): Constraints = { // Létrehozunk egy új objektumot val c = new Constraints // Rács X pozíciójának beállítása c.gridx = x // Rács Y pozíciójának beállítása c.gridy = y // Rács szélességenek beállítása c.gridwidth = gridWidth // Rács magasságának beállítása c.gridheight = gridHeight // Az X atméretezési szorzó beállítása c.weightx = weightX // Az Y atméretezési szorzó beállítása c.weighty = weightY // Kitöltés típusának beállítása c.fill = fill // Visszaadjuk az objektumot c }

A felületre a gombokat egy gyártó metódussal generáljuk. Ez a metódus az összes gombnak egységesen beállítja a betűméretét és az egy gridre eső méretét. A programban ez a metódus a következőképpen néz ki:

def createBtn(text: String): Button = { // Gomb objektum létrehozása val b = new Button // Gomb szövegének beállítása b.text = text // Szöveg méretének beállítása b.font = new Font(b.font.getFontName, b.font.getStyle, 15) // Gomb méretének beállítása b.preferredSize = new Dimension(60, 50) // Gomb visszaadása b }

A program tartalmaz egy szövegmezőt is, ami tulajdonképpen a számológépnek a megjelenítője. A szövegmező csak egyszer kerül létrehozásra, úgy hogy ne lehessen szerkeszteni. A szövegmezőt a következő kód generálja ki:

val display = new TextField() { // Beállítja a kezdőértéket a szövegmezőnek text = "0" // Letiltja, hogy a szövegmező szerkeszthető legyen editable = false // Szöveg méretének beállítása font = new Font(font.getFontName, java.awt.Font.BOLD, 20) // Szövegmező méretének beállítása preferredSize = new Dimension(60, 50) // Beállítja, hogy a szöveg jobbra legyen igazítva horizontalAlignment = Alignment.Right }

A teljes számológép programotot a itt lehet letölteni.

2048

A program a népszerű 2048-as játék játszására nyújt lehetőséget.

A játék lényege, hogy egy 4x4-es négyzetben kell a számokat úgy tologatni, hogy a 2 hatványai végül 2048-at adjanak ki. Az egész hálót mozgatjuk négy irányba, a mozgatásokkal az azonos értékek összecsúsznak és összeadódnak.

Készítette: Balogh Bernadett, 2014

Fordítóprogram: Scala 2.10.3

A forrás itt található.

Amőba - konzol

Klasszikus 3x3-as amőba játék, konzolban történő megjelenítéssel és irányítással, 2 emberi játékos részére.

Forrás: letöltés