Luku 4.1: Autoiluharjoitus

../_images/person08.png

Aluksi: lyhennysmerkinnöistä

Kun sijoitamme esimerkiksi kokooja- tai askeltajamuuttujaan, käytämme uuden arvon määräävässä lausekkeessa muuttujan vanhaa arvoa. Kun kyseessä on lukuarvoinen muuttuja, uusi arvo usein määräytyy aritmeettisen laskutoimituksen tuloksena. Esimerkiksi:

this.saldo = this.saldo + maara
this.value = this.value + 1

Koska tämänkaltaiset sijoitukset ovat yleisiä ja koska niissä toistuu sama koodinpätkä useita kertoja, on määritelty lyhyempiä tapoja merkitä asia. Esimerkiksi äskeiset koodinpätkät voi kirjoittaa näinkin:

this.saldo += maara
this.value += 1

Sijoitusoperaattori += siis tarkoittaa: "Kasvata vasemmalla puolella mainitun muuttujan arvoa oikealla puolella olevan lausekkeen verran." Tai tarkemmin: "Ota vasemmalla puolella mainitun var-muuttujan vanha arvo ja oikealla puolella olevan lausekkeen arvo. Laske ne yhteen. Sijoita lopputulos vasemmalla puolella mainittuun muuttujaan."

(Tässä on muuten kyseessä täsmälleen samanlainen merkintä += kuin luvussa 1.5, jossa merkintää käytettiin lisäämään uusi arvo puskuriin. Nyt on kysymys eri asiasta eli yhteenlaskusta. Analogia tapausten välillä toki on: puskuria päivitetään lisäämällä vs. muuttujan arvoa päivitetään lisäämällä.)

Syntaktinen sokeri

Ei ole harvinaista, että ohjelmointikieli tarjoaa erilaisia tapoja kirjoittaa saman asian. Tällöin usein puhutaan syntaktisesta sokerista (syntactic sugar) eli sellaisista kielirakenteista, jotka eivät ole ohjelmointikielen ilmaisuvoiman kannalta välttämättömiä (eli eivät mahdollista uudenlaisia ohjelmia) mutta jotka tekevät asioiden ilmaisemisesta ohjelmoijan kannalta kätevämpää tai kauniimpaa. Yllä esitellyt lyhennysmerkinnät yleisille sijoituksille ovat esimerkki syntaktisesta sokerista.

Syntaktinen sokeri on makuasia, josta voi halutessaan kiistellä. Koska lähes kaikki yleiskäyttöiset ohjelmointikielet (kuten Scala) ovat laskennalliselta ilmaisuvoimaltaan yhtä hyviä, voisi jopa sanoa, että eräällä tavalla ohjelmointikielet yleensäkin ovat vain syntaktista sokeria.

Lisää lyhennyksiä

Vastaavia lyhennysmerkintöjä on enemmänkin; esimerkkejä niistä on alla taulukossa. Scala-työkalut automaattisesti "laventavat" lyhennetyt käskyt.

Pidemmin

Lyhyemmin

arvo = arvo + toinen

arvo += toinen

arvo = arvo - toinen

arvo -= toinen

arvo = arvo * toinen

arvo *= toinen

arvo = arvo / toinen

arvo /= toinen

arvo = arvo % toinen

arvo %= toinen

Näiden lyhennysmerkintöjen käyttö on yleistä, ja käytämme niitä jatkossa myös tällä kurssilla. Voit käyttää niitä itse esimerkiksi tämän luvun ohjelmointitehtävässä tai olla käyttämättä. Joka tapauksessa on tarpeen pystyä lukemaan koodia, jossa niitä käytetään.

Usein kysytty kysymys

Eräissä yleisissä ohjelmointikielissä (esim. C, Java) on oheisiakin lyhyempi ilmaisu, jolla lisätään tai vähennetään ykkönen muuttujan arvosta. Esimerkiksi luku++ ja luku-- muuttavat muuttujan arvoa yhdellä kuten luku += 1 ja luku -= 1. Onko tällaisia merkintöjä Scalassakin?

