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)) // 265252859812191058636308480000000
Toki 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.