Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 8.4: Robotteja ja olemattomia arvoja
Tästä sivusta:
Pääkysymyksiä: Miten käytän Option
-tyyppiä kätevästi?
Miten saan luvun 8.3 robottiohjelman täydennettyä toimivaksi?
Mitä käsitellään? Naitamme Option
in ja kokoelmien korkeamman
asteen metodit. Työstämme robottisimulaattoria eteenpäin.
Mitä tehdään? Luetaan ja ohjelmoidaan. Lopussa läjä kokoavia monivalintoja.
Suuntaa antava työläysarvio:? Kolme tuntia.
Pistearvo: B75.
Oheismoduulit: Robots.
![../_images/person10.png](https://gitmanager.cs.aalto.fi/static/O1_2023/_images/person10.png)
Robottien toimintavuorot
Robottimaailman advanceTurn
- ja advanceFullRound
-metodien olisi tarkoitus pyörittää
toimintavuoroa robottien välillä. Niitä avustaa RobotBody
-luokan takeTurn
-metodi,
joka määrää robotin käyttämään yhden toimintavuoron.
Lähdetään toteuttamaan viimeksi mainittua. RobotBody
-luokasta löytyy metodiaihio.
def takeTurn() =
if this.isIntact then
// TODO: call the brain's controlTurn method (if there is a brain)
Se, miten ehjä robotti käyttää vuoronsa, riippuu siitä, millainen aivo siihen on kytketty (jos mitään). Robotti kutsuu aivonsa metodia, mikä pitäisi toteuttaa tähän.
Tämä pikkutehtävä toimii kertauksena ja johdattaa seuraaviin aiheisiin:
Olisi kiva, jos yksinkertaisen asian voisi sanoa yksinkertaisesti. Muun muassa siksi
pidämme nyt tauon roboteista ja opettelemme hieman uutta. Palaamme sitten robottimaailmaan
Option
tanassa.
Luvunetsimisongelma
Otetaan erillinen esimerkki. Sanotaan vaikkapa, että haluamme löytää kokonaislukuvektorista ensimmäisen suuren luvun (missä suuri tarkoittakoon yli 10000) sekä ensimmäisen negatiivisen luvun. Haluamme lisäksi selvittää, ovatko nuo löydetyt luvut parillisia.
Etsiminen sujuu kokoelmaolion find
-metodilla. Tämä metodi palauttaa Option
-tyyppisen
arvon:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) val isoJosLoytyi = luvut.find( _ > 10000 )res0: Option[Int] = None val negatiivinenJosLoytyi = luvut.find( _ < 0 )res1: Option[Int] = Some(-20)
Option[Int]
-tyyppiselle oliolle eli "luvulle, joka ehkä on ehkä ei", ei voi määrittää
jakojäännöstä %
-operaattorilla:
negatiivinenJosLoytyi % 2 == 0-- Error: ... value % is not a member of Option[Int]
Ongelma on siis sama kuin äsken takeTurn
-metodissa: haluamme suorittaa toimenpiteen
vain, jos tarvittava arvo on olemassa.
Ongelman jäsennys
On kolme eri tapausta: luku löytyi ja on parillinen, luku löytyi ja on pariton, tai lukua ei löytynyt, joten sen parillisuus on määrittelemätön. On laadittava koodi niin, että nämä kaikki kolme tapausta tulevat käsitellyiksi.
Tapauksien esittämiseen sopii tietotyyppi Option[Boolean]
. Päätetäänkin siis tuottaa
tuloksia näin:
Jos
find
palauttaaNone
, niin lukua ei löytynyt, joten tuloskin onNone
.Jos
find
palauttaaSome
, ja sen sisällä on parillinen luku, niin tuotetaan tuloksenaSome(true)
.Jos
find
palauttaaSome
, ja sen sisällä ei ole parillinen luku, niin tuotetaan tuloksenaSome(false)
.
Yksi mahdollisuus olisi käyttää match
-käskyä:
Kelvollinen ratkaisu match
illä
val isoJosLoytyi = luvut.find( _ > 10000 )
val isonParillisuus = isoJosLoytyi match
case Some(loytynytIso) => Some(loytynytIso % 2 == 0)
case None => None
Kuten aiempi takeTurn
-toteutus, tämäkin ohjelmakoodi aika monisanainen, kun ottaa
huomioon, kuinka yksinkertaisesta tarpeesta on kysymys. Helpommallakin pääsee. Otetaan
uusi näkökulma Option
-luokkaan.
Option
alkiokokoelmana
Option
-tyyppinen olio on itse asiassa kokoelma: se sisältää jonkin määrän tietyntyyppisiä
alkioita. Se on vain kokoelmaksi kovin yksinkertainen: alkioita on joko nolla tai yksi.
None
on nolla-alkioinen alkiokokoelma, ja Some(x)
on yksialkioinen kokoelma, jossa
alkiona on x
.
Myös Scala-kirjastojen suunnittelijat ovat ajatelleet Option
-luokkaa kokoelmatyyppinä.
Luokka on nimittäin laadittu niin, että sillä on koko liuta aivan samoja metodeita kuin
muillakin kokoelmilla. Niiden kutsuminen kätevöittää Option
-luokan käyttöä usein kummasti.
Kokeillaan ensin vaikkapa Some
-oliolla eli yksialkioisella kokoelmalla.
Some
alkiokokoelmana
Kun kutsumme Some
lle foreach
-metodia, parametrifunktio suoritetaan Some
-olion
sisällölle:
val kokeilu = Some("Täs mä nyt oon")kokeilu: Some[String] = Some(Täs mä nyt oon) kokeilu.foreach(println)Täs mä nyt oon
filter
-metodi palauttaa alkuperäisen Some
n tai None
:
kokeilu.filter( _.length < 100 )res2: Option[String] = Some(Täs mä nyt oon) kokeilu.filter( _.length >= 100 )res3: Option[String] = None
exists
- ja forall
-metodit toimivat luonnolliseen tapaan:
kokeilu.exists( _.length >= 100 )res4: Boolean = false kokeilu.exists( _.length < 100 )res5: Boolean = true kokeilu.forall( _.length < 100 )res6: Boolean = true
map
-metodilla voi tuottaa toisen Some
-olion, jossa on alkuperäisen Some
-olion
sisältämän arvon perusteella laskettu toinen arvo:
kokeilu.map( _.toUpperCase + "!!!" )res7: Option[String] = Some(TÄS MÄ NYT OON!!!)
None
alkiokokoelmana
Kokeillaan sitten None
-oliolla eli tyhjällä kokoelmalla. Kuten muillekaan tyhjille
kokoelmille, foreach
-metodi ei tee None
-oliolle mitään:
val kokeilu2: Option[String] = Nonekokeilu2: Option[String] = None kokeilu2.foreach(println)
filter
ja map
palauttavat aina None
, koska minkäänlaista alkiota ei ole:
kokeilu2.filter( _.length < 100 )res8: Option[String] = None kokeilu2.map( _.toUpperCase + "!!!" )res9: Option[String] = None
exists
palauttaa vastaavasti aina false
:
kokeilu2.exists( _.length >= 100 )res10: Boolean = false kokeilu2.exists( _.length < 100 )res11: Boolean = false
forall
-puolestaan palauttaa aina true
, koska mikä tahansa ehto pitää paikkansa
kaikille nollalle alkiolle. Tai ehkä paremmin sanoen: kun alkioita on nolla, ei ole
yhtään sellaista arvoa, jolle annettu ehto ei olisi voimassa, oli ehto sitten mikä
tahansa.
kokeilu2.forall( _.length >= 100 )res12: Boolean = true
Luvunetsimisongelman näppärämpi ratkaisu
Oletetaan taas annetuiksi nämä käskyt:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) val isoJosLoytyi = luvut.find( _ > 10000 )res13: Option[Int] = None val negatiivinenJosLoytyi = luvut.find( _ < 0 )res14: Option[Int] = Some(-20)
Annetaan map
-metodille parametriksi parillisuuden selvittävä funktio. Toisin sanoen
käsketään: "Selvitä parillisuus jokaiselle find
-metodin palauttaman Option
-olion
sisältämälle alkiolle."
isoJosLoytyi.map( _ % 2 == 0 )res15: Option[Boolean] = None
Nyt jos sattuu käymään niin, että Option
on tyhjä, on tuloskin None
kuten yllä.
Jos taas Option
-kääre sisältää arvon, sovelletaan parillisuudenselvitysfunktiota
tuohon arvoon ja saadaan tulos Some
-olion sisällä:
negatiivinenJosLoytyi.map( _ % 2 == 0 )res16: Option[Boolean] = Some(true)
map
-metodia käyttämällä voimme siis huomioida kaikki kolme tapausta: "löytyi
parillinen", "löytyi pariton" ja "ei löytynyt mitään".
Yllä käytettiin ensiesimerkin selkiyttämiseksi välivaiheita, mutta lyhyemminkin voi toki kirjoittaa:
luvut.find( _ > 10000 ).map( _ % 2 == 0 )res17: Option[Boolean] = None luvut.find( _ < 0 ).map( _ % 2 == 0 )res18: Option[Boolean] = Some(true)
tempo
-esimerkki
Tässä toinen esimerkki Option
-olion map
-metodin käytöstä. Se on eräs toteutustapa
luvussa 5.2 esillä olleelle tempo
-funktiolle:
def tempo(kappale: String) = kappale.split("/").lift(1).map( _.toInt ).getOrElse(120)tempo(kappale: String): Int tempo("cccedddfeeddc---/150")res19: Int = 150 tempo("cccedddfeeddc---")res20: Int = 120
Tässä käytämme metodeita split
(luku 5.2) ja lift
(luku 4.3). Edellisellä jaetaan merkkijono osiin kauttamerkin
kohdalta. Jälkimmäisellä tuotetaan Option[String]
, joka
sisältää kauttamerkin jälkeisen osan, jos sellaista oli.
lift
-metodikutsun palauttama mahdollinen merkkijono saadaan
muutettua mahdolliseksi kokonaisluvuksi käyttämällä metodeita
map
ja toInt
.
Preferences
-esimerkki
Tavoite: valinnaisia asetuksia käyttäjäprofiileissa
Olkoon kuvitteellisessa sovelluksessa seuraava luokka, joka kuvaa käyttäjän henkilökohtaisia
asetuksia. Ohjelmassa on vain kaksi eri käyttäjäasetusta, jotka kertovat käyttäjän suosiman
kielen ja sen, käytetäänkö SI-järjestelmän mittayksiköitä vai ei. Kumpikin asetuksista on
valinnainen, eli käyttäjä saattaa olla ilmoittanut mieltymyksensä kyseisestä asiasta tai ei.
Asia on kuvattu Option
-olioilla:
class Preferences(val profile: String, val language: Option[String], val metricSystem: Option[Boolean]):
// tänne toString-metodi yms.
end Preferences
Tässä pari käyttöesimerkkiä:
val test = Preferences("My preferred settings", Some("English"), None)test: Preferences = lang: English, metric: NOT SET val test2 = Preferences("Some other settings", Some("Finnish"), Some(true))test2: Preferences = lang: Finnish, metric: true
Olkoon vielä niin, että käyttäjä joko on tehnyt itselleen asetusprofiilin, jota kuvaamaan
on tallennettu Preferences
-olio, tai sitten hän ei ole, jolloin Preferences
-oliotakaan
ei ole. Voimme kuvata asiaintilaa muuttujalla, jonka tyyppi on Option[Preferences]
.
Alla on kolme erillistä esimerkkiä: Tiina on tehnyt asetukset, joihin sisältyy kieliasetus; Kuurallakin on asetukset muttei kieliasetusta; Teemu ei ole tehnyt asetuksia laisinkaan.
val tiinasPreferences = Some(Preferences("Tiina's profile", Some("Finnish"), Some(true)))tiinasPreferences: Some[Preferences] = Some(lang: Finnish, metric: true) val kuurasPreferences = Some(Preferences("Kuura's profile", None, Some(true)))kuurasPreferences: Some[Preferences] = Some(lang: NOT SET, metric: true) val teemusPreferences: Option[Preferences] = NoneteemusPreferences: Option[Preferences] = None
Miten voidaan tuottaa String
-tyyppinen arvo, joka kertoo, millä kielellä ohjelman
käyttöliittymä tulisi esittää käyttäjälle? Halutaan, että jos käyttäjällä on tallennetut
asetukset ja niihin on tallennettu language
-tieto, niin käytetään kyseistä kieltä. Jos
asetukset puuttuvat tai jos language
-asetus on None
, käytetään ohjelman oletuskieltä
(joka olkoon vaikkapa englanti).
Hahmotellaan REPLissä.
Ratkaisun hahmottelua
Tämä voisi olla ensimmäinen yritys selvittää Tiinan suosima kieli:
tiinasPreferences.language-- Error: ... value language is not a member of Some[Preferences]
Näin helpolla emme sentään pääse, koska asetustiedot voivat puuttua tyystin. Some
-oliolla
ei ole language
-ilmentymämuuttujaa, mutta sen sisällöllä on. Käytämme taas map
-metodia:
tiinasPreferences.map( _.language )res21: Option[Option[String]] = Some(Some(Finnish))
Kieliasetus on kahden valinnaisuuden takana: "ehkä on
asetukset ja niissä ehkä on kieliasetus". Se on siis
tyyppiä Option[Option[String]]
.
Tiinalla on asetusolio: tiinasPreferences
ei ole
None
vaan Some
-olioon kääritty Preferences
-olio.
Tämän Preferences
-olion language
ei sekään ole None
vaan Some
-olioon kääritty merkkijono "Finnish"
.
Kun map
-metodilla pyydetään Preferences
-olion
language
-muuttujan arvo, saadaan siis sisäkkäiset
Some
-oliot.
flatten
-metodi poistaa sisäkkäisyyden:
tiinasPreferences.map( _.language ).flattenres22: Option[String] = Some(Finnish)
Toivottavasti jo tuttuun tapaan map
ja flatten
voidaan yhdistää flatMap
-kutsuksi.
tiinasPreferences.flatMap( _.language )res23: Option[String] = Some(Finnish)
Näin siis saimme selville Tiinan kieliasetuksen Option[String]
-muodossa: Some(Finnish)
tarkoittaa, että kieliasetus on olemassa ja se on suomi.
Tavoitteenamme oli käyttää käyttäjän asetusta, jos sellainen on, ja englantia muutoin.
getOrElse
-metodi sopii tarkoitukseen:
val tiinasLanguage = tiinasPreferences.flatMap( _.language ).getOrElse("English")tiinasLanguage: String = Finnish
Tuloksena saatiin Option
iin käärimätön merkkijono, joka kertoo valitun kielen.
Ratkaisu kootusti
Abstrahoidaan äskeisestä tapauksesta funktio, joka selvittää annettujen, ehkä puuttuvien, asetustietojen perusteella, mitä kieltä tulisi käyttää:
def chooseLanguage(prefs: Option[Preferences]) = prefs.flatMap( _.language ).getOrElse("English")chooseLanguage(prefs: Option[Preferences]): String
Tässä vielä kootusti kaikkien kolmen testihenkilömme asetukset ja käskyt, joilla kullekin heistä määritetään käytettävä kieli:
val tiinasPreferences = Some(Preferences("Tiina's profile", Some("Finnish"), Some(true)))tiinasPreferences: Some[Preferences] = Some(lang: Finnish, metric: true) val kuurasPreferences = Some(Preferences("Kuura's profile", None, Some(true)))kuurasPreferences: Some[Preferences] = Some(lang: NOT SET, metric: true) val teemusPreferences: Option[Preferences] = NoneteemusPreferences: Option[Preferences] = None val tiinasLanguage = chooseLanguage(tiinasPreferences)tiinasLanguage: String = Finnish val kuurasLanguage = chooseLanguage(kuurasPreferences)kuurasLanguage: String = English val teemusLanguage = chooseLanguage(teemusPreferences)teemusLanguage: String = English
Olisi tuo match
illakin mennyt mutta monimutkaisemmin
Esimerkiksi näin:
def chooseLanguage(prefs: Option[Preferences]) =
val languagePref = prefs match
case Some(existingPrefs) => existingPrefs.language
case None => None
languagePref match
case Some(pref) => pref
case None => "English"
Lisäesimerkki
Jos haluat, voit katsoa myös seuraavan toString
-toteutuksen.
Siinä on lisäesimerkki map
-metodin käytöstä Option
-olioille.
class Preferences(val profile: String, val language: Option[String], val metricSystem: Option[Boolean]):
override def toString =
def describe(name: String, value: Option[String]) =
name + ": " + value.getOrElse("NOT SET")
describe("lang", this.language) + ", " + describe("metric", this.metricSystem.map( _.toString ))
end Preferences
Passenger
-esimerkki
Luvussa 4.4 teit luokan Passenger
luokkaa TravelCard
apuna käyttäen. Työvälineenä oli
match
-käsky, ja canTravel
-metodin ratkaisu näytti tältä:
class Passenger(val name: String, val card: Option[TravelCard]):
def canTravel = this.card match
case Some(actualCard) => actualCard.isValid
case None => false
end Passenger
Nyt tiedämme, että saman saa tehtyä näinkin:
class Passenger(val name: String, val card: Option[TravelCard]):
def canTravel = this.card.exists( _.isValid )
end Passenger
Koodi sanoo varsin suoraan sen, mistä on kyse: matkustaja voi matkustaa, mikäli hänellä on kortti, joka on voimassa.
Yleisemmin voimme sanoa, että seuraavat koodit ajavat saman asian.
x match
case Some(sisalto) => jokuEhto(sisalto)
case None => false
x.exists(jokuEhto)
x
viittaa tässä johonkin Option
-olioon.
jokuEhto
on jokin toimenpide, joka tuottaa
Boolean
-arvon Option
-olion sisällön
perusteella.
Muitakin Option
in metodeita kuin exists
voi käyttää match
-käskyn sijasta.
Tilaisuuksia tehdä niin tarjoutuu esimerkiksi robottiohjelmassa, johon nyt palaamme.
Robottien liikettä
Tehtävän kaksi ensimmäistä vaihetta teit luvussa 8.3. Seuraavat kaksi tehdään nyt.
Yleisiä vinkkejä kaikkiin robottisimulaattorimme tuleviin vaiheisiin:
Mun motto tähän tehtävään oli "Option
on alkiokokoelma".
Se auttoi kovasti.
Tässä totesin, että nuo Option
ien exists
-, forall
- ja
foreach
-toiminnot ovat aivan ihania.
Koeta ratkaista tämän ja seuraavan luvun tehtävät käyttämättä match
-käskyä Option
eiden
käsittelyyn. Pyri käyttämään match
in sijaan korkeamman asteen metodeita.
Niin, ja vielä yksi varoituskyltti tähän:
Option
-olioiden get
-metodista
Luvun 4.4 lisämateriaalissa mainittiin, että Option
-olioilla on
metodi, joka on nimeltään yksinkertaisesti get
. Saatat törmätä
tuohon metodiin myös verkosta tietoa hakiessasi.
Metodi poimii "kääreen" sisällön mutta kaatuu ajonaikaiseen virheeseen, ellei sisältöä ole. Luvussa varoitettiin, että tätä metodia on syytä välttää käyttämästä, emmekä ole sitä esimerkeissäme käyttäneetkään. Silti kokemus kertoo, että turhan moni O1-opiskelija on tätä metodia päätynyt kutsumaan. Osa heistä on joutunut siksi hankaluuksiin koodinsa kanssa; osa ei, mutta myös heidän on syytä huomioida tämä ohjelaatikko.
Aiempi varoitus pätee myös näihin robottitehtäviin ja yleisemminkin.
Älä käytä Option
ien get
-metodia vaan muita opetettuja konsteja.
Vaikka get
voi vaikuttaa kätevältä, sen käyttö on huono tapa. Metodi
rikkoo kielen tyyppiturvallisuuden ja sopii vain
harvinaisiin erikoistilanteisiin.
Älä palauta O1:n tehtäviin Option
in get
iä käyttävää koodia.
Usein kysyttyä: Mutta mun get
on if
fin sisällä — ei kai se silloin oo kielletty?
Oletetaan, että Option[String]
-tyyppinen muuttuja nimeltä
ehkaSana
. Sitä ei ole mahdotonta käyttää näin:
val tuloste = if ehkaSana.isDefined then ehkaSana.get else "ei sanaa"
println(tuloste)
if ehkaSana.isDefined && munOption.get.length > 10 then
println("löytyi pitkä sana")
Koodi "toimii", mutta älä silti kirjoita noin. Käytännössä aina on parempikin konsti. Esimerkiksi nämä ovat parempia:
val tuloste = ehkaSana.getOrElse("ei sanaa")
println(tuloste)
if ehkaSana.exists( _.length > 10 ) then
println("löytyi pitkä sana")
Yksi näiden parempien konstien keskeinen etu on, että mahdollisia inhimillisiä virheitä tulee kitketyksi pois, kun mahdollisesti ohjelman kaatavaa metodia ei kutsuta lainkaan eikä tarvitse muistaa tarkastaa mitään ehtoja ennen sen kutsumista. Koodimme on myös kätevämpi kirjoittaa ja helpompi lukea, kunhan tähän tyyliin tottuu.
get
illä on hyvissä Scala-ohjelmissa mielekkäitä käyttökohteita
niukasti jos lainkaan. Metodi tuo ohjelmaan ihan turhaan "riskiä"
ja rikkoo tyyppiturvallisuutta ilman, että saavutetaan oikeastaan
mitään.
Robottitehtävä, vaihe 3/9: toimintavuorot ja Spinbot
Toteuta tämän luvun alussa esiin nostettu metodi
takeTurn
luokkaanRobotBody
. Se käy yksinkertaisesti, kunhan valitset sopivan välineenOption
in käsittelyyn.RobotBody
-luokasta puuttuu myös metodispinClockwise
. Toteuta se.CompassDir
-olioilla on parametriton metodiclockwise
, joka palauttaa 90 astetta myötäpäivään olevan ilmansuunnan. (Esim.East.clockwise
onSouth
.) Voit hyödyntää sitä.
Tutki
RobotBrain
in dokumentaatiota ja koodia. Huomaa etenkin metoditcontrolTurn
(jota kutsuitRobotBody
-luokasta) sekämoveBody
. Tähän piirreluokkaan ei vielä tarvita muutoksia. Sen sijaan seuraavaksi toteutetaan sen alatyyppiSpinbot
.Spinbot
-luokanmoveBody
-metodi ei tee mitään. Täydennä se.RobotWorld
-luokasta puuttuvat metoditrobotWithNextTurn
,advanceTurn
jaadvanceFullRound
. Täydennä ne. Korkeamman asteen metodeista voi tässäkin olla apua.Varmista kokeilemalla, että
Spinbot
it toimivat nyt. Kokeile myös rikkoa robotti käyttöliittymän kautta ja varmista, että kaikki silti toimii eli robotti ei. Palauta testattuasi.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Robottitehtävä, vaihe 4/9: liikkumisen pohjustus
RobotBody
- ja RobotBrain
-luokista puuttuu useita pieniä robottien ympäristöön ja
liikkumiseen liittyviä metoditoteutuksia.
Toteuta
RobotBody
n metodineighboringSquare
.Toteuta
RobotBrain
in metoditisStuck
,locationInFront
,squareInFront
,robotInFront
jaadvanceCarefully
.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Jatkamme robottihanketta seuraavassa luvussa 9.1. Ensin kokoamme yhteen Option
-tyypistä
opittua.
Pieniä mutta pähkinäisiä
Lisätieto: Näinkin voi match
-käskyyn kirjoittaa
Luvun 4.4 lisämateriaalissa esiteltiin lyhyesti match
-käskyn
ominaisuuksia. Yksi niistä oli, että case
-tapaukseen voi liittää
if
-sanalla ehdon. Niinpä seuraavat saavat aikaan keskenään saman:
x match
case Some(kaaritty) => if jotain(kaaritty) then x else None
case None => None
x match
case Some(kaaritty) if jotain(kaaritty) => x
case mikaVainMuuTapaus => None
Mitä opittiin?
Erittäin moni Option
-olioihin kohdistuva toimenpide on kätevästi toteutettavissa
kutsumalla jotakin Option
-olion metodia. Korkeamman asteen metodeita käyttämällä
koodista tulee tiivistä — mutta ymmärrettävää, kunhan lukija tuntee kyseiset usein
käytetyt metodit.
Voit itse harkita, kuinka paljon haluat käyttää valintakäskyjä kuten if
ja match
, ja
kuinka paljon korkeamman asteen metodeita, mutta molemmat toteutustavat kuuluu tuntea.
Pidä mielessä myös mahdollisuus käsitellä Option
-oliota for
-silmukalla kuten yllä
monivalintatehtävän viimeisessä kohdassa.
Millainen roboratkaisu tuli?
Jos et vielä käyttänyt tämän luvun esittelemiä metodeita
robottiohjelmassasi, tee se nyt; se on opettavaista.
Selviytyisitkö kokonaan ilman match
-käskyjä?
Lisää harjoitusta (ja petollinen get
-metodi)
Jos edellinen ei piisannut, voit treenata Option
-ajatteluasi myös seuraavassa tehtävässä,
jossa samaan tapaan pyydetään valitsemaan annettua koodia vastaava korkeamman asteen metodi.
Seuraavissa koodinpätkissä on käytetty Option
-olion get
-metodia, joka yksinkertaisesti
ottaa arvon kääreen sisältä ja kaatuu ajonaikaiseen virheeseen, jos kyseessä onkin None
-olio. Kuten luvun 4.4 vapaaehtoisessa materiaalissa kerrottiin, ja ylempänä tässä luvussa
tuli uudestaan esille, tuota metodia tulee kaihtaa, koska se aiheuttaa bugeja ja edustaa
huonoa ohjelmointityyliä. Tämäkään tehtävä ei esittele get
-metodia hyvänä vaihtoehtona
vaan päinvastoin osoittaa, että sen, mitä get
illä voi tehdä, saa tehtyä muutenkin.
Lisää vastaavia monivalintoja
Lisää pohdittavaa ja lukemista
Mieti, voisiko Option
-luokan käytön sijaan käyttää puskuria,
johon aina lisäisi korkeintaan yhden alkion. Mitä hyötyä tai
haittaa tästä olisi?
Selvitä, millainen on Scalan Either
-luokka ja miten sitä voi
käyttää osin samoihin tarkoituksiin kuin Option
ia. Selvitä myös
mitä ovat toisiinsa liittyvät luokat Try
, Success
ja Failure
.
Voit lukea, mitä on sanottu
Option
-luokan liikakäytöstä. Löydätkö tästä luvusta esimerkkejä,
joiden koodia voisi selkiyttää toisenlaisella ratkaisulla?
Siitäkin voit lukea, mikä on Null Object -suunnittelumalli ja miten
se liittyy samaan teemaan.
Yhteenvetoa
Option
-oliokin on alkiokokoelma. Se sisältää nolla tai yksi alkiota.Option
-oliolla on muista kokoelmista tuttuja metodeita:foreach
,exists
,map
,flatMap
jne. Niiden avulla "ehkä olemassa olevia arvoja" on helpompi käsitellä ohjelmissa.match
-käskykin toki toimii, mutta nämä metodit ovat usein näppärämpiä, kunhan totut niihin.Myös
for
-silmukkaa voi käyttääOption
-kokoelman läpikäymiseen.Älä käytä
Option
-olioidenget
-metodia.
Lukuun liittyviä termejä sanastosivulla:
Option
, kokoelma, korkeamman asteen funktio.
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, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, 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 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 nyt 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 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
Joonatan Honkamaa ja Otto Seppälä tehostivat robottimaailman vuorokoodia.
Rikkinäinen tai jumittunut robottirunko jättää vuoronsa käyttämättä. Tämä tarkistus on toteutettu valmiiksi.