Vastaus: Ei ole, koska lisälyhennykset on katsottu tarpeettomiksi. Usein Scalalla ohjelmoidaan tyylillä, jossa var-muuttujia käytetään niukasti (luku 11.2), joten näiden merkintöjenkin merkitys on vähäisempi. Toisaalta Scalalla voi itsekin määritellä "omia operaattoreita" (luku 5.2).

Pikkutehtäviä

Tarkastele seuraavaa koodinpätkää, ja mieti, mitä arvoja luku-muuttuja saa, kun koodi suoritetaan.

var luku = 5
luku += 1
luku *= 3
luku += luku
luku -= 2 * (luku - 13)

Kirjoita tähän allekkain viisi riviä, joilla luettelet järjestyksessä ne kokonaisluvut, jotka ovat lukumuuttujan arvoina kunkin ohjelmamme rivin suorittamisen jälkeen.

Myös merkkijonoilla on operaattorit += ja *=.

Tarkastele seuraavaa koodinpätkää.

var teksti = ""
teksti += "<e--gg#h>ed--"
teksti += "<h>f#-<h>de"
teksti *= 3
teksti += "/135"
play(teksti)

Sivumennen sanoen: Muuttujan alkuarvo on tässä tyhjä merkkijono (empty string) eli merkkijono, joka ei sisällä yhtäkään merkkiä (ei edes välilyöntejä, lainausmerkkejä tms.). Tyhjä merkkijono kirjataan koodiin kahtena lainausmerkkinä, joiden välissä ei ole mitään. Tällaiseen merkkijonoon voi operaattoreilla + ja += yhdistää muita arvoja, kuten tässäkin esimerkissä tehdään.

Minkä merkkijonon koodinpätkä lähettää play-funktiolle? Selvitä ja liimaa tähän.

Lisäharjoite

Muokkaa luvun 3.5 VendingMachine-luokkaa niin, että käytät tavallisten sijoituskäskyjen sijaan yllä esiteltyjä lyhennysmerkintöjä siellä missä mahdollista.

Luokan koodi löytyy Miscellaneous-moduulista.

CarSim-tehtävä

../_images/carsim.png

CarSim on autosimulaattoriohjelma, jossa käyttäjä voi ajella palluroina kuvattuja autoja maailman kartalla.

Tehtävänanto

Nouda moduuli CarSim. Se sisältää hyvän osan sovellusohjelmasta. Käyttöliittymä on annettu kokonaan valmiina. Keskeinen osa ohjelmaa kuitenkin puuttuu. Tehtäväsi on

  • lukea luokan o1.carsim.Car dokumentaatio hartaudella;

  • kirjoittaa Scala-ohjelmakoodi, joka toteuttaa tuon täysin puutteellisena annetun luokan; ja

  • testata, että se toimii dokumentaation mukaisesti, ja korjata tarvittaessa.

???

Annetussa luokan Car raakileessa esiintyy useassa kohdassa kolme peräkkäistä kysymysmerkkiä: ???. Kyseessä on Scala-kielen tarjoama tapa merkitä, että osa ohjelmasta on toistaiseksi toteuttamatta. Jos ???-lausekkeen yrittää evaluoida (eli vaikkapa annetun Car-luokan metodeita kutsua), syntyy ajonaikainen poikkeustilanne.

Kaksi fuel-metodia?

Huomasitko, että luokassa on kaksi eri fuel-nimistä metodia, joilla on erilaiset parametriluettelot?

Nämä metodit ovat toisistaan ihan erilliset, mutta voit hyödyntää yhtä toisen toteutuksessa.

Hieman lisätietoa keskenään samannimisistä metodeista löytyy tämän autotehtävän jäljestä.

Suosittelemme, että noudatat seuraavia työvaiheita.

1/6: Ilmentymämuuttujat (ainakin alustavasti)

Sinun on itse valittava ilmentymämuuttujat niin, että tarvittavat tilatiedot pysyvät tallessa olioissa metodikutsujen välilläkin. Osan muuttujista tulee olla yksityisiä, jotta niiden arvoja ei voi mielivaltaisesti muuttaa luokan ulkopuolelta.

