Helsingin yliopisto / tietojenkäsittelytieteen laitos / Ohjelmointitekniikka (Scala) / © Arto Wikla 2016

1 Johdantoa ja luonnehdintaa: sipulin uloin kerros

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öä:

Taustaa ja ominaisuuksia

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.

Pikku ohjelman laatiminen on vaivatonta

Scalassa kirjoitusvaivaa ja erilaisia ainakin aloittelijalle käsittämättömiä "loitsuja" – "boilerplate-koodia" – on paljon vähemmän kuin Javassa. Jos muuttujan tyyppi selviää käyttöyhteydestä, tyyppiä ei tarvitse mainita. Kääntäjä ei vaadi puolipistettä, jos se ilmankin osaa päätellä ilmaisujen rajat. Monessa tilanteessa hyvin lyhyt ilmaisu on mahdollinen. Jne., jne, ...

Esimerkki: Ohjelma laskee syöttölukujen keskiarvon. Lukumäärä pyydetään.

Scala-ohjelman suorittamisen tapoja

  1. Scalaa voidaan suoraan sellaisenaan käyttää "laskimena", antaa ohjelmarivejä suoraan tulkille (komento scala) (käyttäjä kirjoittaa kehoitteen perään - mahdollisesti useita rivejä, jos rakenne on monirivinen - Scala vastaa ilman rivin aloitinmerkkiä; nuo "res n":t ovat arvoille generoituja tunnuksia). Tässä yksi istunto:
    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()
    
  2. Kielen ilmauksia voidaan sellaisinaan kirjoittaa teksitiedostoksi, joka suoritetaan ilmauksella scala tiedostonimi. Tällä tavoin käytettynä Scalaa voitaisiin kutsua "skriptikieleksi". Esimerkkinä tiedosto tervehdi.ohjelma:
    import scala.io.StdIn._
    println("Montako tervehdystä?")
    var lkm = readInt
    while (lkm>0) {
      println("Hoi maailma!")
      lkm = lkm-1
    }
    
    Suoritus komennolla
    scala tervehdi.ohjelma

    Komento ensin kääntää Scala-ohjelman Javan tavukoodiksi ja sitten suorittaa tuon koodin Javan virtuaalikoneella.

  3. Scala-ohjelma voidaan kääntää Javan tavukoodiksi Java-virtuaalikoneella suoritettavaksi. Nyt tarvitaan pääohjelma, jotta virtuaalikone tietäisi, ketä kutsua. Scalassa ei ole "static"-kalustoa; sellaisen vastineena käytetään ns. singleton-olioita (suomeksi "ainokaisia"). Syntaksi on yksikertainen ja luonteva. Äskeinen ohjelma on muokattava muotoon
    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
    scalac tervehdi.scala
    ja myös suoritetaan Javan tapaan komennolla
    scala tervehdi

    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 ...

  4. Scalan "skripti" voidaan helposti muuttaa myös Linuxin tai Windowsin komentotulkille annettavaksi skriptiksi. Edellä nähty esimerkki voidaan muokata Linuxin skriptiksi:
    #!/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... ;-)

Quicksort kahdella tyylillä

  1. Pikajärjestäminen voidaan ohjelmoida tavalliseen algoritmiseen (imperatiiviiseen eli "proseduraaliseen") tapaan:
    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.

  2. Myös funktionaalinen tyyli Scalalla sujuu:
    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...

Esimerkkiominaisuuksia

Kirjan luvusta 1: A Scalable Language

Oppikirjan esimerkejä luvusta 1 kommentoituna ja laajennettuna:

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!