Tämä kurssi on jo päättynyt.

Kurssin viimeisimmän version löydät täältä: O1: 2024

Luku 1.4: Arvojen tallentaminen muuttujiin

Tästä sivusta:

Pääkysymyksiä: Miten pääsen kätevästi käsiksi ohjelmassa tarvitsemiini arvoihin? Miten voin tallentaa tietoa tietokoneen muistiin?

Mitä käsitellään? Muuttujat (val ja var). Ensisilmäys laatuseikkoihin ohjelmoinnissa. Samalla opitaan myös vähän lisää edellisen luvun aiheista.

Mitä tehdään? Ohjelmoidaan REPLissä ja luetaan.

Suuntaa antava työläysarvio:? Tunti tai vähän yli.

Pistearvo: A35.

Oheisprojektit: Ei ole.

../_images/sound_icon.png

Muuta: Eräissä tämän luvun kohdissa on kaiuttimista tai kuulokkeista hyötyä. Aivan pakolliset ne eivät ole.

../_images/person01.png

Johdanto: välitulosongelma

Miten käyttäisit edellisestä luvusta tuttuja aritmeettisia lausekkeita laskeaksesi Scalalla seuraavat?

  • kuutosen kuution, siis 63
  • kuutosen kertoman, siis 6!
  • kuutosen kertoman kuution, siis 6!3

Eräs ratkaisu REPLissä on annettu alla.

6 * 6 * 6res0: Int = 216
1 * 2 * 3 * 4 * 5 * 6res1: Int = 720

Kaksi ensimmäistä — kuutosen kuutio ja kuutosen kertoma — menivät ihan siedettävästi näinkin. Entä kolmas?

Aargh:

1 * 2 * 3 * 4 * 5 * 6 * 1 * 2 * 3 * 4 * 5 * 6 * 1 * 2 * 3 * 4 * 5 * 6res2: Int = 373248000

Tämä koodinpätkä on jo aika epämiellyttävä kirjoittaa ja lukea. Lisäksi kone joutuu nyt tekemään turhankin monta kertolaskua, kun se suorittaa käskyn (millä ei tosin tässä ole käytännön merkitystä).

Olisi parempi, jos voisi kirjoittaa: "Laske kertoma, ota välitulos talteen ja laske sen kuutio." Ja niin voikin.

Muuttujat

Lähes kaikissa ohjelmissa on tarvetta arvojen tallentamiselle tietokoneen muistiin. Näin voidaan pitää kirjaa ohjelman kannalta oleellisista asioista, kuten välituloksista tai vaikkapa GoodStuff-ohjelman tapauksessa käyttäjän kokemusten saamista arvosanoista ja hinnoista.

Tietysti tallennettuihin arvoihin pitäisi päästä myös jotenkin käsiksi. Parhaiten tämä onnistuu käyttämällä nimiä, joilla arvoihin voi viitata.

Arvojen tallentamiseen käytetään ohjelmoinnissa muuttujia (variable). Muuttuja on nimetty varastointipaikka yhdelle arvolle. Käskyä, jolla arvo tallennetaan muuttujaan, sanotaan sijoitukseksi (assignment).

Välituloksen sijoittaminen muuttujaan

Tässä parempi tapa ratkaista "kuutosen kertoman kuutio" -ongelma.

Määritellään ensin muuttuja, johon otetaan välitulos talteen. Se onnistuu tällaisella käskyllä. Muista taas, että voit siirtää hiiren kursorin vihreiden laatikkojen ylle nähdäksesi, mihin selitykset liittyvät.

val kertoma = 1 * 2 * 3 * 4 * 5 * 6
Käytetään avainsanaa val (lyhennetty englannin ilmaisusta value variable), jonka perään kirjoitetaan...
... ohjelmoijan valitsema muuttujan nimi, tässä kertoma, jonka perään tulee yhtäsuuruusmerkki...
... ja viimeisenä lauseke, jonka evaluointi tuottaa muuttujaan sijoitettavan arvon.

REPL vastaa muuttujan määrittelyyn näin:

val kertoma = 1 * 2 * 3 * 4 * 5 * 6kertoma: Int = 720
REPL kuittaa onnistuneen muuttujamäärittelyn laittamalla näkyviin määritellyn muuttujan nimen (automaattisesti numeroidun resX:n sijaan) sekä...
... muuttujan tietotyypin ja muuttujaan tallennetun arvon. Scalassa sekä muuttujilla että arvoilla on tietotyypit, ja muuttujan tietotyypin on oltava yhteensopiva muuttujaan tallennetun arvon tietotyypin kanssa.

Nyt kertoman kuution voi laskea muuttujan avulla:

kertoma * kertoma * kertomares3: Int = 373248000