Dokumentaatio kuvaa luontiparametrit, ja annetusta koodistakin ne löytyvät. Osa niistä on myös (perustellusti) määritelty ilmentymämuuttujiksi. Mitä muita oleellisia piirteitä kullakin autolla on, eli mistä muusta Car-olion on pidettävä kirjaa, jotta sen metodit voisivat toimia? Millaisiin muuttujiin nuo tiedot voisi tallentaa? Mitkä ovat muuttujien tyypit ja roolit?

Kaikkia ilmentymämuuttujia ei välttämättä tarvitse keksiä heti. Voit myöhemmin palata lisäämään muuttujia, jos huomaat niille tarvetta.

Pitäydy pyydetyssä rajapinnassa

Älä lisää luokkaan julkisia osia, joita ei pyydetty. Yksityisiä osia voit lisätä vapaasti.

Sama ohje pätee kaikkiin niihin kurssin ohjelmointitehtäviin, joissa on annettu tarkka spesifikaatio toteutettavien luokkien julkisesta rajapinnasta.

Valinnainen vinkki ilmentymämuuttujista

Ilmentymämuuttujissa on tarpeen pitää kirjaa niistä asioista, jotka säilyvät metodikutsujen välilläkin. Näitä ovat muun muassa polttoaineen kulutus ja tankin koko, jotka onkin jo annetussa koodissa ilmentymämuuttujiksi merkitty.

Useat metodit käsittelevät tankissa olevan bensan määrää. Tuo tieto on pidettävä tallessa ilmentymämuuttujassa, jotta se ei katoa metodikutsujen välillä.

Metodit location ja drive käsittelevät auton sijaintia, jonka on myös tallennuttava osaksi auton tietoja.

metersDriven ja drive tarvitsevat tietoa siitä, paljonko autolla on siihen mennessä ajettu.

Muita ilmentymämuuttujia et tarvitse. Jos haluat pitää metodin sisäisesti tallessa jotakin tietoa, voit kirjata sen paikalliseen muuttujaan.

2/6: Helpommat(?) metodit

drive on metodeista monimutkaisin. Yksi mahdollisuus on edetä tehtävän parissa niin, että unohdat sen toistaiseksi ja laadit toteutukset ensin muille metodeille. (Luithan silti myös drive-metodin dokumentaation jo, jotta ymmärrät tuon keskeisen metodin tarkoituksen?)

Valinnainen vinkki location- ja metersDriven-metodeihin

Kunhan sinulla on sijaintia ja ajettua matkaa vastaavat ilmentymämuuttujat, näiden metodien toteutuksista tulee äärimmäisen yksinkertaiset.

Huom. Vaikket olisi vielä tehnytkään drive-metodia, voit tässä olettaa, että se tulee vielä myöhemmin toteutettua ja päivittää sijaintia ja ajettua matkaa sopivasti. Näille kahdelle muulle metodille jää tehtäväksi vain palauttaa arvo; metodit ovat vaikutuksettomia.

Valinnainen vinkki fuel-metodeihin

Parametrillinen fuel-metodi on samantapainen kuin eräät aiemmissa tehtävissä laatimasi metodit (esim. nostot pankkitililtä). Huomaa, mitä metodin tulee palauttaa. Käytä paikallista apumuuttujaa.

Parametriton fuel-metodi on toteutettavissa erittäin yksinkertaisesti kutsumalla parametrillista fuel-metodia riittävän suurella parametriarvolla.

Valinnainen vinkki fuelRatio- ja fuelRange-metodeihin

Näissä metodeissa selviät tavallisilla laskutoimituksilla. Huomaa yksiköt: prosentit nollan ja sadan väliltä; auton "kantama" metreinä (eikä satoina kilometreinä).

3/6: Testaa luokkaasi irrallisena ohjelmakomponenttina

Kokeile ajaa annettu testiohjelma carTest, joka käyttää Car-luokkaa varsinaisesta kokonaisesta CarSim-simulaattoriohjelmasta irrallaan. Tuon testiohjelman pitäisi nyt toimia muuten paitsi ajamisen osalta.

Voit muokata testiohjelmaa kattavuuden parantamiseksi. Voit myös kokeilla Car-luokan käyttöä REPLissä.

4/6: Testaa luokkaasi CarSim-sovelluksen osana

