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, erityisesti merkkijonoista.
Mitä tehdään? Ohjelmoidaan REPLissä ja luetaan.
Suuntaa antava työläysarvio:? Reilu tunti.
Pistearvo: A40.
Oheismoduulit: Ei ole.
Muuta: Eräissä tämän luvun kohdissa on kaiuttimista tai kuulokkeista hyötyä. Aivan pakolliset ne eivät ole.
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
kertoma
, jonka
perään tulee yhtäsuuruusmerkki...REPL vastaa muuttujan määrittelyyn näin:
val kertoma = 1 * 2 * 3 * 4 * 5 * 6kertoma: Int = 720
resX
:n
sijaan) sekä...Nyt kertoman kuution voi laskea muuttujan avulla:
kertoma * kertoma * kertomares3: Int = 373248000
Huomaa, että muuttujan nimi yksinäänkin toimii lausekkeena. Tuon lausekkeen arvo on kyseiseen muuttujaan tallennettu arvo. Muuttujan nimeä, kuten muitakin lausekkeita, voi käyttää suurempien lausekkeiden muodostamiseen. Esimerkiksi tässä muuttujan nimeä 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.
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 laatukriteeriin: 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
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ä.
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 yleensä pienellä kirjaimella. Mitään teknistä pakotetta pienen alkukirjaimen käyttöön ei ole, mutta tämän tavan noudattaminen kuuluu hyvään Scala-ohjelmointityyliin.
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 oheismoduuleissa taas on yleensä 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
Käytätkö IntelliJ’tä Mac-tietokoneella? Lue tämä ennen seuraavaa tehtävää. (Ohita muuten.)
Kurssin oletusarvoiset näppäimistöasetukset, jotka IntelliJ’n A+ Courses -lisäosa asentaa, eivät ole kaikilta osin yhteensopivat Mac-näppäimistöjen kanssa. Asian voi korjata seuraavasti.
Kokeile pystytkö kirjoittamaan REPLissä esim. dollarimerkin $
ja hakasulkeet []
normaalisti. Jos pystyt, kaikki on hyvin. Mikäli
ne eivät toimi, valitse käyttöösi Macille sopivammat asetukset.
Valikosta: IntelliJ IDEA → Preferences → Keymap ja sitten
otsikon alla näkyvästä pudotusvalikosta O1 MacOS Keymap.
Paina OK. Nyt pitäisi toimia.
Jos ei onnistunut, lue seuraava:
Mikäli olit hyvissä ajoin liikkeellä ja asensit IntelliJ’n ennen kurssin virallista alkua 9.9.2020, niin (hyvin toimittu, mutta) tuolta ei välttämättä löydy valintaa O1 MacOS Keymap. Siinä tapauksessa toimi näin:
- Valitse ylävalikosta A+ → Reset A+ Courses Plugin Settings ja hyväksy.
- Valitse samasta valikosta A+ → Turn Project into A+ project ja hyväksy taas ehdotetut toimenpiteet.
- Nyt O1 MacOS Keymap pitäisi löytyä asetusvalikosta IntelliJ IDEA → Preferences → Keymap.
(Tulemme parantamaan A+ Courses -lisäosaa niin, että tuo käy automaattisesti, mutta toistaiseksi se pitää tehdä itse; onneksi vain kerran. Pahoittelut lisävaivasta.)
Arvojen upottaminen merkkijonoon
Käytetään tätä muuttujaa:
val ikavuodet = 20ikavuodet: Int = 20
Mitä, jos haluaisimme tuottaa tekstin, joka kuvailee muuttujaan tallennetun tiedon? Teksti voisi olla vaikkapa muotoa "Asiakas on X vuotta vanha.", missä X korvataan muuttujaan tallennetulla arvolla. (Tämä on yleinen tarve. Lukemattomat sovellusohjelmat raportoivat samaan tyyliin muistiin tallennettuja tietoja.)
Tässä yksi tapa:
s"Asiakas on $ikavuodet vuotta vanha."res6: String = Asiakas on 20 vuotta vanha.
s
-kirjain. Se toimii
merkkinä siitä, että upotamme merkkijonoliteraaliin lausekkeen
arvon. (Englanniksi tämä tekniikka tunnetaan nimellä string
interpolation.)$
ja sen perään nimen.
Nimi korvautuu vastaavalla arvolla. Tämä toimii, kunhan alussa oli
se s
-kirjain. (Jos s
unohtuu, mitä tapahtuu? Arvaa ja kokeile
REPLissä, menikö oikein.)Alla on lisäesimerkkejä merkkijonoupotuksista.
val luku = 10luku: Int = 10 s"Luku kahdesti ja välilyöntejä välissä: $luku $luku"res7: String = Luku kahdesti ja välilyöntejä välissä: 10 10
Merkkijonoon voi upottaa useitakin lausekkeita. Yllä upotettiin kaksi kertaa sama lauseke; alla kaksi eri lauseketta.
s"$luku on vähän pienempi kuin ${luku + 1}."res8: String = 10 on vähän pienempi kuin 11.
Upotettavan lausekkeen ei tarvitse olla vain muuttujan nimi, vaan
monimutkaisempikin lauseke kelpaa. Tällöin lauseke on rajattava
aaltosulkeilla, kuten juuri tuossa yllä on rajattu aritmeettinen
lauseke luku + 1
. (Jos aaltosulkeet unohtuvat, mitä tapahtuu? Arvaa
ja kokeile REPLissä, menikö oikein.)
Äskeisissä esimerkeissä merkkijonoon upotettiin kokonaislukuja
kuvaavia merkkejä, mutta sama toimii toki muunkintyyppisille
arvoille, kuten Double
ille:
val arvosana = 9.5arvosana: Double = 9.5 val raportti = s"arvosana: $arvosana"raportti: String = arvosana: 9.5 s"$arvosana on saamasi arvosana"res9: String = 9.5 on saamasi arvosana
Merkkijonon sisään voi upottaa myös String
-tyyppisiä arvoja:
val nimi = "Satu"nimi: String = Satu println(s"$nimi, $raportti")Satu, arvosana: 9.5
Tuo viimeinen käsky siis tekee saman kuin tämä merkkijonoja plus-operaattorilla peräkkäin laittava käsky:
println(nimi + ", " + raportti)Satu, arvosana: 9.5
Lisää merkkijonojen plus-operaatiosta
Olemme nähneet, että merkkijonoja voi laittaa peräkkäin plus-operaattorilla. Voit
yhdistää merkkijonoon plussalla myös muunlaisen arvon kuten luvun. Lopputulos on
samanlainen kuin merkkijonoupotuksessa. Alla on muutama yllä käytetyistä esimerkeistä
kirjoitettuna plus-operaattorilla merkkijonoupotuksen (eli s
n ja dollarimerkin)
sijaan.
"Asiakas on " + ikavuodet + " vuotta vanha."res10: String = Asiakas on 20 vuotta vanha. "Luku kahdesti ja välilyöntejä välissä: " + luku + " " + lukures11: String = Luku kahdesti ja välilyöntejä välissä: 10 10 val raportti = "arvosana: " + arvosanaraportti: String = arvosana: 9.5
Käytännön Scala-ohjelmissa muodostetaan merkkijonoja sekä plus-merkillä yhdistäen että dollarimerkillä upottaen. Niinpä molemmat tavat on syytä tuntea, ja tässä oppimateriaalissakin käytetään molempia.
Plus-operaattorin käyttöön liittyy kuitenkin Scalassa eräs rajoitus, joka pitää huomata. Verrataan kahta koodinpätkää:
s"$arvosana on saamasi arvosana"res12: String = 9.5 on saamasi arvosana arvosana + " on saamasi arvosana"arvosana + " on saamasi arvosana" ^ warning: method + in class Double is deprecated (since 2.13.0): Adding a number and a String is deprecated. Use the string interpolation `s"$num$str"`
Ensimmäinen käsky toimii halutusti merkkijonoupotuksella. Äkkiseltään voisi luulla, että toinen käsky toimisi yhtä hyvin. Kuitenkin saamme varoitusilmoituksen: Scala-työkalustomme varoittaa, että noin ei ole suotavaa tehdä.
Merkkijonon ja luvun peräkkäin liittävä plus-operaatio on määritelty Scalassa näin: "yhdistä plussan vasemmalla puolella olevan merkkijonon perään oikealla oleva luku". Toisin päin — eli luku vasemmalla ja merkkijono oikealla — ei käykään.
Älä siis käytä tuollaisessa tapauksessa plus-operaattoria. Voit käyttää sen sijaan merkkijonoupotusta, kuten varoitusilmoituskin vihjaa.
Lisää vaihtoehtoja äskeisille
(Tämä ei ole tässä kohden tärkeää, mutta kiinnostanee joitakin kurssilaisia. Voit ohittaa tämän ilman tunnontuskia.)
On vielä muitakin tapoja muodostaa merkkijonoja. Esimerkiksi kaikki seuraavat käskyt tuottavat saman tuloksen:
s"$arvosana on saamasi arvosana"res13: String = 9.5 on saamasi arvosana "" + arvosana + " on saamasi arvosana"res14: String = 9.5 on saamasi arvosana arvosana.toString + " on saamasi arvosana"res15: String = 9.5 on saamasi arvosana
""
on tyhjä merkkijono (empty string)
eli nollan merkin mittainen merkkijono. Kun siihen
yhdistetään luku, saadaan tuon luvun kuvaava merkkijono
(johon voi sitten edelleen yhdistellä muita arvoja).
Lisää käyttöä tyhjille merkkijonoille löytyy mm.
luvussa 4.1.Vauhtia musiikkiin
Käytetään taas luvun 1.3 esittelemää 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:
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
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 + "/" + normaaliTempores16: String = cccedddfeeddc---eeeeg-f-ddddf-e-cccedddfeeddc---/120
Juuri tuollaista kauttaviivallista merkkijonoa play
osaa käsitellä. Tässä
pari esimerkkiä kokeiltavaksi:
play(kokoNooa + "/" + normaaliTempo)play(kokoNooa + "/" + hidasTempo)
play
-käskyn oletustempo, joten
ensimmäinen soittokerta kuulostaa ihan samalta kuin aiemmatkin.Jos muuttujille ei ole muuta tarvetta, niin koko merkkijonon voi toki kirjata tempoineen kerrallakin kuten alla.
play("cdefg/140")
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
val
eiksi, 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ö var
illa 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 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äytät, mutta tässä jo valmiiksi nyrkkisääntö tulevia ohjelmointitehtäviä varten:
Tee jokaisesta muuttujastaval
, 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 var
iin.
Onko val
it 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 var
it, ja onpa esitetty sellaistakin, että juuri val
-muuttujat
niitä oikeita muuttujia ovatkin, kun taas var
it 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 val
illa ja var
illa eroa muistinkäytön tai muun suorituskyvyn kannalta?
Pohjimmiltaan eroa ei ole. Muuttujille varatun muistin määrä riippuu vain tietotyypistä; siitä luvussa 5.4.
Käytännössä asia on monimutkaisempi, ja valinta var
in ja
val
in välillä voi vaikuttaa esimerkiksi niihin optimointeihin,
joita kääntäjät 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
play
ja MIDI-äänisynteesi
Soittokäskyn 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.
play
ja pisteet
Äsken käytetyssä peikkotanssimerkkijonossa oli pisteitä.
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 * tasalukures17: Int = 10000 lukuarvo = tasalukulukuarvo: Double = 100.0 lukuarvo * tasalukures18: 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.
res
-muuttujat REPLissä
REPLin res
-alkuiset vastaukset sille syötettyihin lausekkeesiin ovat jo tulleet tutuiksi.
Itse asiassa REPL luo aina uusia val
-muuttujia, joiden nimet alkavat res
ja joita voit
käyttää itse määrittelemiesi muuttujien tapaan:
1 + 1res19: Int = 2 res19 * 10res20: Int = 20 val yhteensa = res19 + res20yhteensa: 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
javar
.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; merkkijonoupotus.
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!
Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.
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 suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee- ja Kelmu.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Tapa, jolla 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+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki. Pääkehittäjänä on tällä hetkellä Markku Riekkinen, jonka lisäksi A+:aa ovat kehittäneet kymmenet Aallon opiskelijat ja muut.
A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen ovat luoneet Nikolai Denissov, Olli Kiljunen ja Nikolas Drosdek yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
val
(lyhennetty englannin ilmaisusta value variable), jonka perään kirjoitetaan...