Huomaa, että muuttujan nimi yksinäänkin toimii lausekkeena, jonka arvo on kyseiseen muuttujaan tallennettu arvo. Sitä, kuten muitakin lausekkeita, voi käyttää suurempien lausekkeen muodostamisessa. Esimerkiksi tässä muuttujan nimen muodostamaa lauseketta on käytetty (kolmeenkin kertaan) osalausekkeena aritmeettisessa lausekkeessa.

Muuttujaan sijoittamisen vaiheet

Alla on vielä animaatio yllä kuvailtujen koodirivien suorituksesta. Katso se, vaikka olisitkin jo mielestäsi tajunnut yllä olevan esimerkin! Kiinnitä erityistä huomiota järjestykseen, jossa suoritus etenee vaiheittain. Näiden vaiheiden järjestys on keskeinen, kun käsittelemämme ohjelmat mutkistuvat.

Arvioi äskeisen animaation perusteella, mitkä kaikki seuraavista väittämistä pitävät paikkansa, ja valitse ne.

Varo tutunlaista matikkaa!

Ohjelmakoodi joiltain osin muistuttaa tuttuja matemaattisia merkintätapoja. Aivan samasta asiasta ei kuitenkaan ole kyse, ja samankaltaisuus on harhauttanut monta ohjelmoinnin aloittelijaa.

Esimerkki tästä on sijoituskäskyssä. Se ei ole yhtälö, jossa yhtäsuuruusmerkin molemmat puolet ovat samanarvoisessa asemassa. Sijoituskäsky ohjeistaa tietokonetta sijoittamaan oikealla olevan lausekkeen arvon vasemmalla puolella nimettyyn muistipaikkaan. Esimerkiksi käsky-yritelmä 1 * 2 * 3 * 4 * 5 * 6 = val kertoma ei siis toimi lainkaan vaan tuottaa virheilmoituksen.

Koodin laatu parani

Tässä kaksi edellä laatimaamme ohjelmaa:

1 * 2 * 3 * 4 * 5 * 6 * 1 * 2 * 3 * 4 * 5 * 6 * 1 * 2 * 3 * 4 * 5 * 6res4: Int = 373248000
val kertoma = 1 * 2 * 3 * 4 * 5 * 6
kertoma * kertoma * kertomares5: Int = 373248000

Uusi muuttujaa käyttävä versiomme ohjelmakoodista ratkaisee aivan saman ongelman kuin alkuperäinen yksirivinen ratkaisukin. Kuitenkin uusi koodimme on ihmiselle selkeämpi lukea. Toinen parannus on (periaatteessa) se, että kone joutuu laskemaan hieman vähemmän kuin edellisessä versiossa. Tämä oli siis ensisilmäys kahteen ohjelman laadulliseen arviointikriteeriin: tyyliin ja suoritustehokkuuteen.

Parannetussa koodissamme voi ainakin periaatteessa nähdä jo kolmannenkin laatuparannuksen: vähensimme saman ilmaisun toistoa eriyttämällä kertoman laskemisen omaksi toimenpiteekseen. Toiston vähentäminen helpottaa ohjelman jatkokehittämistä ja muuntamista. Esimerkiksi jos haluaisit muokata ohjelman laskemaan vaikkapa luvun seitsemän kertoman kuution, niin muutos on helpompi tehdä muuttujaan perustuvaan toiseen ratkaisuumme kuin alkuperäiseen versioon: sinun ei tarvitse muuttaa kuin yhtä kohtaa koodista eikä kolmea. Tämä ei ainoastaan vähennä vaivaa vaan myös huolimattomuusvirheiden mahdollisuutta. Tässä erittäin pienessä ohjelmassamme tämäkin parannus on toki käytännön merkitykseltään vähäinen.

Koodin toisteisuuden välttämisen periaate kulkee nimellä DRY (sanoista don’t repeat yourself); jotkut käyttävät periaatteen rikkomisesta ilmaisua WETWET (write everything twice write everything twice). Isommissa ohjelmissa DRY-periaatetta on tärkeä noudattaa, ja siihen palaamme kurssilla myöhemmin. Tässä vaiheessa riittää saada muhimaan tausta-ajatus siitä, että ohjelmoijan kuuluu arvioida pelkän toimivuuden lisäksi myös laatuseikkoja.

Muuttujien nimet lausekkeina ja lausekkeissa

Muuttujaan sijoitettavan arvon voi määritellä erilaisilla lausekkeilla. Yksinkertaisimmat lausekkeet ovat literaaleja, ja myös literaalin arvon voi sijoittaa muuttujaan:

val kokeilu = 100kokeilu: Int = 100

Tutustu muuttujien käyttöön ohjelmoimalla REPLissä ja vastaa oheisiin helppoihin kysymyksiin.