Käynnistä CarSim käynnistysoliosta o1.carsim.gui.CarSim. Kokeile autojen luomista ja tankkaamista graafisessa käyttöliittymässä. Lyhyt käyttöohje näkyy siinä ikkunan alareunassa.

5/6: Toteuta myös drive-metodi

Kuten dokumentaatiostakin käy ilmi, tässä tehtävässä voit olettaa auton liikkuvan suoraa viivaa pitkin, "linnuntietä". Joskus se jää matkan varrelle, kun bensa loppuu.

Jos auton pysähtymissijainnin määrittämiseen liittyvä matematiikka tuottaa vaikeuksia, niin näistä vinkeistä voi olla apua:

  • CarSim-ohjelma käyttää Pos-olion x:ää ja y:tä kuvaamaan leveys- ja pituusasteita maapallon pyöreällä pinnalla. Kuitenkin tämä tehtävä on pedattu sinulle niin, että voit edelleen ajatella Pos-olioiden yksinkertaisesti kuvaavan pisteitä tavallisessa, litteässä, kaksiulotteisessa koordinaatistossa.

  • Unohda ohjelmointi hetkeksi, piirrä kaavio auton liikkeestä ja selvitä matemaattinen pulma ensin. Mieti vasta sitten, miten saat ongelman ratkaisun kirjoitettua Scalaksi.

  • Sinun ei tarvitse mallintaa auton nopeutta. Riittää, kun huolehdit dokumentaation kuvaamista asioista.

Valinnainen vinkki tarvittavista laskutoimituksista

Trigonometriaa ei tarvita. Peruslaskutoimitukset (kerto-, yhteen-, jako-, vähennys-) riittävät.

Lisäksi voit hyödyntää luokan Pos metodeita. Esimerkiksi jo tuttu xDiff ja yDiff voivat olla hyödyllisiä, ja muitakin dokumentaatiossa kuvattuja voit maun mukaan ottaa avuksi.

Lisävinkkejä

Huomaa yksiköt: matkat metreinä, kulutus sataa kilometriä kohden.

Hankalin kohta saattaa olla sijainnin määrittäminen bensan loppuessa. Se onnistuu helpoimmin, kun lasket suhteen oikeasti ajetun matkan ja ajettavaksi määrätyn matkan (eli metodin toisen parametrin) välillä. Koska mallinnamme auton liikkeen suorana viivana kohti määränpäätä, auton kaksi koordinaattia muuttuvat samassa suhteessa.

6/6: Testaa lisää

Testaa ajamismetodia. Suosittelemme nytkin, että kokeilet sitä ensin carTest-pikkuohjelmassa, irrallaan varsinaisen CarSim-sovelluksen kokonaisuudesta.

Leiki sitten autoiluohjelmalla.

Miten autot ajavat CarSimissä?

Ehkä kummastelet, miten CarSimissä autolta sujuu mutkittelukin, vaikka toteutuksesi on suoraviivaisempi.

CarSim-sovellus kyllä käyttää juuri laatimaasi autoluokkaa mutta taiten: se kuvaa lähtösijainnin ja määränpään välisen mutkittelevan reitin pikkuisina suorina paloina ja kutsuu drive-metodiasi kullekin palaselle peräjälkeen. Jos haluat, saat palaset näkyviin CarSimin Settings-valikosta.

Entä reitin haku? Siihen CarSim käyttää Here.com:in verkkopalvelua, joka muistuttaa esimerkiksi Google-yhtiön tarjoamia sijaintipalveluita. Myös karttatiedot sovellus noutaa ilmaisesta verkko-APIsta.

Kaikki tämä on tehty CarSimin valmiina annetussa koodissa, jota voit silmäillä mutta joka on monin paikoin sellaista, ettei siitä ohjelmoinnin aloittelija saane vielä tolkkua. Joka tapauksessa sen havainnon voit tehdä, että ohjelma voi käyttää toisten tahojen tarjoamia palveluja apunaan.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Kuormittamisesta

Samannimisiä metodeita

Äskeissä tehtävässä oli kaksi fuel-metodia. Toinen otti yhden parametrin, toinen vain tyhjän parametriluettelon. Kuten viimeistään tästä näkyy, luokassa (tai yksittäisoliossa) voi mainiosti olla useita keskenään samannimisiä metodeita. Tämä on kuitenkin sallittua vain, jos samannimisillä metodeilla on erilaiset parametriluettelot.

