Muutettu viimeksi 19.4.2016 / Sivu luotu 10.4.2014 / [oppikirjan esimerkit] / [Scala]
Oppikirjan luku 20 esittelee laveasti mm. abstrakteja luokkamäärittelyn jäseniä, funktio-olioiden laiskaa käynnistämistä ja paljon muuta.
Tällä kurssilla vain kurkistetaan muutamaan kiinnostavaan ideaan.
Luokkaan tai piirretyyppiin voidaan määritellä metodien lisäksi myös kenttiä ja tyyppejä(!) abstrakteiksi:
trait Abstract { type T def transform(x: T): T val initial: T var current: T }Tuollainen piirretyyppi voidaan liittää konkreettiseksi tarkoitettuun luokkaan korvaamalla kaikki abstraktit jäsenet konkreettisilla ja kiinnittämällä abstrakti tyyppi:
class Concrete extends Abstract { type T = String def transform(x: T) = x + x // tai def transform(x: String) = x + x val initial = "hi" var current = initial } val a = new Concrete println(a.initial + " " + a.transform(a.current)) // hi hihi
Huomioitavaa:
trait OnnenluvunOminaisuudet { val itseLuku: Int def paritonko = itseLuku%2 != 0 // ym. } class Kakkonen extends OnnenluvunOminaisuudet { val itseLuku = 2 // ym. } class Kolmonen extends OnnenluvunOminaisuudet { val itseLuku = 3 // ym. } val matti = new Kakkonen val pekka = new Kolmonen println(matti.paritonko) // false println(pekka.paritonko) // true // myös nimettömään luokkaan (!) voi liittää piirretyypin: val maija = new OnnenluvunOminaisuudet { val itseLuku = 123 // ym. } println(maija.paritonko) // true
import scala.language.reflectiveCalls // !! // reflective access of structural type member method a // should be enabled by making the implicit value // scala.language.reflectiveCalls visible. val x = new {var a = 123} // nimetön luokka println(x.a) // 123 x.a = 789 println(x.a) // 789 trait Piirre { var b = 321 } val y = new Piirre {} // nimetön luokka piirretyypillä täydennettynä println(y.b) // 321 y.b = 987 println(y.b) // 987Jos nämä käännetään ainokaisen sisällä
object Koe extends App { val x = new {var a = 123} // nimetön luokka println(x.a) // 123 x.a = 789 println(x.a) // 789 trait Piirre { var b = 321 } val y = new Piirre {} // nimetön luokka piirretyypillä täydennettynä println(y.b) // 321 y.b = 987 println(y.b) // 987 }saadaan seuraavat luokkatiedostot:
Koe$$anon$1.class Koe$$anon$2.class Koe.class Koe$.class Koe$delayedInit$body.class Koe$Piirre.class Koe$Piirre$class.class
Kuten muistetaan, myös funktiot ovat first-class-arvoja – niitäkin voi sijoitella kenttiin tai muuttujiin ...
Kenttään val sijoitettavan arvon laiska evaluointi ilmaistaan määreellä lazy.
Pikku esimerkki interaktiivisella tulkilla:
scala> val boo = {println("evaluoidun"); "böö"} // "ahkera" evaluoidaan saman tien evaluoidun boo: String = böö scala> lazy val boo = {println("evaluoidun"); "böö"} // laiskaa ei vielä boo: String = <lazy> scala> boo // vasta tämä saa aikaan funktion suorituksen evaluoidun res0: String = böö scala> boo // mutta se suoritetaan vain kerran! res1: String = böö
Tarkastellaan sitten tilannetta, jossa jotain "isoa" kokoelmaa manipuloidaan useammalla tavalla:
val lista = List(3, -6, 23, 92, 4, 66) // tämä on se "iso" kokoelma lazy val eiNegat = lista.filter(_>=0) lazy val tuplaa = eiNegat.map(_*2) lazy val plus1 = tuplaa.map(_+1) println(plus1) // List(7, 47, 185, 9, 133)Nyt muuttujille (!) eiNegat, tuplaa ja plus1 ei lasketa alkuarvoa määrittelyjen yhteydessä, vaan vasta tulostuskomento käynnistää funktio-olion plus1, joka käynnistää funktio-olion tuplaa, joka käynnistää funktio-olion eiNegat,
Seuraus on, että koko laskenta tehdään yhdellä kerralla ja vain jos joku tulee kutsuneeksi funktio-olioa plus1. (Laitoksella kehitelty hyvin hajautettu Spark-järjestelmä käyttää tämän tyyppistä tekniikkaa!)
Tutkaillaanpa sitten vielä tilannetta, jossa olion kentät ovat laiskoja tai "tavallisia" valleja:
class C(lista: List[Int]) { lazy val eiNegat = lista.filter(_>=0) lazy val tuplaa = eiNegat.map(_*2) lazy val plus1 = tuplaa.map(_+1) } val a = new C(List(1,2,-3,4)) println(a.plus1) // List(3, 5, 9) val b = new C(List(10,20,-30,40)) println(b.plus1) // List(21, 41, 81) class CC(lista: List[Int]) { val eiNegat = lista.filter(_>=0) val tuplaa = eiNegat.map(_*2) val plus1 = tuplaa.map(_+1) } val aa = new CC(List(1,2,-3,4)) println(aa.plus1) // List(3, 5, 9) val bb = new CC(List(10,20,-30,40)) println(bb.plus1) // List(21, 41, 81)Aivan kuin mitään eroa ei olisi laiskan ja "ahkeran" välillä, vaan laitetaanpas vähän aputulostuksia, niin kuinkas sitten käykään ...
class C(lista: List[Int]) { lazy val eiNegat = {println("eiNegat"); lista.filter(_>=0)} lazy val tuplaa = {println("tuplaa"); eiNegat.map(_*2)} lazy val plus1 = {println("plus1"); tuplaa.map(_+1)} } val a = new C(List(1,2,-3,4)) println("Ei vielä kutsuttu metodia plus1") println(a.plus1) println("----------------") class CC(lista: List[Int]) { val eiNegat = {println("eiNegat"); lista.filter(_>=0)} val tuplaa = {println("tuplaa"); eiNegat.map(_*2)} val plus1 = {println("plus1"); tuplaa.map(_+1)} } val aa = new CC(List(1,2,-3,4)) println("Ei vielä kutsuttu metodia plus1") println(aa.plus1)Tulostus on
Ei vielä kutsuttu metodia plus1 plus1 tuplaa eiNegat List(3, 5, 9) ---------------- eiNegat tuplaa plus1 Ei vielä kutsuttu metodia plus1 List(3, 5, 9)Olion kenttien arvojen laiska evaluointi voi liittyä esimerkiksi tilanteeseen, jossa joitain raskaita tai tilaa vieviä laskentoja tai tilanvarauksia tehdään vain tarvittaessa.