Muuttujaa voi käyttää toisen muuttujan alustamiseen samoin kuin missä vain muussa lausekkeessa. Luo ensin kokeilu-muuttuja kuten yllä ja suorita sitten tämä käsky:

val toinen = 1 + kokeilu

Mikä on nyt toinen-muuttujan arvo?

Muuttujan nimi yksinäänkin on lauseke:

toinen

Mikä on tämän lausekkeen arvo? (Oletetaan, että muuttuja on alustettu kuten edellä.)

Muuttujan sisältämän arvon voi kopioida toiseen:

val kolmas = toinen

Huomaa tässäkin tapauksessa, että sijoitus tapahtuu aina "yhtäsuuruusmerkin oikealta puolelta vasemmalle": oikeanpuoleisena mainitun muuttujan on oltava ennestään olemassa (tai tulee virheilmoitus); vasemmanpuoleinen val-sanan perässä mainittu muuttuja luodaan tällä käskyllä.

Mikä arvo on yllä olevan käskyn suorittamisen jälkeen muuttujassa kolmas?

Muuttujia voi käyttää myös silloin, kun määritellään parametreja käskyille. Tässä esimerkki tulostuskäskystä, jonka parametriarvon määräävässä lausekkeessa on käytetty kahta muuttujaa:

println(kokeilu - kolmas)

Minkä arvon tämä käsky tulostaa?

Tarkastellaan seuraavan käskyn antamista REPLissä:

val joku = kolmas * (kokeilu + 10)

Mitkä kaikki seuraavista väittämistä pitävät paikkansa?

Erityyppisiä muuttujia

Kaikki yllä käytetyt muuttujat olivat Int-tyyppisiä, ja niillä oli arvoinaan kokonaislukuja. Voimme määritellä myös muuntyyppisiä muuttujia, mikä onnistuu yksinkertaisimmin sijoittamalla muuttujaan toisentyyppinen arvo, vaikkapa Double-tyyppinen luku:

val arvosana = 9.5arvosana: Double = 9.5

Tai merkkijono:

val nimi = "Satu"nimi: String = Satu
val nooaAlku = "cccedddfeeddc---"nooaAlku: String = cccedddfeeddc---

Tai väri tai kuva, kuten seuraavassa tehtävässä.

Määritellään REPLissä pari muuttujaa ja annetaan niille jotkin arvot:

import o1._import o1._
val ympyranKoko = 300ympyranKoko: Int = 300
val ympyranVari = BlueympyranVari: o1.gui.Color = Blue

Koetetaan näyttää ympyrä, jolla on noiden muuttujien mukainen halkaisija ja väri:

show(ympyranKuva)<console>:18: error: not found: value ympyranKuva
      show(ympyranKuva)
           ^

Jotain unohtui välistä! Virheilmoitus kertoo, että muuttujaa ympyranKuva, jota yritimme käyttää, ei ole määritelty. Eikä olekaan.

Kirjoita alle käsky, joka määrittelee muuttujan ympyranKuva siten, että äskeinen show-käsky voidaan antaa ja tuo esiin ympyrän. Käytä apuna luvussa 1.3 kohdattua circle-käskyä sekä jo määriteltyjä muuttujia ympyranKoko ja ympyranVari. (Älä siis syötä alle tuota show-käskyä vaan vain muuttujan määrittely.)

Muuttujien nimeämisestä

Ohjelmoija valitsee muuttujien nimet eli virallisemmin sanoen niiden tunnukset (identifier). Kuten edeltä on käynyt ilmi, Scala-kieltä käytettäessä muuttujien nimet aloitetaan pääsääntöisesti pienellä kirjaimella. Mitään teknistä pakotetta pienen alkukirjaimen käyttöön ei ole, mutta tämän tavan noudattaminen kuuluu hyvään Scala-ohjelmointityyliin.

../_images/camelCase.png

Jos muuttujan nimessä on useita sanoja, jäljempien sanojen aluissa on tapana käyttää isoja kirjaimia. Tässä esimerkkejä:

  • munHienoMuuttuja
  • numberOfPlayers
  • xCoordinate

Tämä Scalassa käytetty nimeämistapa, joka on ohjelmoinnissa melko yleinen muttei mitenkään ainoa, kulkee nimellä camelCase.

Älä käytä muuttujien nimissä välilyöntejä. Numeroita saa käyttää, mutta nimi ei voi alkaa numerolla. Erikoismerkkejä (+, & jne.) on aloittelijan syytä välttää nimissä kokonaan; joillakin merkeillä on myös erityismerkityksiä Scalassa. Skandinaavisia "ääkkösiä" ja eksoottisempia kirjoitusmerkkejä voi periaatteessa käyttää, mutta koska ne voivat joidenkin ohjelmoijan aputyökalujen kanssa tuottaa ongelmia, niin saattaa olla parempi välttää niitä. Scala-kielen "taikasanoja" kuten val, virallisemmin varattuja sanoja (reserved words), ei voi käyttää muuttujien niminä.