Saman nimen käyttöä sanotaan kuormittamiseksi (overloading). Kyse on sinänsä ihan samasta kuin luvussa 1.7, jossa oli kaksi pystypalkki-nimistä funktiota; tällä kertaa vain kuormitit laatimasi autoluokan metodeita.

Metodin määrittelyyn kirjattu nimi sekä parametriluettelo kuuluvat metodin puumerkkiin (eli signatuuriin; signature). Kun kutsut olion metodia — olio.metodinNimi(parametrit) — suoritettava metodi määräytyy sen mukaan, minkä metodin puumerkki vastaa kutsussa esiintyviä tietoja. Kahta puumerkiltään identtistä metodia ei samaan luokkaan voi määritellä.

Periaatteessa kuormitetut metodit voisivat tehdä jotakin keskenään ihan erilaistakin, mutta yleinen käyttö kuormittamiselle on laatia erilaisia variaatioita samasta toiminnosta kuten autotehtävässä yllä.

Kuormittaminen ja paluuarvojen tyypit Scalassa

Luvussa 1.8 oli ensi kerran puhetta siitä, että minkä tahansa Scala-funktion koodiin voi kirjata paluuarvon tyypin. Joissakin tilanteissa tämä on myös pakollista, jotta Scalan automaattinen tyyppipäättely toimisi oikein. Ehkä yleisin tällainen pakkotilanne liittyy metodinimien kuormitukseen: kun metodi kutsuu toista samannimistä metodia, kutsuvalle "kaimalle" on määriteltävä paluuarvon tyyppi. Voit todeta asian itsekin seuraavasti.

Toteutitko parametrittoman fuel-metodin kutsumalla parametrillista fuel-metodia, kuten vinkattiin? Jos teit niin, voit nyt kokeilla poistaa tyyppimerkinnän (eli kaksoispisteen ja Double-sanan) metodin alusta. Saat virheilmoituksen overloaded method fuel needs result type eli "kuormitetulle metodille fuel tarvitaan paluuarvon tyyppi".

Ilmiö on helppo havaita myös REPLissä:

class Luokka:
  def metodi(eka: Int, toka: Int) = eka + toka
  def metodi(luku: Int) = this.metodi(luku, luku)-- Cyclic Error:
  |    def metodi(luku: Int) = this.metodi(luku, luku)
  |                            ^
  |                   Overloaded or recursive method metodi needs return type
class Luokka:
  def metodi(eka: Int, toka: Int) = eka + toka
  def metodi(luku: Int): Int      = this.metodi(luku, luku)// defined class Luokka

Tämä on nyt siis vain eräs Scalan pieni erikoisuus, ei sen keskeisempi asia. Onneksi virheilmoitus muistuttaa asiasta varsin selkeästi. Ja muista, että mille tahansa metodille saa aina kirjata paluuarvon tyypin koodiin.

Lisämateriaalia: ilmentymämuuttujia käsittelevistä metodeista

Nimeämishaaste: ilmentymämuuttuja vs. metodi

Keskenään samankaltaisia tilanteita:

  • Luvun 3.2 tilausluokassa halusimme, että kokonaishintaa voi tiedustella ulkopuolelta (metodilla kokonaishinta) mutta ei voi ulkoisesti muokata suoralla sijoituksella (muuttujaan lisattyjenHinta). Niinpä käytimme yksityisesti var-muuttujaa ja erillistä julkista metodia, joka palauttaa sen arvon.

  • Sovelsimme samaa ratkaisumallia myös FlappyBug-peliin, jossa ötökän ja esteen sijainnin muutokset tapahtuvat rajatusti luokan sisäisesti käyttämässä currentPos-muuttujassa. Luokkien käyttäjille tarjotaan julkinen pos-metodi.

  • Luvun 3.5 Match-luokassa maalimäärät kirjattiin yksityisiin muuttujiin (awayCount, homeCount). Ulkopuolelta vastaavat arvot sai selville metodeilla (awayGoals, homeGoals).

  • Ehkä teit jotakin samantapaista myös äskeisessä tehtävässä?

