Ohjelmointikieliä on paljon, hyvin paljon. Scala on yksi uusi monien joukossa. Sillä on kuitenkin jotakin, mitä monilta puuttuu: se vaikuttaa hyvin lupaavalta, suorastaan häkellyttävän lupaavalta.
Kielen viralliset sivut ovat osoitteessa http://www.scala-lang.org/ . Tuolta löytyy mm. aloittelijan opas ja kielen spesifikaatio sekä jotakin noiden ääripäiden väliltäkin. Tekeillä olevan oppikirjan Programming in Scala "PrePrint"-versio löytyy (maksua vastaan!) kustantajan sivulta http://www.artima.com/shop/forsale . Yhtenä kirjoittajana on Scala-kielen isä Martin Odersky.
Scalalla on monia kiinnostavia ominaisuuksia. Seuraavassa on pieni ja epätäydellinen luettelo joistakin kielen ominaisuuksista. Luettelossa on muutamia sivun sisäisiä linkkejä kyseisen ominaisuuden esittelyyn.
Tämän sivun sisältöä:
Esimerkki 1: Ohjelma laskee syöttölukujen keskiarvon. Lukumäärä pyydetään.
import java.util.Scanner; public class Karvo { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { System.out.println("Monenko luvun keskiarvo lasketaan? "); int lukujenLkm = lukija.nextInt(); double lukujenSumma = 0; int monesko = 0; while (monesko < lukujenLkm) { monesko = monesko+1; System.out.print("Anna "+monesko+". luku: "); double luku = lukija.nextDouble(); lukujenSumma += luku; } if (lukujenLkm <= 0) System.out.println("\nEi lukuja, ei keskiarvoa.\n"); else { double lukujenKarvo = lukujenSumma/lukujenLkm; System.out.println("\nKeskiarvo on "+lukujenKarvo+".\n"); } } }
println("Monenko luvun keskiarvo lasketaan? "); val lukujenLkm = readInt var lukujenSumma = 0.0 var monesko = 0 while (monesko < lukujenLkm) { monesko = monesko+1 print("Anna "+monesko+". luku: ") val luku = readDouble lukujenSumma += luku } if (lukujenLkm <= 0) println("\nEi lukuja, ei keskiarvoa.\n"); else { val lukujenKarvo = lukujenSumma/lukujenLkm; println("\nKeskiarvo on "+lukujenKarvo+".\n"); }Ylläolevat rivit voidaan kirjoittaa tekstitiedostoon ja suorittaa sellaisenaan komennolla:
Java-ohjelmahan joudutaan aina ensin kääntämään tavukoodiksi ja sitten erikseen suorittamaan tulkilla.
Esimerkki 2: Myös olioiden kanssa askartelu on Javaa paljon vaivattomampaa. Seuraavassa peruskurssin "ensimmäinen olio"
public class Kuulaskuri { private int kuu; // sallitut arvot 1,..12 public Kuulaskuri() { kuu = 1; } public int moneskoKuu() { return kuu; } public void seuraavaKuu() { ++kuu; if (kuu == 13) kuu = 1; } public static void main (String[] args){ Kuulaskuri a = new Kuulaskuri(); System.out.println(a.moneskoKuu()); a.seuraavaKuu(); System.out.println(a.moneskoKuu()); int i = 0; while (i < 20) { a.seuraavaKuu(); System.out.println(a.moneskoKuu()); i += 1; } } }
class Kuulaskuri { private var kuu = 1 // sallitut arvot 1,..12 def moneskoKuu = kuu def seuraavaKuu { kuu += 1 if (kuu == 13) kuu = 1 } } var a = new Kuulaskuri println(a.moneskoKuu) // voi kirjoittaa myös println(a moneskoKuu) a.seuraavaKuu // voi kirjoittaa myös a seuraavakuu; println(a moneskoKuu) var i = 0 while (i < 20) { a seuraavaKuu; println(a moneskoKuu) i += 1 }
scala> 1+2 res0: Int = 3 scala> println("Hoi maailma!") Hoi maailma! scala> var a=5 a: Int = 5 scala> while (a>0) { | println(a) | a=a-1 | } 5 4 3 2 1 scala> def neliösumma(a:int, b:Int)= | a*a+b*b neliösumma: (int,Int)Int scala> neliösumma(3,4) res3: Int = 25 scala> exit
println("Montako tervehdystä?") var lkm = readInt while (lkm>0) { println("Hoi maailma!") lkm = lkm-1 }Suoritus komennolla
object tervehdi { def main(args: Array[String]) = { println("Montako tervehdystä?") var lkm = readInt while (lkm>0) { println("Hoi maailma!") lkm = lkm-1 } } }Ohjelma Javan tapaan käännetään ensin tavukoodiksi
#!/usr/bin/env scala !# println("Montako tervehdystä?") var lkm = readInt while (lkm>0) { println("Hoi maailma!") lkm = lkm-1 }Ohjelma käynnistyy yksinkertaisesti tiedostonimellä. Suoritusoikeus tiedostolle on luonnollisesti annettava.
def sort(xs: Array[Int]) { def swap(i: Int, j: Int) { val t = xs(i); xs(i) = xs(j); xs(j) = t } def sort1(l: Int, r: Int) { val pivot = xs((l + r) / 2) var i = l; var j = r while (i <= j) { while (xs(i) < pivot) i += 1 while (xs(j) > pivot) j -= 1 if (i <= j) { swap(i, j) i += 1 j -= 1 } } if (l < j) sort1(l, j) if (j < r) sort1(i, r) } sort1(0, xs.length - 1) }Tässä huomiota kannattaa kiinnittää mm. siihen, miten Scala Algolin ja Pascalin tapaan sallii sisäkkäiset aliohjelmat meille vanhemmille tutuin näkyvyyssännöin ;-). C-pohjaisissa kielissä - Java mukaan lukien! - aliohjelmarakenne on "litteä": niissä aliohjelma ei voi sisältää omia "pikku apulaisiaan" Algolin ja Pascalin tapaan!
def sort(xs: Array[Int]): Array[Int] = if (xs.length <= 1) xs else { val pivot = xs(xs.length / 2) Array.concat( sort(xs filter (pivot >)), xs filter (pivot ==), sort(xs filter (pivot <)) ) }Tässä huomio kannattaa kiinnittää muutamaan seikkaan: Array.concat liittää yhteen kolme osataulukkoa. ensimmäiseen suodatetaan kaikki jakoalkiota pienemmät alkiot, keskimmäiseen jakoalkion kanssa yhtä suuret ja kolmanteen suuremmat. Suodattimelle (filter) annetaan argumenttina ns. predikaattifunktio tyyliin "pivot >", mikä todellakin tarkoittaa sitä, että vertailun operaatio annetaan parametrina. Scalassa funktiot ovat arvoja siinä kuin numeeriset arvotkin, funktioita voidaan välittää parametreina, muuttujan arvona voi olla funktio, funktio voi palauttaa arvonaan funktion, käytettävissä ovat funktioliteraalit siinä kuin numeeriset literaalitkin...
var a = new Array[Int](4) a(0) = 4 a(1) = 3 a(2) = 8 a(3) = 1Alkiot voidaan käydä läpi indeksoiden tai "for each" -tyyliin:
for (i <- 0 to 3) println(a(i)) for (e <- a) // "foreach"! println(e)Aivan vastavalla tavalla voidaan käsitellä listoja:
val a = List(4, 3, 8, 1) for (i <- 0 to 3) println(a(i)) for (i <- a) // "foreach"! println(i)
// määritellään pari funktiota ja luodaan lista: def muotoile(muotoilija: String => String, sanat: List[String]) = for(val sana <- sanat) print(muotoilija(sana)) def huuda(x: String) = x.toUpperCase val lista = List("hip ", "hurraa!") // annetaan parametrina ensin nimetty funktio ja lista: muotoile(huuda, lista) println // tulostus: HIP HURRAA! // annetaan sitten parametrina funktioliteraali ja lista: muotoile({x => x.replace('h', 'j')}, lista) println // tulostus: jip jurraa!Esimerkissä siis funktion muotoile muodollinen funktioparametri määriteltiin muodossa: muotoilija: String => String ja vastaavina todellisina parametreina käytettiin nimettyä funktiota huuda ja funktioliteraalia {x => x.replace('h', 'j')}.
class Pikkuvarasto (private var määrä: double, val tuote: String) { def this() = this(0.0, "(nimetön)") // parametriton konstruktori oletusarvoin def paljonkoOn = määrä // aksessori on tehtävä koska määrä on private! def vieVarastoon(paljonko: double) = if (paljonko > 0) määrä += paljonko def otaVarastosta(paljonko: double) = if (paljonko <= 0) 0.0 else if (paljonko <= määrä) {määrä -= paljonko; paljonko;} else {val saatiin = määrä; määrä = 0; saatiin;} override def toString = "("+tuote+": "+määrä+")" def summa(toinen: Pikkuvarasto) = new Pikkuvarasto(määrä + toinen.määrä, tuote + toinen.tuote); }Huomioita:
// Pikkuvaraston kokeilua Java-esimerkin tapaan: val mehua = new Pikkuvarasto(10, "Mehu") val olutta = new Pikkuvarasto(123.4, "Olut") val bensaa = new Pikkuvarasto(90.1, "Bensa") println(mehua); println(olutta); println(bensaa) val a = new Pikkuvarasto(); println(a) val b = mehua; println("" + b + mehua) b vieVarastoon(25); println("" + b + mehua) println("Muuttuja bensaa:" + bensaa + ", määrä=" + bensaa.paljonkoOn + ", nimi=" + bensaa.tuote) println(bensaa) bensaa.vieVarastoon(27.4); println(bensaa) bensaa.vieVarastoon(-34.1); println(bensaa) println(olutta) var saatiin: double = olutta.otaVarastosta(10.2) println("Saatiin " + saatiin + ", tilanne: "+olutta) saatiin = olutta.otaVarastosta(-78.8) println("Saatiin " + saatiin + ", tilanne: "+olutta) saatiin = olutta.otaVarastosta(175.5) println("Saatiin " + saatiin + ", tilanne: "+olutta) olutta.vieVarastoon(432.1) val c = mehua.summa(olutta) println("" + mehua + olutta + c) var d = c.summa(bensaa); println("" + c + bensaa + d) d = d.summa(bensaa.summa(mehua)); println(d)
Esimerkki Algol 60 -kielellä:
begin integer i; real procedure sum (i, lo, hi, term); value lo, hi; integer i, lo, hi; real term; comment term is passed by-name, and so is i; begin real temp; temp := 0; for i := lo step 1 until hi do temp := temp + term; sum := temp end; comment note the correspondence between the mathematical notation and the call to sum; print (sum (i, 1, 100, 1/i)) endTässä esimerkissä Jensen's Deviceä käytetään harmonisen sarjan (osa-)summan laskentaan:
Samalla aliohjelmalla voitaisiin laskea samaan tapaan vaikkapa sarjan 1+2+3+...+100 summa: print(sum(i, 1, 100, i)). Tai geometrinen 1+1/2+1/4+...+1/100: print(sum(i, 1, 100, 1/(i*i))).
Jensen's Devicen toteuttamisessa Scalalla vaikuttaa olevan yksi ongelma: Nimiparametrivälitys kyllä onnistuu - arvo lasketaan kutsuneen ohjelman viittausympäristössä - mutta Algol 60:n tapainen nimiparametrina annettavan muuttujan arvon muuttaminen ei (näyttäisi!) onnistuvan. Yritys:
def sum(i: =>Int, lo:Int, hi:Int, term: =>Double) = { // term is passed by-name, and so is i var temp = 0.0 for (i <- lo to hi) // FOR MÄÄRITTELEE UUDEN i:N, EI TOIMI! temp = temp + term // ja parametri-i:tä ei saa muuttaa... temp }; var i: Int = 1 // jokin alkuarvo on pakko antaa; 0 johtaa 0:lla jakoon println(sum (i, 1, 100, 1/i))Tämä ei toimi, koska kutsuneen ohjelman i ei muutu. Ja itse asiassa aliohjelman muodollista parametria i ei voi lainkaan käyttää muuttujana, sijoitukset siihen ovat kiellettyjä: yrityksestä saadaan seuraus: "assignment to non-variable".
Toki vastaava laskenta voidaan ohjelmoida kielellä kuin kielellä. Scalalla vaikkapa (Petri Kutvonen):
def sum(lo:Int, hi:Int, term: Int => double) = { var temp = 0.0 for (i <- lo to hi) temp = temp + term(i) temp } println(sum(1, 100, {i => 1.0/i}))Tai enemmän alkuperäistä "deviisiä" muistuttavasti (Petri Kutvonen):
def sum(i: Int => Unit, lo: Int, hi: Int, term: => Double) = { var temp = 0.0 for (j <- lo to hi) { i(j) temp = temp + term } temp } var i = 0; println(sum((j) => {i = j}, 1, 100, 1.0/i))Edellinen taitaa kyllä olla tyylikkäämpi...
begin real procedure A (k, x1, x2, x3, x4, x5); value k; integer k; begin real procedure B; begin k:= k - 1; B:= A := A (k, B, x1, x2, x3, x4); end; if k <= 0 then A:= x4 + x5 else B; end; outreal (A (10, 1, -1, -1, 1, 0)); end;Scala-versio on hyvin selkeä - jos tämän algoritmin yhteydessä nyt selkeydestä voi puhua (vrt. Wikipedia-artikkelin esimerkkeihin)
def A(in_k: Int, x1: =>Int, x2: =>Int, x3: =>Int, x4: =>Int, x5: =>Int): Int = { var k = in_k def B: Int = { k = k-1 A(k, B, x1, x2, x3, x4) } if (k<=0) x4+x5 else B } println(A(10, 1, -1, -1, 1, 0))