Isot ja pienet kirjaimet lasketaan nimissä eri kirjaimiksi. Kun siis teet muuttujan omaKokeilu, niin ole tarkkana, että kirjoitat nimen aina samassa muodossa. Nimi omakokeilu ei toimi tuon muuttujan käyttämiseen.

Kurssimateriaalin pienimmissä esimerkeissä on muuttujat ja muut ohjelman osat useimmiten nimetty suomeksi, jotta korostuisi, mitkä sanat ovat ohjelmoijan itse valitsemia ja mitkä ovat osa (englannista sanoja lainaavaa) Scala-ohjelmointikieltä. Kurssin isommissa oheisprojekteissa taas on pääsääntöisesti käytetty englantia. Itse voit tehdä tältä osin niin kuin parhaaksi näet. Yritysten ohjelmistohankkeissa yleensä käytetään englantia, ja monet suomenkielisetkin ohjelmoijat käyttävät englanninkielisiä nimiä myös henkilökohtaisissa projekteissaan.

Hyvään ohjelmointityyliin kuuluu muuttujien nimeäminen niiden käyttötarkoitusta kuvaavasti. Tästä näet paljon esimerkkejä jatkossa. Pikkuisissa kokeilukoodinpätkissä toki voi käyttää yleisluontoisempia ja lyhyempiä nimiä kuten luku, a tai vaikka kokeilu.

Nimeämistä käsitellään myös kurssin tyylioppaassa, johon kannattaa tutustua jossain vaiheessa kurssin alkupuolella, ei kuitenkaan välttämättä vielä tässä ihan alussa.

Muuttujia ja merkkijonoja

Lisää merkkijonojen yhdistelystä

Jos yhdistät merkkijonon plus-operaattorilla lukuun, tuloksena on merkkijono, jossa ovat mukana lukuarvoa kuvaavat merkit. Esimerkiksi näin:

"kuor" + 100res6: String = kuor100
val raportti = "arvosana: " + arvosanaraportti: String = arvosana: 9.5
println(nimi + ", " + raportti)Satu, arvosana: 9.5
Tulos on siis nimenomaan merkkijono, johon ...
... luvut sisältyvät kirjoitusmerkkeinä, eivät lukuarvoina.

Vauhtia musiikkiin

Käytetään taas luvun 1.3 esittelemää o1.play-käskyä. Muodostetaan ensin hieman pidempi melodia muuttujia apuna käyttäen. Ukko Nooassa alku toistuu uudestaan kappaleen lopussa, mikä järjestyy esimerkiksi näin:

import o1._import o1._
val nooaAlku = "cccedddfeeddc---"nooaAlku: String = cccedddfeeddc---
val valiosa = "eeeeg-f-ddddf-e-"valiosa: String = eeeeg-f-ddddf-e-
val kokoNooa = nooaAlku + valiosa + nooaAlkukokoNooa: String = cccedddfeeddc---eeeeg-f-ddddf-e-cccedddfeeddc---
play(kokoNooa)

Kokeillaan nyt kappaleemme soittamista kahdella eri tempolla. Tallennetaan ensin käyttämämme tempot muuttujiin, joilla on kuvaavat nimet:

val normaaliTempo = 120normaaliTempo: Int = 120
val hidasTempo = 60hidasTempo: Int = 60

o1.play pystyy soittamaan melodioita eri nopeuksilla. Tämän merkiksi sille on annettava merkkijono, jossa on nuotteja kuvaavien kirjaimien perässä kauttaviiva ja edelleen sen perässä haluttua tempoa kuvaavat numeromerkit. Esimerkiksi tällainen:

kokoNooa + "/" + normaaliTempores7: String = cccedddfeeddc---eeeeg-f-ddddf-e-cccedddfeeddc---/120

Juuri tuollaista kauttaviivallista merkkijonoa o1.play osaa käsitellä. Tässä pari esimerkkiä kokeiltavaksi:

play(kokoNooa + "/" + normaaliTempo)play(kokoNooa + "/" + hidasTempo)
120 sattuu olemaan juuri o1.play-käskyn oletustempo, joten ensimmäinen soittokerta kuulostaa ihan samalta kuin aiemmatkin.
Kun käytetään tempoa 60, Nooan saunaretki sujuu rollaattori-ikäiselle sopivampaan tahtiin.

Jos muuttujille ei ole muuta tarvetta, niin toki on mahdollista kirjata koko merkkijono tempoineen kerrallakin kuten alla on tehty.