Yhteistä noille tilanteille on, että ulkopuolinen "saa katsoa muttei koskea" tiettyä tietoa. Luokkien laatijan on valittava kaksi nimeä, jotka molemmat kuvaavat tavallaan samaa asiaa: toinen metodille ja toinen yksityiselle muuttujalle.

Tähän ei ole mitään patenttiratkaisua, ja eri ohjelmoijat tekevät eri tavoin.

Jos kahta hyvää nimeä ei keksi, käytä parempaa julkisen metodin nimenä ja huonompaa yksityisen muuttujan nimenä. Näin asetetat etusijalle luokan käytön helppouden.

Jotkut harrastavat lyhenteitä sisäisten ilmentymämuuttujien nimissä. Tällöin pitää varoa, että nimien ymmärrettävyys säilyy!

Jotkut tykkäävät käyttää etuliitteitä ilmentymämuuttujien nimissä, esim. mBuyer (missä m = member eli luokan jäsen) tai alaviivallinen _buyer.

Eräillä kielillä ohjelmoidessa tyyli on usein tällainen: buyer (muuttuja) ja getBuyer (metodi). Tämä tyyli ei kuitenkaan priorisoi luokan käytön kätevyyttä. Scalassa ei ole tapana nimetä näin.

Useimmissa tämän kurssin ohjelmointitehtävissä julkiset nimet on määrätty etukäteen ja yksityiset voit keksiä itse.

Huomaa muuten, että tämä nimeämishaaste koskee nimenomaan var-muuttujia. val-muuttujat ovat yksinkertaisempia, koska val-muuttujaan ei voi sijoittaa uutta arvoa eikä tarvetta erilliselle metodille ole.

Kysyttyä: eikö Scalassa yleensä käytetä "gettereitä" ja "settereitä" kuten esimerkiksi Javassa?

Yllä oli esimerkkejä ohjelmista, joissa yksityisen var-muuttujan yhteyteen oli määritelty ns. "getteri" eli olion ominaisuuden arvon palauttava julkinen metodi. Kuitenkaan emme ole tehneet tällaisia "gettereitä" useimmille ilmentymämuuttujille.

Scala-ohjelmissa on yleistä jättää muuttujia julkisiksi, kun kyseessä on olion ulospäin näkyvä ominaisuus. "Getterit" ovat pikemminkin poikkeus kuin sääntö.

Tarkastellaan esimerkkiä:

class Profile(val name: String, var status: String):
  // metodit tänne

Yllä profiilin nimestä ja statustekstistä kirjaa pitävät muuttujat ovat julkisia. Niiden arvoja voi tutkia luokan ulkopuolelta ja jälkimmäiseen voi myös sijoittaa vaikkapa käskyllä munProfiili.status = "lomalla". Jos olisimme laatineet luokan enemmän "Java-tyyliin", olisimme kirjoittaneet pidemmin:

class Profile(private val name: String, private var status: String):

  def getName = this.name

  def getStatus = this.status

  def setStatus(status: String) =
    this.status = status

  // muut metodit tänne
end Profile

Ilmentymämuuttujat name ja status ovat yksityisiä.

Tarjotaan erilliset metodit profiiliolioiden ominaisuuksien tutkimiseen ja muuttamiseen.

Tällaisia "gettereitä" ja "settereitä" käytetään mm. Java-ohjelmoinnissa aivan jatkuvasti. Lukemattomien tyylioppaiden mukaan julkisia ilmentymämuuttujia tulisi Javassa karttaa kuin ruttoa, heinäsirkkoja ja raemyrskyä. Tämänkin kurssin muinaisella Java-kielisellä edeltäjällä käskettiin opiskelijoita näin:

../_images/java_xi-fi.png

Vanhan liiton ohjelmointia

Miksi moinen? Ja miksei tuo mahtikäsky esimerkiksi Scalassa päde, vaan voit huoleti kirjoittaa lyhyemmin? Onko Scalassa sittenkin kulissien takana eräänlaisia "gettereitä", vaikka emme niitä määrittelekään?

Näihin aiheisiin voit tutustua nettilähteiden kautta, jos siltä tuntuu. Tässä pari linkkiä:

Lisämateriaalia: konstruktoreista ja parametreista

