Ohjelmoinnin peruskurssien laaja oppimäärä Luento 14: Johdanto Scala-kieleen Riku Saikkonen 9. 2. 2011
Sisältö 1 Kieli ja työkalut 2 Scala-esimerkkejä 3 Olioista ja tyypeistä
Lyhyesti Scalasta kielenä Scalassa on: Java lyhyemmällä syntaksilla (melkein: mm. static puuttuu) Javan oliojärjestelmää on hieman laajennettu (ja korjailtu?) kaiken koodin ei tarvitse olla luokan sisällä (tosin sisäisesti on) ominaisuuksia funktionaalisesta ohjelmoinnista: mm. tyyppipäättely (osittainen), hahmonsovitus ja listankäsittelyoperaatiot (esim. map) eräänä tavoitteena oli tehdä parempi Java kuin Java toimii Javan virtuaalikoneessa etuja: toimii suoraan yhteen Java-koodin kanssa, Javan kirjastoja voi käyttää haittoja: erikoista toimintaa yksityiskohdissa, kokonaisuutena melko monimutkainen kieli, muita kuin Java-kirjastoja työläs käyttää (kuten Javassakin) myös.net-toteutus on (ollut?) olemassa
Scala-työkaluja kääntäjä scalac ja interaktiivinen tulkki scala kuten melkein kaikki Scheme-toteutuksetkin, interaktiivinen tulkki kääntää koodinsa ennen suoritusta mutta Scala-tulkin kääntäjä ei ole kovin nopea scaladoc ja scalap kuten Javassa IDE-tukea mm. Eclipse, Emacs, vim, IDEA, IntelliJ, Netbeans monissa IDEissä tuki on vielä keskeneräistä debuggerituki lienee kesken: Java-debuggeri jdb toimii enimmäkseen monissa IDEissä on oma debuggerituki tulkkiin saattaa olla myöhemmin tulossa debuggeri
Ohjelmien ajaminen ohjelmia voi ajaa ja kokeilla Scala-tulkissa (komento scala) samaan tapaan kuin Schemessäkin kuten Schemessäkin, tulkkiin voi kirjoittaa Scala-koodia suoraan tulkkikomento :load tiedostonimi.scala lataa tiedoston katso myös :help niitä voi myös kääntää (komento scalac) käännettyä koodia voi ajaa joko scalalla tai javalla (lisää classpathiin scala-library.jar) Kokonainen ohjelma (Javan mainin vastine) object HelloWorld { def main(args: Array[String]) { println("hello, world!")
Sisältö 1 Kieli ja työkalut 2 Scala-esimerkkejä 3 Olioista ja tyypeistä
Scheme Scala: SICPin sqrt-iter-esimerkki Scalalla (SbE 4.44.5) def square(x: Double) = x * x def average(x: Double, y: Double) = (x + y) / 2 def abs(x: Double) = if (x >= 0) x else -x def sqrt(x: Double) = { def isgoodenough(guess: Double) = abs(square(guess) - x) < 0.001 def improve(guess: Double) = average(guess, x / guess) def sqrtiter(guess: Double): Double = if (isgoodenough(guess)) guess else sqrtiter(improve(guess)) sqrtiter(1.0) Schemellä (SICP 1.1.8) (define (square x) (* x x)) (define (average x y) (/ (+ x y) 2)) (define (sqrt x) (define (good-enough? guess) (< (abs (- (square guess) x)) 0.001)) (define (improve guess) (average guess (/ x guess))) (define (sqrt-iter guess) (if (good-enough? guess) guess (sqrt-iter (improve guess)))) (sqrt-iter 1.0)) (SbE = Scala by Example, SICP = Structure and Interpretation of Computer Programs)
Yksinkertainen olioesimerkki Luokka, aliluokka ja käyttöesimerkki class Point(val x: Double, val y: Double) { def dist(that: Point) = { def square(x: Double) = x * x math.sqrt(square(this.x - that.x) + square(this.y - that.y)) override def tostring() = "Point at (" + x + ", " + y + ")" class ColoredPoint(override val x: Double, override val y: Double, val color: String) extends Point(x, y) { var brightness: Int = 1 def brighten() { brightness += 1 override def tostring() = super.tostring() + " colored " + color + " brightness " + brightness val p1 = new Point(5, 8) val p2 = new ColoredPoint(2, 3, "blue") p2.brighten() println(p1 dist p2); println(p1.tostring); println(p2.tostring)
Esimerkkejä syntaksista: lausekkeita enimmäkseen syntaksi on lähellä Javaa, mutta monet kohdat voi jättää pois puolipistettä ei useimmiten tarvita rivin lopussa aaltosulkuja ei usein tarvita vain yhden lausekkeen ympärillä lausekejonon {...;...;... (paluu)arvo on sen viimeisen lausekkeen arvo, kuten Schemessä: if (x<6) { println("joo"); 3 else 2 3 metodikutsussa sulut voi usein (ei aina) jättää pois: a b x on a.b(x) ja a b c d e on a.b(c).d(e) Scalassa on paljon muitakin lyhennysmerkintöjä: esim. 1 to 5 foreach { println on sama kuin 1.to(5).foreach({ x: Int => println(x) ); x: Int kertoo, että x on tyyppiä Int (luokan tai aliluokan jäsen) yleensä tyyppejä ei tarvitse kertoa käsin, paitsi funktion ja metodin määrittelyssä argumenttien tyypit (joskus myös paluuarvo) Scala on case-sensitive: a on eri muuttuja kuin A
Esimerkkejä syntaksista: määrittelyjä var määrittelee muuttujan: var x: Int = 3 var y = x + 6 val määrittelee vakiomuuttujan: val x: Int = 3 val y = x + 6 muuten kuin muuttuja, mutta siihen ei voi sijoittaa uutta arvoa (vrt. Javan final) valia käytetään Scalassa paljon, varia pyritään välttämään (kuten funktionaalisessa ohjelmoinnissa) def määrittelee funktion tai metodin: def square(x: Double) = x * x def square(x: Double) : Double = x * x def square(x: Double) : Double = { return x * x // ei suositella def square(x: Double) = { println(x); x * x def myprint(x: Double) { println(x) // ei paluuarvoa, ei =-merkkiä funktioita voi määritellä sisäkkäin (leksikaalinen sidonta)
Esimerkkejä syntaksista: luokat tyhjä luokka: class Foo kaksi kenttää: class Point(x: Int, y: Int) tai: class Point(val x: Int, val y: Int) tekee automaattisesti kaksiargumenttisen konstruktorin sekä get-metodit val kertoo, että kenttien arvoja ei voi muuttaa class Point(var x: Int, var y: Int) tekee myös set-metodit metodin määrittely näyttää samalta kuin funktion: class Point(val x: Double, val y: Double) { def dist(that: Point) = { def square(x: Double) = x * x // metodin sisäinen funktio math.sqrt(square(this.x - that.x) + square(this.y - that.y)) override def tostring() = "Point at (" + x + ", " + y + ")" yliluokan metodia korvatessa pitää käyttää avainsanaa override tarkoituksena on suojata ohjelmoijaa väärin kirjoitetuilta nimiltä
Scheme Scala: SICPin sqrt-iter-esimerkki Scalalla (SbE 4.44.5) def square(x: Double) = x * x def average(x: Double, y: Double) = (x + y) / 2 def abs(x: Double) = if (x >= 0) x else -x def sqrt(x: Double) = { def isgoodenough(guess: Double) = abs(square(guess) - x) < 0.001 def improve(guess: Double) = average(guess, x / guess) def sqrtiter(guess: Double): Double = if (isgoodenough(guess)) guess else sqrtiter(improve(guess)) sqrtiter(1.0) Schemellä (SICP 1.1.8) (define (square x) (* x x)) (define (average x y) (/ (+ x y) 2)) (define (sqrt x) (define (good-enough? guess) (< (abs (- (square guess) x)) 0.001)) (define (improve guess) (average guess (/ x guess))) (define (sqrt-iter guess) (if (good-enough? guess) guess (sqrt-iter (improve guess)))) (sqrt-iter 1.0))
Yksinkertainen olioesimerkki Luokka, aliluokka ja käyttöesimerkki class Point(val x: Double, val y: Double) { def dist(that: Point) = { def square(x: Double) = x * x math.sqrt(square(this.x - that.x) + square(this.y - that.y)) override def tostring() = "Point at (" + x + ", " + y + ")" class ColoredPoint(override val x: Double, override val y: Double, val color: String) extends Point(x, y) { var brightness: Int = 1 def brighten() { brightness += 1 override def tostring() = super.tostring() + " colored " + color + " brightness " + brightness val p1 = new Point(5, 8) val p2 = new ColoredPoint(2, 3, "blue") p2.brighten() println(p1 dist p2); println(p1.tostring); println(p2.tostring)
Sisältö 1 Kieli ja työkalut 2 Scala-esimerkkejä 3 Olioista ja tyypeistä
Scalan tyyppejä Scalan omia tyyppejä: Int, Double, jne. (kaikki ovat olioita, mutta kääntäjä voi taustalla käyttää Javan primitiivityyppejä) AnyRef Javan Object AnyVal on Int, Double, jne. -luokkien yhteinen yliluokka tyyppihierarkian huipulla on Any (aliluokkina AnyRef ja AnyVal) Scala-kirjastoissa paljon lisää (esim. List) lisäksi kaikki Javan luokat ja kirjastot (esim. merkkijonovakio on java.lang.string) lähinnä virheilmoituksissa näkyy myös: RichInt, RichString, jne. ovat laajennettuja versiota Java-tyypeistä (Scala konvertoi tarvittaessa Java-tyyppejä automaattisesti näiksi) Unit on void eli esim. ei paluuarvoa (tyyppi, jolla on vain yksi arvo () vrt. display:n paluuarvo monissa Schemeissä) Nothing on tyyppi, jolla ei ole arvoja (näkyy joskus virheissä, jos tyyppipäättely ei keksi tyyppiä)
Konstruktorit olioita tehdään samoin kuin Javassakin: new luokka ( konstruktorin argumentit ) joissain luokissa (ns. case class) new-sanan voi jättää pois Scalan konstruktorit määritellään eri lailla kuin Javassa class Point(val x: Int, val y: Int) {... :ssä x ja y ovat pääkonstruktorin argumentit ja... sen runko siis class:n rungossa mahdollisesti oleva koodi ajetaan konstruktoria kutsuttaessa apukonstruktoreja määritellään metodien tapaan nimellä this Konstruktoriesimerkki class Circle(val x: Int, val y: Int) { var radius: Int = 0 val atorigin: Boolean = (x==0 && y==0) println("circle constructed!") def this() { this(0, 0); radius = 5 // muokattava kenttä // vakiokenttä // ajetaan new:tä kutsuttaessa // apukonstruktori
Kentät ja get- ja set-metodit olion kentät ovat konstruktorin argumentit sekä class-rakenteen rungossa olevat valit ja varit kentät ovat aina Java-mielessä private mutta niille tehdään automaattisesti haku- ja tarvittaessa asetusmetodit kentän haku saman nimisellä metodilla: obj.x tai obj.x() kutsuu metodia x ilman argumentteja asetussyntaksi obj.x = y muuttuu metodikutsuksi obj.x_=(y) automaattiset metodit voi myöhemmin vaihtaa Java-tyylisesti käsin määritellyiksi, esim. private var realx: Int = 1 def x = { println("get"); realx def x_=(v: Int) = { println("set"); realx = v mutta aliluokassa niitä ei voi korvata (!) tällaisia erikoismetodeja on muutama muukin: esimerkiksi funktio on toteutettu oliona, jolla on apply-metodi, ja funktiokutsu f(2) on sisäisesti f.apply(2) Scala-taulukon apply-metodi indeksoi taulukkoa: a(3) eikä a[3]
Operaattorit ovat metodeja Scalassa operaattorit +, *, += jne. ovat todellisuudessa tavallisia metodeja muuten normaaleja metodikutsuja, mutta laskujärjestyksen määräämässä järjestyksessä myös esim. 1+2 on Int-luokan metodikutsu 1.+(2) kääntäjä osaa optimoida turhia kutsuja pois (kuinka hyvin?) erikoisuus: kaksoispisteeseen : loppuvat metodit ovat muuten tavallisia, mutta ensimmäiset kaksi argumenttia toisin päin esim. Schemen appendin vastine List(1) ::: List(2) kutsuu jälkimmäisen List-olion :::-metodia näitäkin metodeja voi määritellä ja korvata itse, ja Scalan kirjastot käyttävät niitä paljon varsinkin kokoelmaluokissa (listat, taulukot, jne.)
Yksittäisoliot ja object Scalassa ei ole Javan staticia sen sijaan object-lauseella voi tehdä olioista yksittäiskappaleita (stand-alone, singleton object) syntaksi on sama kuin classissa (voi myös periä) nämä ovat globaaleja eli näkyvät kaikkialle (näkyvyyksien mukaan) tietynnimistä objectia on olemassa aina tasan yksi saman nimiset object ja class näkevät toistensa private-osat objectit luodaan (ajamalla konstruktori eli sisällä oleva koodi) object-lausetta evaluoidessa (käännetyn ohjelman käynnistyessä) objectin kentät ja metodit ovat Javan staticin vastine Javan main on Scalassa objectin main-metodi Esimerkki (sekä tulkissa ajettava että itsenäinen ohjelma) object Foo { def f(x: Int) = x + 1 def main(args: Array[String]) { println("hello, world!") Foo.f(4) 5
Anonyymit funktiot Scalassa voi tehdä anonyymejä funktioita samalla lailla kuin Schemen lambdalla tavallisin syntaksi: { argumentit => runko (aaltosulut voi usein jättää pois) näissäkin yleensä argumentteihin tarvitaan tyyppitieto, mutta muutamissa tilanteissa Scala osaa päätellä sen itse lyhennysmerkintöjä: { _ < 0 on { x => x < 0 ja { _ + _ on { x,y => x + y (muitakin on) Esimerkki (myös funktioargumenteista) (SbE 5) def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b) def square(x: Int): Int = x * x def sumsquares1(a: Int, b: Int): Int = sum(square, a, b) def sumsquares2(a: Int, b: Int): Int = sum((x: Int) => x * x, a, b) def sumsquares3(a: Int, b: Int): Int = sum(x => x * x, a, b)