play("cdefg/140")

Soitetaanpa Ukko Nooa oikein ripeästi, vaikkapa normaaliin nähden kaksinkertaisella tempolla. Pistetään kone laskemaan, paljonko kaksinkertainen nopeus on:

play(kokoNooa + "/" + normaaliTempo + normaaliTempo)

Mutta eihän se toimi! Selvitä, mistä kiikastaa, ja valitse seuraavista kaikki paikkansa pitävät väitteet. Ratkaisu kaikkiin kohtiin löytyy, kun kokeilet itse REPLissä ja kiinnität huomiota sekä arvoihin että tietotyyppeihin. Voit esimerkiksi tulostaa merkkijonon sen soittamisen sijaan.

Muuttujan arvon vaihtaminen

Ohjelmissa on usein tilanteita, joissa jokin tieto muuttuu. Esimerkiksi pelissä pelihahmon sijaintikoordinaatti voi muuttua hahmon liikkuessa, ja GoodStuff-sovelluksessamme käyttäjän suosikkihotelli voi vaihtua toiseen, kun hän lisää tietoja sovellukseen.

Yksi tapa vastata tällaisiin tarpeisiin ohjelmissa on pitää kirjaa vaihtuvasta tiedosta muuttujassa ja vaihtaa muuttujan arvo toiseen tilanteen niin vaatiessa.

Kuulostaa aika hyvältä, mutta tämäpä ei val-sanaa käyttäen määritellyillä muuttujilla onnistukaan! val-muuttujan arvo on "lukittu" muuttujaan, eikä sitä voi vaihtaa toiseksi.

var-muuttujat

Scala-kielen sanalla var (englannin sanasta variable) voi luoda muuttujan, johon sijoitetaan arvo kuten val-muuttujaankin. Ainoana mutta tärkeänä erona val- ja var-muuttujan välillä on se, että jälkimmäisen arvoa voi vaihtaa toiseksi myöhemmin.

Tutustu seuraavaan animaatioon huolellisesti.

Tässä esimerkissämme siis lopulta vaihdettiin sekä luku- että tupla-muuttujan arvoa. Tämä edellyttää, että muuttujat on määritelty var-sanalla. Jos vaihtaisit var-sanat valeiksi, niin yllä käytetyt koodirivit eivät toimisikaan, vaan saisit virheilmoituksen error: reassignment to val.

Kenties hämäävä REPL-juttu

Itse asiassa REPL sallii sinun määritellä olemassa olevan nimisiä muuttujia uudestaan kirjoittamalla uuden var- tai val-alkuisen alustuksen samalle muuttujanimelle. Jos teet näin, saattaa vaikuttaa siltä, että val-muuttujankin arvoa voisi vaihtaa. Oikeastaan kyse on kuitenkin siitä, että uusi määrittely peittää aikaisemman.

Tämä on Scala REPLin erityispiirre, eikä REPLin ulkopuolisissa Scala-ohjelmissa voi tällä tavoin peräkkäisillä käskyillä luoda keskenään samannimisiä muuttujia. Joten kannattaa unohtaa tämä tekniikka.

Toinen esimerkki

Kun var-muuttujan arvoa muuttaa, voi sen vanhaa arvoa käyttää apuna uutta arvoa määritettäessä:

Varo matikkaa! (taas)

Huomaa ja muista: Matematiikassa muuttuja on symboli, joka vastaa jotakin arvoa. Ohjelmoinnissa muuttuja on nimetty muistipaikka, johon voi tallentaa arvon.

Eron käytännön merkitys korostuu var-muuttujien kohdalla. Ohjelma ei ole yhtälöryhmä! Samassa ohjelmassa voi hyvin esiintyä esimerkiksi sijoitukset luku = 10 sekä luku = 5. Myös tutusta matemaattisesta näkökulmasta omituiselta vaikuttava luku = luku + 10 on mahdollinen. Ja sijoituskäskyjen järjestyksellä on väliä!

Miksi val?

Protesti! Miksi ikinä tekisin val-muuttujan? Eikö varilla voi tehdä kaikki samat ja enemmän?

var-muuttujat tosiaan lisäävät mahdollisuuksia, mutta se ei ole pelkästään hyvä puoli.

Ohjelmoijan on jatkuvasti järkeiltävä koodinsa toiminnasta koodia kirjoittaessaan ja siitä virheitä etsiessään. Järkeilyä helpottaa se, että ohjelmoija tietää tiettyjen asioiden koodissa olevan muuttumattomia. Hän voi esimerkiksi nähdä val-sanasta suoraan, että tietyn muuttujan arvo ei missään ohjelman suorituksen vaiheessa muutu miksikään. Tämä seikka alkaa tuntua merkityksellisemmältä, kunhan kohtaat suurempia ja monimutkaisempia ohjelmia; huomannet val-muuttujien edun itsekin vielä tämän kurssin aikana.