Konstruktorin kuormittaminen

Tuossa oli puhetta metodien kuormittamisesta. Myös konstruktoria voi kuormittaa: voit määritellä erilaisia tapoja luoda luokasta ilmentymiä käyttäen erilaisia luontiparametreja. Tällä kurssilla tosin ei tarvitse.

Tehdään kokeeksi pieni luokka. Tarkoitus on ensinnäkin, että voimme luoda luokasta ilmentymän antaen luontiparametreiksi sanan ja luvun:

Kokeilu("kissa", 100)res0: Kokeilu = olio, jonka sana on kissa ja luku 100

Tällaisen luokanhan voi määritellä näin:

class Kokeilu(val sana: String, val luku: Int):
  override def toString = "olio, jonka sana on " + this.sana + " ja luku " + this.luku

Mutta entä jos haluamme, että voimme luoda olion myös niin, että annamme vain luvun, jolloin käytetään oletussanaa "laama"? Näin:

Kokeilu(10)res1: Kokeilu = olio, jonka sana on laama ja luku 10

Kolmantena vaihtoehtona voitaisiin antaa vain sana, jolloin käytetään oletuslukua 12345:

Kokeilu("koira")res2: Kokeilu = olio, jonka sana on koira ja luku 12345

Vaihtoehtoiset olionluontitavat järjestyvät konstruktoria kuormittamalla, mille on Scalassa oma merkintätapansa:

class Kokeilu(val sana: String, val luku: Int):

  def this(sana: String) = this(sana, 12345)

  def this(luku: Int) = this("laama", luku)

  override def toString = "olio, jonka sana on " + this.sana + " ja luku " + this.luku

end Kokeilu

Luokan alussa määritellään edelleen ensisijainen, "normaali" tapa luoda ilmentymä. Tässä siihen käytetään kahta parametria.

Merkintä def this aloittaa vaihtoehtoisen konstruktorin määrittelyn. Sen perässä olevissa sulkeissa ilmoitetaan, mitä parametreja tässä luontitavassa vastaanotetaan.

Merkintä this(…) tarkoittaa vapaasti muotoillen: "tee ilmentymä normaaliin tapaan käyttäen sulkeissa olevia tietoja luontiparametreina". Esimerkiksi tässä määritellään, että jos on annettu vain sana, niin luodaan olio käyttäen tuota sanaa ja lukua 12345.

Konstruktorin kuormittaminen ei kuitenkaan ole ainoa tai välttämättä kätevin tapa lisätä joustavuutta olion luomiseen: katso seuraava laatikko.

Oletusparametriarvot

Seuraavasta luokasta voi luoda ilmentymän joko käskyllä Kokeilu2("laama", 453534) tai pelkästään Kokeilu2("laama").

class Kokeilu2(val sana: String, val luku: Int = 12345):
  override def toString = "olio, jonka sana on " + this.sana + " ja luku " + this.luku

Jos jälkimmäistä parametria ei anneta, käytetään parametrimuuttujan yhteyteen kirjattua oletusparametriarvoa (default parameter value).

Oletusparametriarvoja voi määritellä konstruktorien lisäksi myös metodeille.

val/var-sanan pois jättäminen ja private

Tämä viimeinen lisätietolaatikko käsittelee suhteellisen vähäpätöistä nimenomaisesti Scala-kieleen liittyvää seikkaa. Lue tai ohita.

Etsitään vastaus tähän hyvään opiskelijan kysymykseen:

Aika selkee homma. Eli jos ei tee luontiparametreista ilmentymämuuttujia oliota luodessa niin ohjelma kaatuu, jos myöhemmässä vaiheessa yrittää käyttää metodia, joka yrittää käyttää luontiparametreja, eikö vain?

Vastaus ei ole ihan niin yksinkertainen kuin voisi toivoa. Siihen liittyy eräs Scalan pieni kummallisuus, johon pääsemme kiinni tämän pikkuesimerkin kautta:

class Luokka(val eka: Int, toka: Int):
  val kolmas = toka * toka
  def metodi = this.eka

Kyse on siitä, mitä tapahtuu, jos tuossa...

... luontiparametrin eka edessä ei lukisikaan val eikä var, mutta...

