Luku 4.4: Olemattomuusharjoituksia
Tehtävä: pieni parannus limuautomaattiin
Saatat muistaa, että luvun 3.5 lopussa hieman kyseenalaistettiin laaditun
VendingMachine
-luokan metodin sellBottle
toteutusta.
Ota tuo luokka esiin Miscellaneous-moduulista ja muokkaa sitä. Korvaa sellBottle
uusitulla versiolla, jonka paluuarvo on tyyppiä Option[Int]
. Metodi ei siis enää
palauta miinus ykköstä epäonnistuneen oston merkiksi. Sen sijaan metodin tulee palauttaa
None
, jos pullon myynti epäonnistui, ja Some
-olioon kääritty vaihtorahan määrä, jos
onnistui.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tehtävä: Member
-luokka
Samassa Miscellaneous-moduulissa on muista erillinen esimerkkiluokka o1.people.Member
.
Ota esiin sen Scaladoc-dokumentaatio ja ohjelmakoodi. Huomaat, että koodi ei vastaa
dokumentaatiota: metodit isAlive
ja toString
puuttuvat. Toteuta ne.
Tehtävän voi ratkaista esimerkiksi match
-käskyllä, mutta kätevämmin se ratkeaa, kun
poimit pari sopivaa Option
-olioiden metodia käyttöösi edellisestä luvusta 4.3.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tehtävä: Passenger
-luokka
Tehtävänanto
o1.people
-pakkauksen dokumentaatiosta löytyy myös matkustajia kuvaava luokka
Passenger
. Sille ei kuitenkaan ole annettu lainkaan toteutusta. Toteuta luokka.
Ohjeita ja vinkkejä
Luokka hyödyntää toista luokkaa nimeltä
TravelCard
. Se on annettu valmiina. Älä muuta sitä.Dokumentaatio kuvaa
Passenger
-luokan luontiparametrit ja (niitä suoraan vastaavat) julkiset ilmentymämuuttujat. Tällä kertaa et tarvitse yksityisiä ilmentymämuuttujia.Kuten dokumentaatiokin kertoo, matkustajilla tulee olla
Option[TravelCard]
-tyyppinen ilmentymämuuttuja eli jokaisella matkustajalla on nolla tai yksi matkakorttia. Tässä siis kääritäänOption
iin viittaus omatekoisen luokanTravelCard
ilmentymään.Passenger
-luokalle ei ole tarjottu tiedostoa lainkaan, joten joudut luomaan sen tyhjästä pakkaukseeno1.people
esimerkiksi näin:Klikkaa tuota pakkausta IntelliJ’n Project-näkymässä hiiren oikealla napilla ja valitse New → Scala Class/File. Avautuu pieni ikkuna.
IntelliJ osaa pohjustaa kooditiedoston kätevästi, kunhan syötät hieman lisätietoja. Kirjoita Name-kohtaan luokalle nimeksi
Passenger
ja paina Enter.Editoriin avautuu uusi kooditiedosto, jossa on hieman alkua luokalle.
Metodien toteutukseen löytyy työkaluja luvusta 4.3.
Jos päädyt vaikeuksiin
isValid
-muuttujan käytön kanssa, voit katsoa lisävinkit alta.
Alkuvinkki isValid
-muuttujasta
Käytettävissäsi on Option[TravelCard]
-tyyppinen arvo.
Sillä ei ole isValid
-muuttujaa, vaan sen mahdollisesti
sisältämällä TravelCard
-oliolla on. Koodi
this.card.isValid
ei siis toimi.
Käsittele erikseen tapaukset, joissa Option
on Some
ja None
. Käytä Some
-olion sisällön isValid
-muuttujaa.
Jatkovinkki isValid
in käytöstä
Kun käsittelet Some
-tapauksen match
-käskyllä, saat
"napattua" Some
-olion sisällön muuttujaan, jonka
nimen voit valita itse (luku 4.3). Tuon muuttujan
kautta pääset käsiksi kyseisen TravelCard
-olion
isValid
-muuttujaan: muuttujanNimi.isValid
.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tehtävä: Tilaus
-luokan parantelu
Seuraava pieni tehtävä tarjoaa hieman lisäharjoitusta Option
-olioiden käsittelyssä
ja match
-käskyn käytössä. Mitään uutta tässä ei tule, mutta voit tehdä tehtävän,
jos äskeiset takeltelivat tai jos alempana olevia tehtäviä tehdessäsi toteat pienen
esiharjoituksen olevan paikallaan.
Lisätehtävä
Palaa Oliointro-moduulin Tilaus
-luokkaan.
Luvussa 2.6 oli pieni vapaaehtoinen tehtävä,
jossa tilauksille tehtiin kuvaus
-metodin sijaan toString
-metodi.
Jos et tehnyt tuota muutosta silloin, tee se nyt: vaihda kuvaus
-metodin
nimeksi toString
ja kirjoita override
eteen. Tee sama muutos myös
Asiakas
-luokkaan.
Muokkaa sitten luokkaa seuraavasti:
Lisää
Option[String]
-tyyppinen luontiparametriosoite
ja sitä vastaavaval
-ilmentymämuuttuja. Sitä käytetään tallentamaan (mahdollinen) osoite, johon tilaus toimitetaan asiakkaan osoitteen sijaan.Lisää parametriton, vaikutukseton metodi
toimitusosoite
, jonkaString
-tyyppinen paluuarvo kertoo, mihin tilaus toimitetaan. Se on tilaukselle erikseen kirjattu osoite, mikäli sellainen on, tai tilaajan osoite, ellei tilauksella ole erillistä osoitetta.Muokkaa
toString
-metodia niin, että sen palauttaman merkkijonon loppuun tulee lisäksi pilkku ja välilyönti", "
sekä joko merkkijono"asiakkaan osoitteeseen"
tai"osoitteeseen X"
, missä X on tilaukselle erikseen kirjattu osoite.(Huomaa, että
toString
-kuvauksen muodostuslogiikka on hieman erilainen kuintoimitusosoite
-funktion paluuarvon.)
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tehtävä: kätevää nuolinäppäilyä
Seuraava vapaaehtoinen tehtävä on mielekäs lähinnä, jos olet jo tehnyt Trotter
-tehtävän
luvusta 3.6. Voit toki tehdä tuon aimman lisätehtävän nytkin.
Direction.fromArrowKey
o1
-pakkauksessa on Direction
-luokan lisäksi samanniminen
yksittäisolio. Sillä on kätevä metodi
fromArrowKey
, joka toimii tähän tapaan:
val exampleKey = Key.UpexampleKey: o1.Key.Value = Up Direction.fromArrowKey(exampleKey)res0: Option[Direction] = Some(Direction.Up) Direction.fromArrowKey(Key.X)res1: Option[Direction] = None
Metodi siis palauttaa näppäintä vastaavan suunnan. Apuna se käyttää
Option
-luokkaa, koska vain osaa näppäimistä vastaa jokin suunta.
Muokkaa MoreApps-moduulin TrotterApp
-ohjelman onKeyDown
-metodia.
Sen pitää toimia kuin ennenkin, mutta saanet tehtyä sille aiempaa
yksinkertaisemman ja vähemmän toisteisen toteutuksen, kun käytät
fromArrowKey
-metodia.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tähtikarttatehtävä, osa 1/4: tähden perustiedot
Johdanto: tähtiä koordinaatistoissa
Laaditaan ohjelma, jolla voi kuvata tähtikarttoja eli näkymiä tähtitaivaasta. Kuhunkin tähtikarttaan sisältyy tähtiä; lisäksi tähtiä voi (kunhan ohjelmamme aikanaan valmistuu) yhdistää toisiinsa muodostaen tähtikuvioita.
Tässä tehtäväsarjan ensimmäisessä osassa ei vielä piirretä mitään, vaan luodaan väline yksittäisten tähtien tietojen mallintamiseksi.
Nouda moduuli Stars. Nyt ajankohtaisia luokkia ovat Star
ja StarCoords
, joista
ensimmäisen tulet itse toteuttamaan jälkimmäistä valmiina annettua luokkaa apuna käyttäen.
Lue näiden luokkien dokumentaatio; älä välitä muista luokista.
Kuten dokumentaatiokin kertoo, tämä ohjelma käsittelee kahdenlaisia kaksiulotteisia koordinaatteja:
Kunkin tähden sijainti tähtikartalla kuvataan
StarCoords
-tyyppisenä koordinaattiparina.Tässä yhteydessä käytämme matematiikasta tuttua koordinaatistoa, jossa y-arvot kasvavat yläreunaa kohti.
Sekä x- että y-arvot on normalisoitu välille [-1.0...+1.0]; ks. kuva. Nämä arvot kuvaavat tähden sijaintia näkyvällä taivaalla riippumattomasti siitä, minkäsuuruisiin kuviin tähtikartta saatetaan piirtää.
Toisaalta
StarCoords
-luokan metoditoImagePos
-metodi osaa tuottaaPos
-tyyppisen koordinaattiparin, joka kuvaa tähden sijaintia tähtikartan kuvassa. Nämä koordinaatit kasvavat graafiselle ohjelmoinnille tyypilliseen tapaan kuvan vasemmassa yläkulmassa sijaitsevasta origosta oikealle ja alas. Ne kertovat suoraan, mihin pikseliin tähden kuvan keskipiste tulisi piirtää, jos tähtitaivasta kuvataan tietynkokoisena kuvana.
Tehtävänanto
Varmista yllä annetun kuvauksen ja dokumentaation perusteella, että ymmärrät käytetyt kaksi koordinaatistoa. Varmista, että ymmärrät, mitä
StarCoords
-luokantoImagePos
-metodi tekee.Toteuta sitten luokan
Star
puuttuvat metodit dokumentaation mukaisiksi.
Ohjeita ja vinkkejä
Star
-luokan pitäisi siis lopulta toimia tähän tapaan:val unnamedStar = Star(28, StarCoords(0.994772, 0.023164), 0.1, None)unnamedStar: o1.stars.Star = #28 (x=0.99, y=0.02) unnamedStar.posIn(rectangle(100, 100, Black))res2: o1.world.Pos = (99.7386,48.8418) unnamedStar.posIn(rectangle(200, 200, Black))res3: o1.world.Pos = (199.4772,97.6836) val namedStar = Star(48915, StarCoords(-0.187481, 0.939228), -1.44, Some("SIRIUS"))namedStar: o1.stars.Star = #48915 SIRIUS (x=-0.19, y=0.94)
Sinun ei varsinaisesti tarvitse itse toteuttaa tarvittavaa matematiikkaa, kun hyödynnät
StarCoords
-luokkaa.Sinun ei myöskään tarvitse pyöristää itse tähden koordinaatteja, vaikka
toString
-metodin tuleekin ne pyöristettyinä esittää.StarCoords
-luokantoString
-metodi hoitaa homman puolestasi, kunhan käytät sitä.
Desimaalilukujen muotoilua merkkijonoksi
Voit tutustua StarCoords
-luokan toString
-metodiin, joka
muotoilee koordinaatteja kahden desimaalin tarkkuuteen.
Lisätietoja siellä käytetystä f
-alkuisesta merkinnästä löydät
merkkijonoupotuksen eri muotoja kuvailevasta artikkelista.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tähtikarttatehtävä, osa 2/4: tähdet kuvaksi
Tehtävänanto
Stars-moduuliin kuuluu pakkaus o1.stars.gui
. Sen tiedostossa skypics.scala
määrittelee
funktioita, joilla voi muodostaa kuvia tähtikartoista.
Tässä pikkutehtävässä keskitymme funktioon placeStar
. Sille voi antaa kuvan ja tähden,
ja se palauttaa uusitun version kuvasta, johon myös kyseinen tähti on piirretty.
(Piirrämme tähtitaivaan tähdet ympyröinä, ei sakarallisina.)
Sanotaan vaikkapa, että haluamme kuvan, johon on piirretty nämä kaksi tähteä:
val unnamedStar = Star(28, StarCoords(0.994772, 0.023164), 0.1, None)unnamedStar: o1.stars.Star = #28 (x=0.99, y=0.02) val namedStar = Star(48915, StarCoords(-0.187481, 0.939228), -1.44, Some("SIRIUS"))namedStar: o1.stars.Star = #48915 SIRIUS (x=-0.19, y=0.94)
Homman pitäisi järjestyä näin:
val darkBackground = rectangle(500, 500, Black)darkBackground: Pic = rectangle-shape val skyWithOneStar = placeStar(darkBackground, unnamedStar)skyWithOneStar: Pic = combined pic val skyWithTwoStars = placeStar(skyWithOneStar, namedStar)skyWithTwoStars: Pic = combined pic skyWithTwoStars.show()
Tuo placeStar
-funktio on kuitenkin toteuttamatta. Lue sen spesifikaatio dokumentista ja
toteuta funktio tiedostoon skypics.scala
merkittyyn kohtaan.
Ohjeita ja vinkkejä
Tehtävässä ei ole mitään vaikeaa, kunhan hyödynnät edellisen tehtävän esittelemää työkalustoa.
Kun koodisi toimii, äskeinen esimerkki tuottaa kuvan yläosaan yhden tähden (namedStar
)
ja aivan oikeaan laitaan keskelle toisen pienemmän (unnamedStar
).
Lopuksi
Tehtävän tehtyäsi voit todeta, että noin niitä yksittäisiä tähtiä tosiaan kuvaan saa, mutta kovin kätevää kuvan luominen ei ole, jos tähtiä pitäisi saada esiin muutamaa enemmän.
Kätevämpää olisi, jos tähtien tiedot voisi ladata kerralla jostakin, mihin ne on
kirjattu. Niin pian teemmekin. (Voit jo silmäillä kansioiden test
ja northern
sisältöä.
Katso etenkin niistä löytyviä stars.csv
-tiedostoja. Palaamme asiaan luvussa 5.2.)
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Football3-tehtävä
Futistulosohjelmamme (luvusta 3.5) meni jo kerran uusiksi (luvussa 4.2), ja nyt niin käy taas.
Tehtävänanto
Nouda moduuli Football3. Tutustu sen dokumentaatioon. Luokan Match
spesifikaatio on
nyt osin erilainen kuin ennen, ja luokka Season
on uusi tuttavuus.
Laadi näille luokille dokumentaation mukaisesti toimivat toteutukset.
Tehtävä palautetaan kahdessa vaiheessa: ensin Match
ja sitten Season
.
Ohjeita ja vinkkejä: uusittu Match
-luokka
Voit kopioida
Match
-luokan toteutuksen pohjaksi Football2-moduuliin laatimasi koodin. Varmista vain, että koodin alkuun tuleepackage o1.football3
-pakkausmäärittely (eikäfootball2
).Aiemmin toteutetun
Match
-luokan voit säilyttää ennallaan, paitsi voittajametodit. Niihin tulee muutoksia:winnerName
-metodin lisäksi on metodiwinner
, joka palauttaaOption
-arvon.winningScorerName
-metodia ei enää ole; sen tilalle tulee metodiwinningScorer
, joka palauttaaOption
-arvon.(Kun
winner
-metodisi toimii, kokeile käyttää sitä apuna, kun laadit yksinkertaisemman toteutuksenwinnerName
-metodille.)
Muista:
Match
isolla on ottelua kuvaavalle luokalle valittu nimi.match
pienellä on Scalan käsky (jota ei voi käyttää nimenä; se on varattu sana). Näiden sotkeminen toisiinsa voi tuottaa jännittäviä virhetilanteita.Voit jälleen käyttää testausapuna annettua
FootballApp
-ohjelmaa.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Ohjeita ja vinkkejä: Season
-luokka
Kun
Season
-luokkakin on tehty,FootballApp
-ohjelma näyttää pääikkunassaan kauden tilastoja ja otteluluettelon.Season
-luokkaa toteuttaessasi voi olla apua lukujen 4.2 ja 4.3 ja GoodStuff-moduulin tutkimisesta.Season
in ja GoodStuff`inCategory
-luokan välillä on yhtäläisyyksiä.Alta voit halutessasi avata muutaman lisävinkin.
Yleinen vinkki biggestWin
-metodiin
GoodStuffin Category
-luokassa pidettiin kirjaa kokemuksesta,
jolla oli korkein arvosana. Season
-luokassa taas pidetään
kirjaa ottelusta, jolla on suurin voittomarginaali. Ohjelmat
muistuttavat tässä suhteessa kovasti toisiaan.
Suurimman voiton (biggestWin
) selvittämisessä voit hyödyntää
Option
-tyyppistä ilmentymämuuttujaa sopivimman säilyttäjän
roolissa juuri samaan tapaan kuin teimme Category
-luokassa.
Voit päivittää sitä addResult
-metodissa vastaavasti kuin
Category
-luokka tekee addExperience
-metodissaan.
Vinkkejä otteluiden vertailemiseen keskenään
Huomasithan, että otteluiden goalDifference
-metodi voi palauttaa
myös negatiivisen luvun? Isoin voittomarginaali on ottelussa,
jonka goalDifference
n itseisarvo on suurin.
Itseisarvon laskemiseen voit käyttää funktiota scala.math.abs
.
Voit tehdä vertailun joko addResult
-metodissa itsessään tai
määrittää sitä varten apumetodin, jota kutsut.
Vinkki tilanteeseen, jossa ohjelmasi kaatuu IndexOutOfBounds
-virheeseen
Indeksivirhe tarkoittaa, että olet käyttänyt liian suurta tai (tässä luultavammin) liian pientä indeksiä.
Huomioitko myös tapauksen, jossa otteluita ei vielä ole lainkaan?
Ellei ratkea, katso myös seuraava vinkki.
Vinkki matchNumber
- ja latestMatch
-metodeihin
Näiden metodien tulee palauttaa ottelu Option
-kääreessä.
Et tarvitse siihen if
-käskyjä tai muutakaan monimutkaista.
Luvussa 4.3 esiteltiin metodi, jolla voit poimia alkion
kokoelmasta "turvallisesti" Option
-muodossa.
Huomasitko muuten, että latestMatch
on oikeastaan erikoistapaus
matchNumber
ista? Voit toteuttaa edellisen kutsumalla jälkimmäistä.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Pohdittavaa
Dokumentaatiossa erikseen sanottiin, että Season
-olioon lisättävien
otteluiden oletetaan jo päättyneen. Niihin ei siis enää saa lisätä
maaleja. Mitä tapahtuu, jos tätä oletusta rikotaan? Miten vastaavan
sovelluksen voisi laatia niin, ettei tuota vaaraa ole?
Lisämateriaalia: monipuolinen match
-käsky
Seuraavat laatikot kertovat lisää match
-käskystä, jota olemme käyttäneet Option
-olioiden käsittelyyn. Tämä ei ole kurssin kannalta keskeistä asiaa mutta kiinnostanee
ainakin niitä aiemmin ohjelmoineita lukijoita, jotka haluavat nyt oppia Scala-kieltä
mahdollisimman monipuolisesti. Aloitteleva ohjelmoija voi mainiosti ohittaa seuraavan
ja opetella nämä asiat joskus myöhemmin.
match
on hahmonsovituksen työkalu
match
-käskyn yleinen muoto on:
lauseke L match case hahmo A => koodia, joka suoritetaan, jos L:n arvo sopii hahmoon A case hahmo B => koodia, joka suoritetaan, jos L:n arvo sopii hahmoon B (muttei A:han) case hahmo C => koodia, joka suoritetaan, jos L:n arvo sopii hahmoon C (muttei A:han tai B:hen) Ja niin edelleen. (Usein katetaan kaikki mahdolliset tapaukset.)
... ns. hahmoihin (pattern), joilla kuvataan
erilaisia tapauksia. Toistaiseksi olemme
määritelleet tapaukset käyttäen None
- ja
Some
-hahmoja, mutta tässä kohdassa voi käyttää
monenmoisia muitakin eri hahmoja, joista eräitä
on esitelty alla.
Alkeellista match
äystä literaaleilla
Oletetaan, että on Int
-tyyppinen muuttuja nimeltä luku
. Tutkitaan
vaikkapa lausekkeen luku * luku * luku
arvoa match
-käskyllä:
val kuutionKuvaus = luku * luku * luku match
case 0 => "luku on nolla ja niin sen kuutiokin"
case 1000 => "kympistä tulee tuhat"
case muuKuutio => "luku " + luku + ", jonka kuutio on " + muuKuutio
Tässä esimerkissä on kolme eri hahmoa, joihin lausekkeen arvoa yritetään sovittaa järjestyksessä, kunnes sopiva löytyy.
Hahmona voi käyttää literaalia; tässä on
käytetty Int
-literaaleja. Näistä tapauksista
ensimmäinen valitaan, jos luvun kuutio oli
nolla, toinen jos se oli tuhat.
Hahmoksi voi myös kirjoittaa uuden muuttujanimen;
tässä on valittu nimi muuKuutio
. Tällainen
tapaus sopii yhteen minkä tahansa arvon kanssa
ja tulee siis tässä valituksi ellei kuutio ollut
nolla eikä tuhat.
Kun tällainen tapaus kohdataan, syntyy uusi paikallinen muuttuja, jonka arvoksi tapaukseen "osunut" arvo tallentuu. Muuttujan nimeä voi käyttää tapauksen koodissa.
Tässä vielä vastaava esimerkki, jossa Int
-literaalien sijaan käsitellään Boolean
-literaaleja. Seuraavat koodinpätkät saavat aikaan ihan saman:
if luku < 0 then "negatiivinen" else "ei negatiivinen"
luku < 0 match
case true => "negatiivinen"
case false => "ei negatiivinen"
Kaiken äskeisen saa aikaan luvusta 3.4 tutuilla if
–else
-ketjuillakin. Näin
yksinkertaisesti käytettynä match
ei vielä ihan pääse oikeuksiinsa. Mutta luepa
alta lisää.
Kysyttyä: onko match
-käsky suunnilleen sama kuin esim. Javan switch
?
Javassa ja joissakin muissa kielissä on switch
-niminen käsky,
jolla voi valita yhden useasta vaihtoehdosta sillä perusteella, mikä
nimenomainen arvo tietyllä lausekkeella on.
Scalan match
illä on yhtäläisyyksiä tuon käskyn kanssa. Kuitenkin
switch
pystyy ainoastaan valitsemaan tapauksen, joka vastaa
yksittäistä arvoa (kuten kuutionKuvaus
-esimerkissämme).
match
tarjoaa monipuolisempia mahdollisuuksia hahmonsovitukseen.
Erityisen huomionarvoista on, että match
-käskyllä voi
tehdä valinnan tyypin perusteella, ja
"purkaa" tarkasteltavan olion hahmon mukaisesti ja poimia olion osia paikallisiin muuttujiin.
Muun muassa näistä mahdollisuuksista on esimerkkejä alla.
Lisäehto tapaukselle
Hahmon yhteyteen voi määritellä lisäehdon (pattern guard), jonka pitää myös täyttyä, jotta kyseinen tapaus tulisi valituksi.
val kuutionKuvaus = luku * luku * luku match
case 0 => "luku on nolla ja niin sen kuutiokin"
case 1000 => "kympistä tulee tuhat"
case muu if muu > 0 => "positiivinen kuutio " + muu
case muu => "negatiivinen kuutio " + muu
Tämä lisäehto rajaa tapausta: kyseinen tapaus
valitaan vain, jos kyseessä on arvo, joka on
nollaa suurempi (eikä ole 1000, jonka edellinen
tapaus jo kattoi). Tässä käytetään match
-käskyn
osana samaa if
-sanaa kuin tutussa erillisessä
if
-valintakäskyssäkin.
Viimeiseen tapaukseen päädytään nyt vain silloin, jos kyseessä ei ole nolla, tuhat eikä mikään positiivinen luku.
Alaviiva match
-hahmoissa
Alaviivaa voi käyttää match
-käskyssä tarkoittamaan "mikä vain"
tai "ei väliä mikä". Tässä pari esimerkkiä:
luku * luku * luku match
case 0 => "luku on nolla ja niin sen kuutiokin"
case 1000 => "kympistä tulee tuhat"
case _ => "joku muu kuin nolla tai tuhat"
Alaviivahahmo sopii mihin tahansa arvoon ja tulee valituksi, jos kumpikaan edellisistä ei tullut. Tähän olisi voinut kirjoittaa uuden muuttujan nimen (kuten ylempänä tehtiinkin), mutta jos muuttujalle ei ole käyttöä, pelkkä alaviiva kelpaa.
match
äystä tyypin mukaan
Äskeisissä esimerkeissä hahmot vastasivat erilaisia mutta keskenään samantyyppisiä arvoja. Seuraavassa hahmot ovat keskenään erityyppisiä:
def kokeilu(jonkinlainenArvo: Matchable) =
jonkinlainenArvo match
case jono: String => "kyseessä on merkkijono " + jono
case luku: Int if luku > 0 => "kyseessä on positiivinen kokonaisluku " + luku
case luku: Int => "kyseessä on ei-positiivinen kokonaisluku " + luku
case vektori: Vector[?] => "kyseessä on vektori, jossa on " + vektori.size + " alkiota"
case _ => "kyseessä on jokin sekalainen arvo"
Esimerkkifunktiomme parametrityyppi on Matchable
, mikä tarkoittaa
että sille voi antaa minkä tahansa "match
-kelpoisen" arvon
parametriksi.
Hahmoihin on kirjattu tietotyyppi. Kukin näistä hahmoista tärppää vain, jos tutkittava arvo on kyseistä tyyppiä.
Hahmojen määrittelemillä muuttujilla on vastaavat tyypit.
Esimerkiksi vektori
-niminen muuttuja on Vector
-tyyppinen,
ja sen kautta voimme esimerkiksi kutsua vektorien size
-metodia.
Olion "purkaminen" match
-käskyllä
Yksi match
-käskyn näppärimmistä ominaisuuksista on, että hahmossa voi
"purkaa" tutkittavan olion, mikäli se sopii kyseiseen hahmoon, ja poimia
sen osia talteen muuttujiin. Yksinkertainen esimerkki tästä on tuttu
Some
-kääreen "purkaminen":
lukuvektori.lift(4) match
case Some(kaaritty) => "luku " + kaaritty
case None => "ei lukua"
Hahmossa määritellään rakenne: jos kyseessä on
Some
, niin sen sisällä on jokin arvo. Tuo
arvo "puretaan esiin" ja poimitaan muuttujaan
kaaritty
.
Tätä tekniikkaa sopii yhdistellä muihin esiteltyihin. Alla puretaan Option
ja samalla koetetaan sovittaa sen mahdollinen sisältö johonkin useasta eri
tapauksesta:
lukuvektori.lift(4) match
case Some(100) => "nimenomaan luku sata"
case Some(kaaritty) if kaaritty % 2 == 0 => "muu parillinen luku " + kaaritty
case Some(pariton) => "pariton luku " + pariton
case None => "ei lukua"
Olion "purkaminen" onnistuu vain, jos kyseiselle luokalle on määritelty, miten
sentyyppiset olio puretaan. Tällainen määrittely on monilla Scalan valmiilla
luokilla, esimerkiksi Some
-olioilla. Vastaavasti O1-kirjaston Pos
-tyypille on
määritelty, että sen voi purkaa kahdeksi koordinaatiksi kuten tässä:
jokuPos match
case Pos(x, y) if x > 0 => "koordinaatit, joiden x on positiivinen ja y on " + y
case Pos(_, y) => "muu koordinaattipari, jossa y on " + y
"Jos kyseessä on Pos
, se on purettavissa kahdeksi
luvuksi. Tallenna ne paikallisiin muuttujiin x
ja y
.
Jos näistä x
on positiivinen, valitaan tämä tapaus."
"Pura Pos
kahdeksi luvuksi, joista ensimmäisellä voi
heittää vesilintua ja toinen tallennetaan paikalliseen
muuttujaan y
." Tämä hahmo sopii kaikkiin mahdollisiin
Pos
-arvoihin ja tulee väistämättä valituksi, jos
ensimmäinen tapaus ei tärpännyt.
Miten purkamistapa sitten määritellään? Helpoin tapa on tehdä luokasta ns. tapausluokka (case class), mistä on tässä yksinkertainen esimerkki:
case class Album(val name: String, val artist: String, val year: Int):
// ...
Luokkamäärittely on ihan tavallinen paitsi että
alussa on sana case
.
Tapausluokan luontiparametrit määräävät
samalla, millaisiin osiin match
-käskyllä voi
olion purkaa. Albumiolion voi purkaa nimeksi,
artistiksi ja vuodeksi.
Käyttöesimerkki:
jokinAlbumiolio match
case Album(_, _, vuosi) if vuosi < 2000 => "muinainen"
case Album(nimi, tekija, vuosi) => tekija + ": " + nimi + " (" + vuosi + ")"
Hahmot määritellään käyttäen tapausluokan nimeä ja luokan luontiparametreja vastaavia osasia.
Lisätietoja löytyy netistä esimerkiksi hakusanoilla Scala pattern matching ja Scala case class. Kuten todettu, tässä esiteltyjä Scala-kielen ominaisuuksia ei ole O1-kurssilla pakko osata käyttää.
Lisämateriaalia: Option
-olioiden get
-metodista
Tässä vapaaehtoisessa kappaleessa esitellään eräs Option
-olioiden metodi, get
. Tämä
metodi voi vaikuttaa kätevältä, mutta se on petollinen ja sen käyttöä on syytä välttää.
Ja itse asiassa sen käyttö O1:n varsinaisissa ohjelmointitehtävissä on sekä tarpeetonta
että kiellettyä. Metodi esitellään tässä vain varoituksena, koska opiskelijat joskus
törmäävät siihen muissa verkkomateriaaleissa ja sitten käyttävät sitä aivan turhaan ja
koodin laatua huonontaen.
Vaarallinen get
-metodi
Yksi tapa avata Option
-kääre on kutsua sen parametritonta
get
-metodia. Kokeile itse REPLissä.
Esimerkiksi Category
-luokan addExperience
-metodissa olisimme
periaatteessa voineet käyttää tätä metodia match
-käskyn sijaan:
def addExperience(newExperience: Experience) =
this.experiences += newExperience
val newFave =
if this.fave.isEmpty then
newExperience
else
newExperience.chooseBetter(this.fave.get)
this.fave = Some(newFave)
Tutkitaan vanhan suosikin olemassaoloa
isEmpty
-metodilla.
Poimitaan vanha suosikki Option
-kääreestä.
Tämä tehdään vain else
-haarassa, joten on
varmaa, että kyseessä ei ole None
-arvo.
Option
-olion get
-metodissa kuitenkin piilee osa samasta vaarasta
kuin null
-viittauksissakin: jos metodia kutsuu None
-arvolle,
syntyy ajonaikainen poikkeustilanne. Jää ohjelmoijan muistettavaksi,
että ennen get
-metodin kutsumista pitää aina varmistaa, että
Option
-arvolla todella on sisältö. Tämä unohtuu helposti.
Opiskelijan sanoin:
Käärettä ei voi avata, jos karkki puuttuu.
Älä ainakaan ryntää suin päin avaamaan sitä kuten get
tekee.
Varaudu avatessa mahdolliseen pettymykseen, ettei tule itku.
On ihan hyvä tietää, että get
-metodi on olemassa. Saatat
nähdä sitä käytetyn muiden laatimissa ohjelmissa. Kaikki muiden
kirjoittamat ohjelmat eivät ole laadukasta koodia. Jätä itse
tämä metodi käyttämättä. Sen voi aina korvata paremmalla
ratkaisulla, esimerkiksi match
-käskyllä tai getOrElse
-metodikutsulla.
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.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
match
-käskyllä voi tutkia minkä tahansa lausekkeen arvoa. Teknisemmin sanoen kyseessä on hahmonsovitus (pattern matching): lausekkeen arvoa verrataan...