Pienissä REPL-kokeiluissa ei ole niin väliä, kummanlaisia muuttujia käyttää, mutta tässä jo valmiiksi nyrkkisääntö tulevia ohjelmointitehtäviä varten:

Tee jokaisesta muuttujasta val, ellei sinulla ole juuri nyt selvää syytä tehdä siitä var.

Älä tee var-muuttujia "varmuuden vuoksi, jos sitä arvoa vaikka tarvitsisi muuttaa". Se on heikkoa ohjelmointityyliä. Jos myöhemmin osoittautuu, että val ei sovi tarkoitukseen, voit vaihtaa variin.

Onko valit edes mitään "muuttujia"?

Voidaan ajatella, että vain var-muuttujat ovat niitä varsinaisia muuttujia, koska niiden arvoa voi muuttaa luomisen jälkeenkin. Kuitenkin myös val-muuttujaa voi perustellusti kutsua "muuttujaksi", muun muassa siksi, että se voi saada eri arvoja ohjelman eri suorituskerroilla (vaikkapa käyttäjän syötteestä) ja siksi, että sama val-muuttujan luontikäsky saatetaan suorittaa ohjelman aikana useita kertoja niin, että joka kerta luodaan eriarvoinen val-muuttuja edellisen tilalle. Näistä nähdään esimerkkejä myöhemmin kurssilla.

val-muuttujat ovat myös lähempänä matemaattisen muuttujan käsitettä kuin varit, ja onpa esitetty sellaistakin, että juuri val-muuttujat niitä oikeita muuttujia ovatkin, kun taas varit ovat "sijoituspaikkoja" ("assignables"). Jätetään tämä sanasota nyt kuitenkin tähän.

Funktionaalinen ohjelmointi

On olemassa laaja ohjelmoinnin suuntaus, funktionaalinen ohjelmointi, jossa puhtaimmillaan käytetään ainoastaan val-tyylisiä muuttujia. Aiheeseen tutustutaan tämän oppimateriaalin luvussa 10.2 ja syvemmin jatkokursseilla.

Kysyttyä: onko valilla ja varilla eroa muistinkäytön tai muun suorituskyvyn kannalta?

Lähtökohtaisesti eroa ei ole. Muuttujille varatun muistin määrä riippuu vain tietotyypistä; siitä luvussa 5.4.

Käytännössä asia on monimutkaisempi, ja valinta varin ja valin välillä voi vaikuttaa esimerkiksi niihin optimointeihin, joita kääntäjät (luku 5.4) tekevät muokatessaan ohjelmoijan kirjoittaman Scala-koodin suoritettavaan muotoon. Huomionarvoista on esimerkiksi se, että val-muuttujista voi olla etua, kun ohjelmia tehostetaan jakamalla suorituksen osia eri tahojen rinnakkain suoritettavaksi. Tuota teemaa ei tällä peruskurssilla syvemmin käsitellä.

var-tehtäviä ja soittimia

Mitä tämä koodinpätkä tulostaa?

var esimerkki = 2
esimerkki = esimerkki * esimerkki
esimerkki = esimerkki * esimerkki
println(esimerkki * esimerkki)

Kirjoita tuloste tähän:

Tässä vielä merkkijonoesimerkki:

var sana = "laama"
sana = sana + "nni"
sana = "piiri"
sana = "ta" + sana
sana = "noi" + sana

Mikä on sana-muuttujan arvo lopuksi?

Käsky o1.play mahdollistaa myös virtuaalisoittimen vaihtamisen oletusarvoisesta, eli pianosta, toiseksi. Se järjestyy laittamalla merkkijonon sisään, esimerkiksi heti alkuun, instrumentin numero hakasulkeissa. Numeron on oltava väliltä 1–128.

Otetaanpa, lapset, Ukko Nooa nokkahuilulla eli soittimella numero 75:

o1.play("[75]>cccedddfeeddc---")

Tässä yhteydessä hakasulut eivät ole mikään yleisemmin Scala-ohjelmointiin liittyvä tekniikka vaan merkkejä merkkijonossa (lainausmerkkien sisällä) siinä missä muutkin. o1.play-käsky tulkitsee ne soitinmerkinnäksi. (Hakasuluille löytyy kyllä Scala-ohjelmissa muuta käyttöä seuraavassa luvussa 1.5.)

Tutustu nyt huolellisesti seuraavaan koodinpätkään. Mieti, mitä arvoja kukin muuttuja saa missäkin vaiheessa, kun käskyt annetaan yksi kerrallaan peräjälkeen.