... metodi olisi silti määritelty käyttämään kyseistä muuttujaa?

Tai vastaavasti: mitä jos metodi käyttäisi eka-muuttujan sijaan myös toka-muuttujaa, jota ei ole kirjattu ilmentymämuuttujaksi val- tai var-sanalla?

Kysymyksen esittänyt opiskelija ehdottaa, että tästä seuraisi ohjelman kaatuminen ajonaikaiseen virheeseen. Vielä parempi voisi olla, jos tuollainen yritys tuottaisi käännösaikaisen virheilmoituksen ennen ohjelma-ajoa. Kumpikaan noista ei kuitenkaan ole se, mitä oikeasti Scalan sääntöjen mukaan tapahtuu.

Se, mitä oikeasti tapahtuu, selviää kohta, kunhan kerrataan nämä asiat luontiparametreista ja niiden yhteyteen määritellyistä ilmentymämuuttujista:

  • Jos luontiparametrin alkuun kirjoittaa vain val/var, sen arvo tulee sijoitetuksi ilmentymämuuttujaan. Tuo ilmentymämuuttuja on julkinen ellei toisin mainita.

  • Jos edessä on sana private, niin ilmentymämuuttujaa ei voi käyttää tuon luokan ulkopuolelta mutta luokan koodin sisältä voi, käsiteltiinpä sitten mitä tahansa tuon luokan ilmentymää.

  • Jos luontiparametrin edestä jättää kaikki määreet (val, var, private) pois, niin tuo parametri on tarkoitettu käytettäväksi ilmentymän alustavassa koodissa (joka kirjoitetaan yleensä siihen luokan määrittelyn alkuun ennen metodeita). Esimerkiksi yllä toka on tällainen muuttuja. Ilman val/var-sanaa ilmentymämuuttuja ei tule määritellyksi. Ainakaan tavallisesti. Mutta katso esimerkki alla.

class Luokka( eka: Int, toka: Int):
  val kolmas = toka * toka
  def metodi = this.eka

Koodi on muuten sama kuin yllä paitsi, että ekan edessä ei lue val. Tällainen luontiparametrimuuttuja olisi käytettävissä vain oliota luodessa eikä jäisi ilmentymämuuttujana olion osaksi...

... mutta jos tuollaista ilman val- tai var-sanaa määriteltyä parametria kuitenkin käytetään jostakin metodista, kuten tässä, niin tulee huomaamattomasti määritellyksi parametrin niminen yksityinen ilmentymämuuttuja.

Äskeinen koodi siis toimii samoin kuin jos parametrimuuttuja olisi esitelty muodossa private val eka: Int. O1-kurssilla emme kirjoita ohjelmia tuohon tyyliin.

Yhteenvetoa

  • Sovellus voi hyödyntää ulkoisten tahojen tarjoamia rajapintoja. Jotkin tällaiset rajapinnat eli APIt ovat tarjolla verkon yli.

    • Esimerkiksi tämän luvun sovellus käytti erään yhtiön karttapalvelua.

    • Verkkopalvelujen hyödyntämistä opetellaan kunnolla vasta jatkokursseilla.

  • Yleisille var-muuttujien muokkauskäskyille on tarjolla (mm.) Scalassa lyhennysmerkintöjä kuten += ja *=.

  • Metodinimiä voi kuormittaa: luokassa voi olla useita erillisiä metodeita, joilla on keskenään sama nimi mutta erilaiset parametriluettelot ja eri toteutukset.

    • Scalassa kuormitetulle metodille, joka kutsuu toista samannimistä, on erikseen kirjattava paluuarvon tyyppi ohjelmakoodiin.

  • Lukuun liittyviä termejä sanastosivulla: API; kuormittaa, puumerkki eli signatuuri; syntaktinen sokeri.

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, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó 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, Juha Sorva ja Jaakko Nakaza. 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; sitä 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 suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.

Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.

Lisäkiitokset tähän lukuun

CarSim-ohjelman muokkasi Here.com:in palvelua käyttäväksi kurssin pääassari emeritus Teemu Sirkiä.

Luvussa tehdään vääryyttä The Beatlesin musiikille. Kiitos ja anteeksi.

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