Viimeksi päivitetty 22.2.2016 / 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: 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");
}
}
}
import scala.io.StdIn._
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.
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 neliosumma(a:Int, b:Int)=
| a*a+b*b
scala> neliosumma: (a: Int, b: Int)Int
scala> neliosumma(3,4)
res3: Int = 25
scala> neliosumma(b=2, a=4) // !!
res4: Int = 20
scala> sys.exit()
import scala.io.StdIn._
println("Montako tervehdystä?")
var lkm = readInt
while (lkm>0) {
println("Hoi maailma!")
lkm = lkm-1
}
Suoritus komennolla
Komento ensin kääntää Scala-ohjelman Javan tavukoodiksi ja sitten suorittaa tuon koodin Javan virtuaalikoneella.
import scala.io.StdIn._
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
Vieläkin vähemmällä vaivalla voi päästä "miksaamalla" olioon tervehdi "traitti" eli liittämällä siihen piirretyyppi (trait) nimeltään App:
import scala.io.StdIn._
object tervehdi extends App {
println("Montako tervehdystä?")
var lkm = readInt
while (lkm>0) {
println("Hoi maailma!")
lkm = lkm-1
}
}
App-piirretyyppi tuo olioon pääohjelman.
Piirretyypeistä lisää aikanaan ... ja paljon ...
#!/usr/bin/scala
!#
import scala.io.StdIn._
println("Montako tervehdystä?")
var lkm = readInt
while (lkm>0) {
println("Hoi maailma!")
lkm = lkm-1
}
Suoritusoikeus tiedostolle on luonnollisesti annettava.
Ohjelman voi nyt käynnistää pelkällä tiedostonimellä,
jonka eteen kirjoitetaan "./".
(Hakupolkuasetuksilla tuosta etuliitteestäkin voi
päästä eroon, mutta ei ns. kuulu kurssiin... ;-)
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.
C-pohjaisissa kielissä - Java mukaan lukien (!) - aliohjelmarakenne on
"litteä": niissä aliohjelma ei voi sisältää omia
"pikku apulaisiaan" Algolin, Pascalin ja monien modernien kielten tapaan!
Taulukon indeksoinnissa Scalassa käytetään
kaarisulkeita tutumpien hakasulkeiden síjaan.
import scala.language.postfixOps
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 a'la Java"
println(e)
a.foreach(println) // "oikea" foreach
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" a'la Java
println(i)
a.foreach(println) // "oikea" foreach
// 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 PosPariton (private var luku: Int) {
// pääkonstruktorin parametreista tulee kenttiä!
// --- "pääkonstruktorin algoritmi" ---
aseta(luku)
// -- apukonstruktori ---
def this() {this(1)} // parametrittomana oletusarvo on 1
// --- ottavat aksessori eli getteri: ---
def arvo() = luku
// --- asettavat aksessorit eli setterit: ---
def aseta(a: Int) {
luku = a
if (luku<0) luku = -luku
if (luku%2==0) luku += 1
}
def +=(a: Int) = { // += kelpaa mainiosti aksessorin tunnukseksi!
aseta(luku+a)
}
// korvataan peritty toString
override def toString = "Positiivinen pariton on nyt " + luku
}
// Kokeillaan:
val x = new PosPariton(8) // huom: x:ää ei voi muuttaa, mutta oliota voi
println(x) // Positiivinen pariton on nyt 9
x.aseta(-200)
println(x.arvo()) // 201
var y = new PosPariton()
println(y) // Positiivinen pariton on nyt 1
val z = new PosPariton(3)
z.+=(3)
println(z) // Positiivinen pariton on nyt 7
z += 7 // edes piste ja sulut eivät ole aksessoinnissa tarpeen!
println(z) // Positiivinen pariton on nyt 15
z += -16
println(z) // Positiivinen pariton on nyt 1
Huomioita:
z.+=(3) // nämä tarkoittavat samaa! z += 3
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 on 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!
Myös kontrollirakenteita voi ohjelmoida itse, ja esimerkiksi for-lause on itse asiassa kirjastofunktio!