var soitin = 13
val nuotit = "<<<h.>c#.d.e.f#.d.f#-.e#.c#.e#-.e.c.e-.<h.>c#.d.e.f#.d.f#.h.a.f#.d.f#.a-- "
var peikkotanssi = "[" + soitin + "]" + nuotit + "/138"
o1.play(peikkotanssi)
soitin = 72
o1.play(peikkotanssi)

Kumpi seuraavista kuvaa sitä, mitä tapahtuu, kun esimerkin viimeinen rivi suoritetaan? Tutki ilmiötä itse REPLissä tarpeen mukaan. Soittokäskyn lisäksi tai sijaan voit tulostaa tuolle käskylle esimerkissä annetut parametriarvot.

Alla on yksi lisäkoodirivi.

peikkotanssi = "["+ soitin + "]" + nuotit + "/138"

Monenneksiko tämä rivi pitäisi lisätä edellisen kohdan koodiin, jotta se toimisi siten, että kun rivit suoritetaan yksi kerrallaan järjestyksessä, niin peikkotanssi tulee ensin soitetuksi soittimella 13 (marimba) ja toiseksi soittimella 72 (klarinetti)?

Monesko tämä lisätty rivi olisi toimivassa seitsenrivisessä koodissa? Vastaa yhdellä numerolla.

o1.play ja MIDI-äänisynteesi

Soittokäskyn o1.play tuntemat soittimet on määritelty General MIDI -standardissa, jonka nimessä MIDI on lyhenne sanoista Musical Instrument Digital Interface eli digitaalinen soitinrajapinta. MIDI-pohjaisesti voi tuottaa synteettistä ääntä erilaisilla virtuaalisoittimilla; tulosten laatu vaihtelee. Soittokäskymme tarjoaa merkkijonoihin perustuvan, yksinkertaiseen käyttöön sopivan tavan hyödyntää keskeisimpiä MIDI-äänisynteesitoimintoja.

Numeroitu soitinluettelo löytyy midi.org -sivustolta.

Tällä kurssilla käytämme MIDI-ääntä "huviksemme", lähinnä eräissä kurssin alkupuolen merkkijonoesimerkeissä. Kuvaamme merkkijonoilla nuotteja, emme suoranaisesti ääntä. Äänen digitaalisesta kuvaamisesta ja nauhoitetusta äänestä tulee puhetta esimerkiksi kurssilla Ohjelmointistudio 1.

o1.play ja pisteet

Äsken käytetyssä peikkotanssimerkkijonossa oli pisteitä. o1.play-käsky tulkitsee pisteitä edeltävät nuotit teräväksi staccato-soitoksi, jossa nuotti soitetaan lyhyempänä ja äänen perässä on tauko.

var ja tietotyypit

Muuttujan tietotyyppi määrää, millaisia arvoja voit sijoittaa muuttujaan. Se ei muutu, ei var-muuttujankaan tapauksessa. Esimerkiksi String-tyyppiseen muuttujaan voi sijoittaa vain String-arvoja, kuten seuraava esimerkki osoittaa.

var titteli = "opiskelija"titteli: String = opiskelija
titteli = "DI"titteli: String = DI
titteli = 12345Int(12345) <: String?
false
<console>:8: error: type mismatch;
found   : Int(12345)
required: String
titteli = 12345
          ^

Virheilmoitusten tulkintataito kehittyy ohjelmointikokemuksen mukana. Tässä virheilmoituksessa lukee osapuilleen, että:

"Onko kokonaisluku 12345 eräänlainen merkkijono? No ei. Virhe: Tietotyypit eivät sovi yhteen; riviltä titteli = 12345 löytyi kokonaisluku 12345 kohdasta, johon olisi kaivattu merkkijonoa."

Lukutyyppien yhteispeliä

On kuitenkin tilanteita, joissa näennäisesti rikotaan sääntöä tyyppien yhteensopivuudesta. Yksi tällainen tilanne on Int-arvon sijoittaminen Double-muuttujaan, kuten seuraavan esimerkin loppupäässä:

var lukuarvo = 123.45lukuarvo: Double = 123.45
val tasaluku = 100tasaluku: Int = 100
tasaluku * tasalukures8: Int = 10000
lukuarvo = tasalukulukuarvo: Double = 100.0
lukuarvo * tasalukures9: Double = 10000.0

Viimeinenkin sijoitus onnistui: muuttujasta tasaluku katsottu Int-arvokin "kelpaa Double-arvoksi". Kuitenkin kuten esimerkin viimeisistä tulosteista huomaat, Int-arvon sijaan tulee muuttujaan lukuarvo tallennetuksi vastaava Double-arvo, jolla laskeminen tuottaa Double-tyyppisiä tuloksia.

