Muutettu viimeksi 22.3.2010 / Sivu luotu 3.3.2010 / [oppikirjan esimerkit] / [Scala]
Ohjelmointikieliä on paljon, hyvin paljon. Scala on yksi uusi monien joukossa. Kielen viralliset sivut ovat osoitteessa http://www.scala-lang.org/ . Tuolta löytyy tietoa laidasta laitaan spesifikaatiosta ja APIsta aloittelijan oppaisiin.
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. Jos Linuxissa turvallisuussyistä "työhakemisto ei ole hakupolulla", käynnistyksessä tiedostonimen eteen pitää kirjoittaa "./" Koko istunnon ajaksi "työhakemiston voi lisätä hakupolkuun" komennolla "PATH="$PATH:.". Jos tuon komennon lisää ~/.bashrc:hen (ei-login-shellit) ja ~/.bash_profile:en (login-shellit) "työhakemisto on hakupolulla" oletusarvoisesti.
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(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! // tässä tilanteessa edes aaltosulkeet eivät ole välttämättömiä: muotoile(x => x.replace('h', 'B'), lista) println // tulostus: Bip Burraa!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)
Asiat voivat olla helppoja:
var capital = Map("US" -> "Washington", "France" -> "Paris") capital += ("Japan" -> "Tokyo") println(capital("France")) // Paris var pk = Map("Suomi" -> "Helsinki", "Ruotsi" -> "Tukholma") println(pk("Ruotsi")) // Tukholma println(pk) // Map(Suomi -> Helsinki, Ruotsi -> Tukholma) println(pk + ("Viro" -> "Tallinna")) // Map(Suomi -> Helsinki, Ruotsi -> Tukholma, Viro -> Tallinna) println(pk - ("Ruotsi")) // Map(Suomi -> Helsinki)Helppoa ja hauskaa. Näyttää vaarattomalta, mutta sisältää itse asiassa mielenkiintoisia juttuja:
Omat tyypit ja operaatiot:
Scalassa BigInt on kirjastossa. Vastaa Javan BigInteger-luokkaa (itse asiassa "wräppää" tuon luokan. Kyseessä siis on kooltaan rajoittamaton kokonaislukutyyppi. Mutta ero löytyy helppokäyttöisyydessä.
Scalalla voi ohjelmoida seuraavasti:
def factorial(x: BigInt): BigInt = if (x == 0) 1 else x * factorial(x - 1) println(factorial(30)) // 265252859812191058636308480000000Toki Javan luokkaakin voi käyttää, mutta, mutta:
import java.math.BigInteger def factorial(x: BigInteger): BigInteger = if (x == BigInteger.ZERO) BigInteger.ONE else x.multiply(factorial(x.subtract(BigInteger.ONE))) println(factorial(new BigInteger("30"))) // 265252859812191058636308480000000
Luokka BigInt nyt vaan sattuu olemaan Scalan standardikirjastossa. Ja sille nyt vaan sattuu olevan ohjelmoitu mm. metodit +, -, *, ... Ja toki metodikutsun saa kirjoittaa esim. Javasta tuttuun tapaan:
def factorial(x: BigInt): BigInt = if (x == 0) 1 else x.*(factorial(x - 1))Myös molemmat:
def factorial(x: BigInt): BigInt = if (x == 0) 1 else x.*(factorial(x.-(1)))Ja toki yhtäsuuruusvertailun voi tehdä toisinkin
def factorial(x: BigInt): BigInt = if (x.equals(0)) 1 else x.*(factorial(x.-(1)))Vielä kerran: BigInt siis ei ole kieleen sisäänrakennettu tyyppi vaan kirjastoon ohjelmoitu tyyppi. Vastaavia voi siis tehdä myös itse!
Kontrollirakenteetkin voivat olla kirjastorutiineita:
Esimerkkinä Scalan kirjastosta ns. aktorimalli rinnakkaisohjelmointiin. Aktori osaa lähettää ja vastaanottaa viestejä muiden aktoreiden kanssa. Se ei jaa yhteistä muistia muiden aktoreiden kanssa. Viesti lähetetään !-operaatiolla:
vastaanottaja ! viestiOperaation jälkeen viestin lähettäjän toiminta jatkuu välittömästi, so. ei odotella kuittausta.
Jokaisella aktorilla on postilaatikko, johon aktorin saamat viestit tulevat jonottamaan. Postilaatikon viestit vastaanotetaan receive-operaatiolla:
receive { case Msg1 => ... // handle Msg1 case Msg2 => ... // handle Msg2 // ... }receive-lohko sisältää "message pattern" -ehtoja. Postilaatikosta valitaan vanhin sellainen viesti, johon täsmää jokin lohkon ehto, ja tuohon ehtoon liittyvä operaatio suoritetaan. Jos laatikko on tyhjä tai jos mikään laatikon viesteistä ei täsmää yhteenkään ehtoon, odotellaan...
Aktoreita kirjoitellaan tyyliin:
actor { var sum = 0 loop { receive { case Data(bytes) => sum += hash(bytes) case GetSum(requester) => requester ! sum } } }Oleellista tässä on, ettei yksikään nimistä actor, loop, receive, tai operaatio "!" ole Scala-kieleen sisäänrakennettu. Ne kaikki ovat (taas kerran!) kirjastoon ohjelmoituja välineitä. (Esim. Erlang-kielessä - josta aktorit ovat kotoisin - ne ovat varattuja sanoja ja sisäänrakennettuja operaatioita.)
Ja siis taas kerran: Tällaisia siis voi tehdä myös itse.