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

5 Perustyyppejä ja operaatioita

Muutettu viimeksi 23.3.2010 / Sivu luotu 18.3.2010 / [oppikirjan esimerkit] / [Scala]

Sivun sisältöä:

Numeeriset tyypit

Scalan numeeriset tyypit Byte, Short, Int, Long, Char, Float ja Double vastaavat arvoalueiltaan, literaalivakioesityksiltään, ym. Javan pienellä kirjoitettuja alkeistyyppejä. Jos asiaa haluaa kerrata, peruskurssien materiaalin luvusta 3.2 Alkeistyyppejä ja literaalivakioita löytyy luettavaa.

Toistaiseksi myös Javan alkeistyyppien nimet ovat Scala-ohjelmassa käytettävissä -- tosin nykyään saa jo vanhenemisvaroituksen. Myös nämä Javalla kirjoitetut tyypit kuitenkin tarkoittavat Scalan omia numeerisia tyyppejä, jotka löytyvät pakkauksesta scala. Tämä pakkaus näkyy oletusarvoisesti käännösyksikköön

Scalan numeeriset tyypit on Bytecode-tasolla itse asiassa toteutettu Javan alkeistyyppeinä, joten ohjelmien suortiustehokkuuden ei pitäisi juuri vähetä!

Scalassa numeeristen tyyppien operaatiovalikoimaa on rikastettu, ks. alla.

String-tyyppi

String-tyyppinä käytetään sellaisenaan Javan tyyppiä java.lang.String. Koko pakkaus java.lang näkyy oletusarvoisesti Scala-käännösyksikköön.

String-literaalivakioihin on tehty kirjoitusvaivoja säästävä lisäys: kolmikertaisin lainausmerkein voi kirjoittaa "raakatekstiä":

println("""Näin voi kirjoittaa tekstiä, joka sisältää vaikkapa
           rivinvaihtoja, "- ja \-merkkejä sellaisinaan

              tyhjiä rivejä yms, yms.
           vain kolminkertaista lainausmerkkiä ei voi olla...""")
Tulostus:
Näin voi kirjoittaa tekstiä, joka sisältää vaikkapa
           rivinvaihtoja, "- ja \-merkkejä sellaisinaan
           
              tyhjiä rivejä yms, yms.
           vain kolminkertaista lainausmerkkiä ei voi olla...
On ehkä tylsää saada nuo rivin alkujen välilyöntijonot "raakoina" mukaan. Apu löytyy pystyviivasta ja stripMargin-metodista:
println("""|Näin voi kirjoittaa tekstiä, joka sisältää vaikkapa
           |rivinvaihtoja, "- ja \-merkkejä sellaisinaan
           |
           |   tyhjiä rivejä yms, yms.
           |vain kolminkertaista lainausmerkkiä ei voi olla...""".stripMargin)
Tulostus:
Näin voi kirjoittaa tekstiä, joka sisältää vaikkapa
rivinvaihtoja, "- ja \-merkkejä sellaisinaan

   tyhjiä rivejä yms, yms.
vain kolminkertaista lainausmerkkiä ei voi olla...
Huom: Javasta poiketen String-olioiden sisältöjen yhtäsuuruutta voi verrata ==-operaatiolla!

Symboliliteraalit

Symboliliteraalin syntaksi on heittomerkillä alkava muuten aakkosnumeerinen tunnus. Siis vaikkapa 'tunnus. Ilmaus on lyhenne factory-metodin kutsulle Symbol("tunnus"), joka palauttaa arvonaan Symbol-olion.

Symbolit ovat, ..., hmm..., ..., no symbolit ovat symboleja:

def sanoJotain(kuka:Symbol) {
  if      (kuka=='kissa) println("Miau")
  else if (kuka=='koira) println("Hauhau")
  else                   println("Ammuu")
}

sanoJotain('possu) // Ammuu
sanoJotain('kissa) // Miau
sanoJotain('koira) // Hauhau
Juuri mitään muuta kuin samuuden kyselyä symboleille ei voi tehdä. Mutta tämähän on oikeastaan aika näppärä tapa toteuttaa erilaisia "ohjauskoodeja". Ei ainakaan tavitse itse koodata asioiden nimiä kokonaisluvuiksi. Puhumattakaan että joutuisi jotain Javan kamalaa "enumerated"-tyyppiä käyttämään.

"Operaattori == metodi"

Kuten jo on nähty, Scalassa metodit -- eli oliolle sovellettavat aksessorit -- ovat operaattoreita. Ja myös toisin päin: operaattorit ovat oliolle sovellettavia aksessoreita. Esimerkiksi:
println(1+2)        // 3
println((1).+(2))   // 3

// mutta huom:
println(1.+(2))     // 3.0 (tarkoittaa siis "double 1. ynnä int 2" !)

println("kukkapenkki".indexOf('p'))  // 5
println("kukkapenkki" indexOf 'p' )  // 5
Esimerkiksi Int-luokka sisältää useita erilaisia kuormitettuja versioita +-metodista: Vaikkapa kutsu 1+2L eli (1).+(2L) käynnistää sellaisen Int-luokassa määritellyn +-metodin, jonka parametri on tyyppiä Long.

Nähdyt esimerkit olivat infix-operaatioita: "operandi-operaatio-operandi". Muitakin on:

// näin kaksiparametrinen "infixataan":
println("kukkapenkki".indexOf('k', 4))   // 8
println("kukkapenkki" indexOf ('k', 4))  // 8
Prefix ja postfix:
// prefix:
println(-7)         // -7
println(7.unary_-)  // -7

//postfix:
println("HUUDETAAN".toLowerCase) // huudetaan
println("HUUDETAAN" toLowerCase) // huudetaan
Huom: Ainoastaan merkkejä +, - ja ~ voi käyttää unaarisina prefix-operaatioina! Ne ohjelmoidaan metodeina .unary_+, .unary_- .unary_~.

Olioiden samuus

Java-ohjelmoijat ovat kovassa koulussa oppineet ja sisäistäneet, että ==- ja != vertailuilla on yleensä järkevää verrata vain alkeistyypin arvoja ja että olioiden vertailu noilla johtaa vain vaikeasti löydettäviin ohjelmointivirheisiin. Ja siis omiin luokkiin ohjelmoidaan ikiomat equals-metodit, jotka polymorfismi taitavasti käy aina noutamassa, kun olioarvojen samuutta kysellään milloin missäkin.

Asiat ovat lopulta aika samoin Scalassa. Tosin kaikki arvot ovat olioita ja ==- ja !=-operaatiot testaavat equals-samuutta! Ne suorastaan itse asiassa kutsuvat equals-metodia. Siispä:

println(("he"+"llo") == "hello")  // true
Scalassa on toki myös oliosamuustestit, jotka ovat nimeltään eq ja ne. Niistä ehkä myöhemmin.

Operaattoripresedenssi

Hieno sana "operaattoripresedenssi" tarkoittaa operaattoreiden sitovuutta; siis vaikka sitä jo alakoulusta tuttua juttua että kertolaskut lasketaan ennen yhteenlaskuja, ellei ole sulkeita. Muuten nimittäin opettaja antaa kielteistä palautetta...

Scalassa asia on sikäli kiinostava, että ohjelmoija voi sydämensä kyllyydestä ohjelmoida ikiomia operaattoreitaan.

Ideana on sopia asiat tyyliin: kaikki kertomerkillä (*) alkavat operaatiot (eli metodit) sitovat tiukemmin kuin mikään yhteenlaskumerkillä (+) alkava operaatio (eli metodi). Ja vain ensimmäinen merkki merkitsee. Siis vaikkapa kaikki *-merkillä alkavat operaatiot sitovat yhtä tiukasti eli samalla tiukkuudella.

Sitovuus ensimmäisen merkin perusteella tiukimmasta löysimpään:

muut kuin alla luetellut erikoismerkit
* / %
+ -
:
= !
< >
&
^
|
kirjaimet
kaikki sijoitusoperaatiot
Poikkeus: Kaikki =-merkkiin päättyvät sijoitusoperaatiot siis kuuluvat heikoimmin sitovaan luokkaan riippumatta niiden ensimmäisestä merkistä! Esimerkiksi vaikka ilmauksessa x *= y + 1 ensimmäinen operaatio alkaa kertomerkillä, "kertova sijoitus" sitoo silti löysemmin kuin yhteenlasku.

Huom: Kuten aiemmin jo todettiin, :-merkkiin päättyvät operaatiot assosioituvat oikealta vasemmalle. Kaikki muut operaatiot assosioituvat tuttuun tapaan vasemmalta oikealle.

Rikastettuja operaatioita

Scalan perustyyppien operaatiovalikoimaa on täydennetty implisiittisen tyyppimuunnostekniikan avulla. Tekniikasta lisää myöhemmin, mutta tässä muutamia esimerkkejä lisätyistä operaatioista:
0 max 5			5
0 min 5 		0
-2.7 abs 		2.7
-2.7 round 		3L
1.5 isInfinity 		false
(1.0 / 0) isInfinity 	true
4 to 6 			Range(4, 5, 6)
"bob" capitalize 	"Bob"
"robert" drop 2 	"bert"
...
Rikastuksia löytyy mm. luokista RichInt, RichChar, RichString, RichDouble RichBoolean. Katso Scala-API.