Tämä on hyödyllistä monessa tulevassa tilanteessa, jossa laaditaan sellainen ohjelman osa, joka toimii desimaaliluvuilla ja jonka halutaan toimivan samalla tavoin myös kokonaisluvuille.

Olipa kerran var-muuttujat nimeltä hannu ja kerttu, jotka olivat keskenään samantyyppisiä. Tarkastellaan seuraavaa koodinpätkää:

hannu = kerttu
kerttu = hannu

Mikä seuraavista väittämistä kuvaa parhaiten sitä, mitä muuttujien arvoille käy, kun muuttujat on ensin luotu ja alustettu joillakin arvoilla ja äskeiset koodirivit suoritetaan annetussa järjestyksessä? Tutki asiaa tarpeen mukaan ohjelmoimalla REPLissä.

Seuraavasta koodinpätkästä on muokattu pieni arvoitus korvaamalla pari kohtaa kysymysmerkeillä. Lue koodi läpi ja mieti, mitä se tekee kahden ensiksi määritellyn muuttujan arvoille.

var eka = ???
var toka = ???
val apumuuttuja = eka
eka = toka
toka = apumuuttuja
println(eka + ", " + toka)

Lisäksi tiedetään, että viimeinen tulostuskäsky tulostaa "vemmelsääri, ristihuuli". Mikä oli muuttujan toka alkuarvo?

Edellä näit, että Double-tyyppiseen muuttujaan voi sijoittaa Int-tyyppisen lausekkeen arvon, jolloin muuttujaan tallentuu kokonaislukua vastaava desimaaliluku. Toimiiko sama myös toisin päin, eli tallentuuko desimaalilukua vastaava kokonaisluku? Kokeile REPLissä.

res-muuttujat REPLissä

REPLin res-alkuiset vastaukset sille syötettyihin lausekkeesiin ovat jo tulleet tutuiksi. Itse asiassa REPL luo jatkuvasti uusia val-muuttujia, joiden nimet alkavat res ja joita voit käyttää itse määrittelemiesi muuttujien tapaan:

1 + 1res10: Int = 2
res10 * 10res11: Int = 20
val yhteensa = res10 + res11yhteensa: Int = 22

Voit hyödyntää tuota omissa kokeiluissasi, jos haluat. Kurssimateriaalin esimerkeissä tätä mahdollisuutta ei kuitenkaan käytetä, vaan keskitymme opettelemaan ohjelmointitekniikoita, jotka toimivat myös REPLin ulkopuolella. Numeroidut res-alkuiset muuttujat ovat nimenomaan REPL-ympäristön erikoisuus.

Yhteenvetoa

  • Muuttuja on nimetty tallennuspaikka yhdelle arvolle. Muuttujia käytetään tiedon varastoimiseen koneen muistissa.
    • Esimerkiksi GoodStuff-ohjelmassa muuttujia voidaan käyttää kirjaamaan kokemusten tietoja (hinta, arvosana) sekä se, mikä on käyttäjän suosikkikokemus.
  • Ohjelmoija pääsee arvoihin käsiksi muuttujien nimien avulla. Nimiä voi käyttää lausekkeina ja siis myös suurempien lausekkeiden osina.
  • Scalassa on kahdenlaisia muuttujia: val ja var.
    • val-muuttujaan sijoitetaan arvo, joka ei sen jälkeen vaihdu. val-muuttujien käyttö selkeyttää ohjelmakoodia, ja niitä tulee käyttää ensisijaisesti.
    • var-muuttujan arvoa voi muuttaa sijoittamalla uuden arvon vanhan tilalle. var-muuttujat mahdollistavat ohjelman tilan muuttamisen sijoituskäskyillä; niitä käytetään harkitusti tarpeen mukaan.
  • Järkevästi nimetyt muuttujat selkiyttävät ohjelmakoodia. Muuttujien käyttö voi parantaa myös ohjelman tehokkuutta ja muokattavuutta.
  • Lukuun liittyviä termejä sanastosivulla: muuttuja, sijoittaa; lauseke, arvo, evaluoida; var-muuttuja, val-muuttuja; varattu sana; DRY.

Tässä vielä edellisen luvun käsitekaavio tämän luvun tärkeimmillä käsitteillä täydennettynä:

Palaute

Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.

Tekijät

Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!

Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.

Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.

Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.

Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.

Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.

Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Opetustapa, jossa käytämme O1Libraryn työkaluja (kuten Pic) yksinkertaiseen graafiseen ohjelmointiin on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.

Oppimisalusta A+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.

Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.

Lisäkiitokset tähän lukuun

Luvussa tehdään vääryyttä Edvard Griegin säveltämälle musiikille. Kiitos ja anteeksi.

a drop of ink
Palautusta lähetetään...