Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 5.1: Logiikkaa, verta ja ostoksia
Tästä sivusta:
Pääkysymyksiä: Miten sanon "jos tuo tai tuo ehto toteutuu"? Tai "vain jos ne molemmat toteutuvat"? Osaanko jo laatia Scala-luokkia itsenäisemmin?
Mitä käsitellään? Uutena asiana logiikkaoperaattorit. Niiden lisäksi tehtävät yhdistelevät monia aiempia aiheita.
Mitä tehdään? Enimmäkseen ohjelmoidaan, vaikka alussa on vähän luettavaakin.
Suuntaa antava työläysarvio:? Pari, kolme tuntia? Huomattavasti pidempään, jos teet kaikki vapaaehtoisetkin ohjelmointitehtävät.
Pistearvo: A120.
Oheisprojektit: FlappyBug, Miscellaneous, AuctionHouse1 (uusi).
Johdanto: vaikeutus FlappyBugiin
Palataan FlappyBug-peliin. Luvussa 3.3 lisäsit Game
-pelioliolle isLost
-metodin, joka
määrää pelin poikki, jos este koskee ötökkää:
def isLost = this.obstacle.touches(this.bug)
Nykyisessä pelissä on kuitenkin turhan helppo edetä lymyämällä ylä- tai alareunassa. Jospa muokkaamme yllä olevaa ehtoa siten, että peli loppuu, jos törmää esteeseen tai ylä- tai alareunaan.
Jotenkin näin se voisi mennä:
def isLost =
if (this.obstacle.touches(this.bug))
true
else if (this.pos.y <= 0)
true
else
this.pos.y >= GroundY
Toimii se noinkin, mutta tuo on aika vaivalloinen tapa sanoa yksinkertainen asia. Mieluummin kirjoittaisimme: "Jos tuo tai tuo ehto on tosi." Ehkä olet ehtinyt kaipailla tällaista mahdollisuutta jo aiempien lukujen aikana.
Tässä luvussa opit käyttämään logiikkaoperaattoreita kuten "ja" ja "tai", joilla
Boolean
-arvoja voi yhdistellä. Niiden yleisesittelyn jälkeen palaamme mm. FlappyBugiin.
Logiikkaoperaattorit
Scala-kielen tarjoamia logiikkaoperaattoreita (logical operators) on taulukoitu alla:
Operaattori | Nimi | Esimerkki | Vastaa tarpeeseen |
---|---|---|---|
&& |
ja (and) | jokuVaite && toinenVaite |
"Ovatko molemmat totuusarvot true ?" |
|| |
tai (or) | jokuVaite || toinenVaite |
"Onko ainakin toinen totuusarvoista true ?" |
^ |
joko–tai eli poissulkeva tai
(exclusive or eli xor)
|
jokuVaite ^ toinenVaite |
"Onko tasan yksi totuusarvoista true ?" |
! |
ei tai negaatio
(not tai negation)
|
!jokuVaite |
"Onko totuusarvo false ?" |
Tämän kurssin kannalta hyödyllisimpiä ovat &&
, ||
ja !
, joista viimeinen onkin jo
aiemmista luvuista tuttu.
Logiikkaoperaattoreilla rakennetaan Boolean
-tyyppisistä lausekkeista monimutkaisempia
lausekkeita, joilla on niilläkin Boolean
-tyyppinen arvo.
val luku = 50luku >= 0 && luku <= 5res0: Boolean = false if (luku >= 0 && luku <= 5) "on kelvollinen arvosana" else "ei ole kelvollinen arvosana"res1: String = "ei ole kelvollinen arvosana" luku == 100 || luku == 50res2: Boolean = true !(luku >= 0)res3: Boolean = false
&&
- eli ja-operaattori tuottaa true
n vain, jos molemmat
osalausekkeista ovat true
. Tässä ensimmäinen on mutta toinen
ei, joten koko lausekkeen arvo on false
.||
- eli tai-operaattori tuottaa true
n, jos kumpi tahansa
osalausekkeista on true
(tai molemmat ovat). Tässä ensimmäinen
ei ole, mutta toinen on, joten koko lausekkeen arvo on true
.!
riittää yksi
operandi, kun taas muut logiikkaoperaattorit vaativat kaksi.Logiikkaoperaattorien väljä evaluointi
&&
- ja ||
-operaattorien evaluointi on väljää (non-strict). Tässä yhteydessä
väljyys tarkoittaa käytännössä sitä, että vasemmanpuoleinen operandi evaluoidaan ensin,
ja jos se riittää määräämään koko lausekkeen totuusarvon, niin oikeanpuoleista operandia
ei evaluoida lainkaan. (Väljää evaluointia käsitellään laajemmin luvussa 7.1.)
Asialla on käytännön merkitystä, kuten alla olevasta esimerkistä selviää.
Määritellään aluksi pari muuttujaa, joilla on jotkin lukuarvot. Tässä ne ovat eräät literaalit, mutta luvut voitaisiin myös vaikkapa kysyä ohjelman käyttäjältä.
val jaettava = 50000jaettava: Int = 50000 var jakaja = 100jakaja: Int = 100
Tutkitaan jakolaskun lopputuloksen suuruutta, kunhan ensin varmistetaan, että jakaja ei
ole nolla. Tarkastus onnistuu joko &&
- tai ||
-operaattorilla:
jakaja != 0 && jaettava / jakaja < 10res4: Boolean = false jakaja == 0 || jaettava / jakaja >= 10res5: Boolean = true
Silloin, kun jakajana ei ole nolla, voi operandien järjestyksen vaihtaa ja lopputulos pysyy samana:
jaettava / jakaja >= 10 || jakaja == 0res6: Boolean = true
Vaan entä jos jakaja onkin nolla?
jakaja = 0jakaja: Int = 0 jaettava / jakaja >= 10 || jakaja == 0java.lang.ArithmeticException: / by zero ...
Nyt jakolasku suoritetaan ensimmäistä operandia evaluoitaessa. Syntyy ajonaikainen
virhe: nollalla ei voi jakaa. Lauseketta jakaja == 0
ei koskaan edes ehditty aloittaa
evaluoimaan.
Jos taas tarkastus on ensimmäisessä operandissa, homma toimii:
jakaja == 0 || jaettava / jakaja >= 10res7: Boolean = true
Koska jakaja oli nolla, niin lausekkeen jakaja == 0
arvoksi saatiin true
. Näin ollen
loppuosaa lausekkeesta ei edes evaluoitu sillä perusteella, että true || mitaTahansa
on aina true
. Näin myös vältettiin virhe. Järjestyksellä on väliä!
Operaattorien yhdistelemisestä
Kuten aritmeettisia lausekkeitakin, myös logiikka- ja vertailuoperaattoreita yhdistelemällä muodostettuja lausekkeita voi ryhmitellä kaarisulkeilla.
if ((luku > 0 && luku % 2 == 0) || luku < 20) {
println("Joko sekä positiivinen että parillinen tai 20:ä pienempi.")
}
if (luku > 0 && (luku % 2 == 0 || luku < 20)) {
println("Positiivinen; lisäksi joko parillinen tai 20:ä pienempi.")
}
Logiikkaoperaattoriharjoittelua
Tarkastele seuraavaa koodinpätkää ja mieti huolellisesti, mitä tapahtuu, kun se suoritetaan. Muistiinpanolappusen käyttäminen ei ole huono idea.
val first = 20
val second = 10
var third = 50
if (first > second && first + second < third) {
third = 30
println("One")
}
val result = first > second && first + second < third
if (first < second || result) {
println("Two")
}
if (first > second || second == third) {
println("Three")
}
third = 10
if (!(result && third >= 50)) {
println("Four")
} else {
println("Five")
}
Parempi pullokone
Logiikkaoperaattorit ovat monessa tilanteessa kätevämpi vaihtoehto kuin usean if
-käskyn
käyttäminen.
Palautetaan mieleen luvun 3.5 sellBottle
-metodi, josta on tässä alkuperäinen
Int
-palautusarvoinen versio.
def sellBottle() = {
if (this.isSoldOut) {
-1
} else if (!this.enoughMoneyInserted) {
-1
} else {
this.earnedCash = this.earnedCash + this.bottlePrice
this.bottleCount = this.bottleCount - 1
val changeGiven = this.insertedCash - this.bottlePrice
this.insertedCash = 0
changeGiven
}
}
Tutki seuraavia kahta yritystä kirjoittaa tämä metodi logiikkaoperaattoria käyttäen:
def sellBottle() = {
if (this.isSoldOut || !this.enoughMoneyInserted) {
-1
} else {
this.earnedCash = this.earnedCash + this.bottlePrice
// jne. kuten edellä
}
}
def sellBottle() = {
if (this.enoughMoneyInserted && !this.isSoldOut) {
this.earnedCash = this.earnedCash + this.bottlePrice
// jne. kuten edellä
} else {
-1
}
}
FlappyBug-tehtävä (osa 15/17: tappavat reunat)
Tehtävänanto
Vaikeuta FlappyBug-peliä johdannossa suunnitellulla tavalla. Tee se näin:
- Lisää
Bug
-luokkaan parametriton, vaikutukseton metodiisInBounds
, jonka palauttamaBoolean
-arvo kertoo, onko ötökkä sallitulla pelialueella eli nollakoordinaatin ja maanpinnan välissä (rajat poislukien). Käytä sopivaa logiikkaoperattoria. - Muokkaa
Game
-luokanisLost
-metodia siten, että se ilmoittaa pelin päättyneeksi myös silloin, jos ötökkä on sallitun pelialueen ulkopuolella. Käytä logiikkaoperaattoreita jaisInBounds
-metodia.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Verityyppitehtävä
Tehtävänanto
Miscellaneous-projektissa on pakkaus o1.blood
ja siellä luokka BloodType
. Toteuta se
projektin mukana tulevan Scaladoc-dokumentaation mukaiseksi.
Ohjeita ja vinkkejä
Tässä yksi esimerkki siitä, miten luokan pitäisi toimia:
import o1.blood._import o1.blood._ val myBlood = new BloodType("AB", true)myBlood: o1.blood.BloodType = AB+ val yourBlood = new BloodType("A", true)yourBlood: o1.blood.BloodType = A+ val theirBlood = new BloodType("O", false)theirBlood: o1.blood.BloodType = O- myBlood.canDonateTo(yourBlood)res8: Boolean = false yourBlood.canDonateTo(myBlood)res9: Boolean = true theirBlood.canDonateTo(yourBlood)res10: Boolean = true
Voit myös ajaa annetun
BloodTest
-ohjelman kokeillaksesi kaikkia eri yhdistelmiä.Tässäkään tehtävässä ei tarvitse välittää sellaisista erikoistapauksista ja virhekäyttötilanteista, joita ei ole erikseen mainittu. Saat luottaa siihen, että
BloodType
lle annetaan vain mielekkäitä merkkijonoja konstruktoriparametriksi.Pyri toteuttamaan pyydetyt metodit mahdollisimman yksinkertaisesti ja toistamatta samaa koodinpätkää eri paikoissa.
- Käytä logiikkaoperaattoreita.
Pärjäätkö kokonaan ilman
if
- jamatch
-valintakäskyjä (ainakin muissa kuintoString
issä)? - Kutsu muita saman luokan metodeita.
Lisävinkki: myös viittauksen
this
-olioon voi antaa parametriksi metodille.
- Käytä logiikkaoperaattoreita.
Pärjäätkö kokonaan ilman
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tehtävä: FixedPriceSale
Projektin AuctionHouse1 Scaladoc-dokumentit kuvailevat useita luokkia, jotka mallintavat kuvitteellisella verkkohuutokauppasivustolla myyntiin laitettuja esineitä. Tässä tehtävässä keskitytään yhteen myyntitapaan, jossa esineillä on tietty vakiomyyntihinta. Alempana olevissa valinnaisissa tehtävissä toteutetaan huutokauppoja, joissa hinta muuttuu.
Tehtävänanto
Nouda projekti AuctionHouse1 ja avaa sen Scaladoc-dokumentaatio. Toteuta pakkauksen
o1.auctionhouse
luokka FixedPriceSale
dokumentaation mukaiseksi.
Ohjeita ja vinkkejä
Luo ensin luokkaa varten uusi tiedosto. Kertaus:
- Klikkaa oikealla napilla pakkauksen nimeä Package Explorerissa ja valitse New.
- Huolehdi, että pakkauksen nimi
o1.auctionhouse
on mukana Name-kohdassa, johon syötät uuden luokan nimen. - Noin luotuna tiedoston alkuun pitäisi
automaattisesti ilmestyä tarvittava
package
-määrittely ja tiedoston ilmestyä oikean kansioon.
Voi olla hyvä ajatus käyttää
Option
-tyyppistä ilmentymämuuttujaa, jonka alkuarvo onNone
. Jos teet niin, muista että sinun täytyy merkitä sille tietotyyppi (vrt. luku 4.3). Siis:muuttujanNimi: Option[Tyyppi]
.Luokan osat on kuvattu Scaladocissa aakkosjärjestyksessä, mutta se ei liene kätevin toteuttamisjärjestys (eikä metodeita myöskään ole koodiin pakko kirjoittaa tässä järjestyksessä). Voit valita järjestyksen itse; tässä on yksi ehdotus, joka edennee suunnilleen helpoimmasta vaikeimpaan:
daysLeft
,toString
,buyer
,isExpired
,isOpen
,advanceOneDay
,buy
.Lue Scaladoc tarkasti. Huomaa esimerkiksi, että open ja expired eivät ole toistensa suoria vastakohtia, vaikka esine ei voikaan olla molempia yhtäaikaisesti.
Sinulle on annettu valmis testiohjelma käynnistysoliossa
FixedPriceSaleTest
. Käytä sitä!- Huom. Tämän testiolion määrittely aiheuttaa aluksi virheilmoituksia, jotka johtuvat vain siitä, ettet ole vielä toteuttanut niitä metodeita, joita olio kutsuu. Ilmoitukset häviävät kyllä, kun teet tehtävän. (Muista: virheilmoituksen syy ei aina ole siellä, mihin virheilmoitus osoittaa.)
- Voit aluksi "kommentoida ulos" osan testiohjelman koodista, niin pystyt testaamaan osittaista toteutusta luokastasi.
Voit lisäksi ajaa pakkauksesta
o1.auctionhouse.gui
löytyvän toisen testisovelluksenTestApp
, jonka avulla voit luoda ja koekäyttääFixedPriceSale
-olioita vuorovaikutteisesti tällaisessa ikkunassa:
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Lisätehtävä: Moment
ja Interval
Loput luvusta koostuu vapaaehtoisista mutta suositeltavista lisätehtävistä, joilla voit kehittää ohjelmointirutiiniasi. Nämäkin kannattaa tehdä, vaikka sitten niin, että teet ensin kierroksen pisteytetyt tehtävät ja palaat sitten tänne. Alla oleviin tehtäviin menee helposti useita tunteja.
Pakkauksen o1.time
esittely
Tässä tehtävässä pääset soveltamaan logiikkaoperaattoreita hieman aiempia mutkikkaammin ja muutenkin treenaamaan opittuja työkaluja.
Ajanhetkiä ja aikavälejä
Oletetaan, että on ohjelma (tai ohjelmia), joiden on tarpeellista käsitellä
aikavälejä, jotka alkavat tietystä ajanhetkestä ja jatkuvat toiseen hetkeen saakka.
Tätä varten halutaan laatia luokat Moment
kuvaamaan yksittäistä ajanhetkeä ja
Interval
kuvaamaan aikaväliä. Kuhunkin Interval
-olioon liittyy kaksi
Moment
-oliota, jotka kuvaavat aikavälin alku- ja loppuhetkeä.
Kurssin oheisprojektissa Miscellaneous on pakkaus o1.time
, josta löytyy jo alku
Moment
-luokan toteutukselle. Interval
-luokka puuttuu vielä kokonaan.
Aloitetaan luokasta Moment
, jonka pohjakoodista on selitys alla.
import scala.math.abs
class Moment(private val time: Int) {
override def toString = this.time.toString
def distance(another: Moment) = abs(another.time - this.time)
def isLaterThan(another: Moment) = this.time > another.time
def later(another: Moment) = if (this.isLaterThan(another)) this else another
def earlier(another: Moment) = if (this.isLaterThan(another)) another else this
}
Moment
-oliota luotaessa ilmoitetaan mistä ajanhetkestä on
kyse. Tässä toteutuksessa ei oteta lainkaan kantaa siihen,
mitä yksikköä ajan mittaamiseen käytetään, vaan mitä tahansa
kokonaislukumuotoista voi käyttää (nanosekunteja, päiviä,
vuosilukuja, jne.).Moment
-olio delegoi toString
-toteutuksen kokonaisuudessaan
kokonaisluvun huoleksi. Huomaa: Int
-arvollekin voi kutsua
metodia! Kokonaisluvun toString
-metodi palauttaa lukua
kuvaavat merkit, esimerkiksi luvulle 123 kutsuttuna merkkijonon
"123"
. (Int
-arvojen metodeista lisää seuraavassa
luvussa 5.2.)distance
-metodi määrittää kahden hetken etäisyyden.isLaterThan
-, later
- ja earlier
-metodeilla voi vertailla
ajanhetkiä toisiinsa.Yleinen vinkki: hyödynnä abstraktioita
Yksi tämän harjoituksen teemoista on itse laatimiesi metodien käyttö kirjoittaessasi toisia metodeita. Toki tuota on tehty aiemmissa tehtävissäkin, mutta tässä se erityisesti korostuu.
Jos toteutat seuraavat metodit aivan toisistaan riippumattomasti, tulet toistaneeksi samoja koodinpätkiä. Älä kuitenkaan tee niin, vaan mieti joka kohdassa, mitä aiemmin laatimaasi metodia voisit hyödyntää. Koeta saada koodistasi mahdollisimman DRY, eli don’t repeat yourself. Toisteeton koodi on helpommin muokattavaa ja vähemmän virhealtista.
Hienommin sanoen: Jokainen metodi on abstraktio (luku 1.6). Rakenna uusia
abstraktioita vanhojen päälle. Esimerkki: Yllä kuvattu isLaterThan
-metodi on
abstraktio siitä konkreettisesta toteutuksesta, jossa katsotaan kahden
Moment
-olion ilmentymämuuttujien arvot ja verrataan niitä vertailuoperaattorilla
toisiinsa: this.time > another.time
. Toiset metodit, joilla on tarvetta
vertailla kahta Moment
-oliota voivat käyttää tätä metodia, eikä niiden
tarvitse itse ruveta vertailemaan muuttujien arvoja keskenään.
Harjoitus neljässä vaiheessa
Täydennä Interval
ja Moment
dokumentaation mukaisiksi. Alla on ehdotus työvaiheista
ja niihin liittyviä vinkkejä, mutta voit edetä muussakin järjestyksessä.
Vaihe 1/4: Interval
-luokka alkuun
- Lue projektin Scaladoc-dokumentaatiosta nyt ainakin luokkien
yleiskuvaukset sekä kuvaukset
Interval
-luokan metodeistalength
jatoString
. Ne toteutetaan tässä vaiheessa. - Luo uusi tiedosto
Interval
-luokkaa varten. - Kirjoita konstruktoriparametrit. Koska molemmat parametriarvot on saatava talteen metodien toteuttamiseksi, voi niille samalla määritellä ilmentymämuuttujat otsikkorivillä. Pitääkö näiden ilmentymämuuttujien olla yksityisiä?
- Laadi metodi
length
. Käytä apunaMoment
-luokan metodia.- Vaikka kiusaus iskisi, niin älä laadi nyt
eikä myöhemmin
Moment
-luokkaan mitään sellaisia julkisia osia, joita ei ole erikseen pyydetty. Erityisesti: älä teetime
-ilmentymämuuttujasta julkista.
- Vaikka kiusaus iskisi, niin älä laadi nyt
eikä myöhemmin
- Laadi metodi
toString
. Se on vähän monimutkaisempi. Vinkkejä:- Hyödynnä aiemmin laatimaasi metodia.
- Käytä
if
-käskyjä jaelse
-osioita erottaaksesi toisistaan tarvittavat kolme tapausta. - Muista, että merkkijononkin voi "kertoa",
esim.
"ho" * 3
tuottaa"hohoho"
.
- Siirry
TimeTest
-ohjelmaan, jolle on pohjustus valmiina. Lisää sinne käskyjä, joilla testaatInterval
-olioiden luomista ja uusia metodeitasi.
Ajanhetkien yhtäsuuruudesta
Ajanhetkien yhtäsuuruusvertailu operaattoreilla ==
ja !=
on identiteettivertailu
("Osoittavatko viittaukset juuri samaan Moment
-olioon?"; ks. luku 3.3.) Se siis
ei vertaa sitä, kuvaavatko kaksi eri Moment
-oliota samaa ajanhetkeä eli onko
niiden time
-muuttujalla sama kokonaislukuarvo.
Identiteettivertailua yhtäsuuruusoperaattoreilla ei tarvitse tässä tehtävässä lainkaan eikä siitä tässä oikein hyötyäkään ole. Muilla metodeilla pärjää mainiosti!
Vaihe 2/4: isLaterThan
ja metodinimen kuormitusta
Aikavälitehtävä oli sangen mullistava kokemus.
Olin jo vauhdikkaasti tekemässä jokaiselle metodille omaa
if
-lausetta ja jättämässä metodien uusiokäytön vähille,
kun sattumalta paikalla olleet pelottavat ja tarkkasilmäiset
assistentit huomauttivat asiasta.
En uskaltanut uhmata viisaita assareita, joten muutaman
tovin mietin hikoillen, miten ihmeessä nyt voin ilman
yhtäkään if
-lausetta selvitä. Kun lopulta keksin
isLaterThan(Interval)
-metodin ratkaisun (assarin vahvasti
vinkattua), tuntui kuin olisin keksinyt pyörän. Sehän oli
nerokasta!
- Laadi
Interval
-luokan metodi nimeltäisLaterThan
. Huomaa, että metodinimeä on kuormitettu (luku 4.1) ja tällaisia metodeita on kaksi. Yksi ottaaMoment
-tyyppisen parametrin ja toinenInterval
-tyyppisen. Laadi näistä nyt ensinmainittu. Käytä apuna erästä olemassa olevaa metodia. - Laadi sitten toinen
Interval
-luokanisLaterThan
-metodeista. Käytä apuna äsken laatimaasi metodia. Niin tehdessäsi muista luvun 4.1 mainitsema pikkuseikka: jos kuormitetuista Scala-metodeista yksi kutsuu kaimaansa, on kutsujalle kirjattava palautusarvon tyyppi koodiin. Nyt tyyppi on siis kirjoitettava vähintäänkin yhdelleisLaterThan
-metodeista. - Lisää taas
TimeTest
-testiohjelmaasi uusia käskyjä ja aja se. Kutsu testiohjelmasta molempiaisLaterThan
-metodeita ja varmista, että ne toimivat. - Lisää myös
Moment
-luokkaan sieltä vielä puuttuvaisLaterThan
-metodi, joka ottaaInterval
-tyyppisen parametrin.
Vaihe 3/4: logiikkaoperaatioita
Toteuta lisää metodeita Interval
- ja Moment
-luokkiin:
- Toteuta se
Interval
-luokancontains
-nimisistä metodeista, joka ottaa parametriksiMoment
-olion. Hyödynnä logiikkaoperaattoreita ja aiemmin laadittuja metodeita. - Toteuta toinenkin
contains
-metodi. Hyödynnä tässäkin logiikkaoperaattoreita ja aiemmin laadittuja metodeita. Huomaa lisäksi yllä kerrattu asia metodinimien kuormittamisesta. - Toteuta
Moment
-luokanisIn
-metodi. Se tekee käytännössä "saman asian toisin päin" kuin eräs aiemmin laatimasi metodi, joten sen voi toteuttaa hyvin yksinkertaisella tavalla. - Toteuta
Interval
-luokanoverlaps
-metodi. Logiikkaoperaattoreista ja edellä laadituista metodeista on jälleen apua. Muistithan huomioida kaikki erilaiset tapaukset? - Lisää uusia metodikutsuja
TimeTest
-luokkaan. Toimivatko uudet metodisi?
Vaihe 4/4: uusia olioita vanhoja yhdistelemällä
Toteuta ja testaa vielä Interval
-luokasta puuttuvat metodit union
ja
intersection
, joille on yhteistä uusien Interval
-olioiden luominen.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Kuinka yksityinen on private
?
Kysyttyä: toimiiko tuo Moment
tosiaan? time
hän on yksityinen!
Katsotaan vähän tarkemmin, miksi äskeisen tehtävän koodi toimii eikä tuota virheilmoitusta.
class Moment(private val time: Int) {
def isLaterThan(another: Moment) = this.time > another.time
time
on yksityinen. Sitä ei ole tarkoitus käyttää
ulkopuolelta. Mutta minkä ulkopuolelta?Moment
-olio voi "kysyä omaa time
ään".Moment
-olio voi myös "kysyä toisen Moment
-olion time
ä",
kun pisteen eteen laitetaan jokin toiseen olioon viittaava
lauseke, tässä muuttujan another
nimi.private
tarkoittaa, että kyseinen ohjelman osa on käytettävissä vain saman luokan
sisältä. Tuo osa ei ole vain yhden olion saatavilla vaan "yhteistä tietoa" kyseisen
luokan olioille.
Edesmennyt nobelisti on lausunut teemastamme näin:
Kaikilla ihmisillä on kolme elämää:julkinen, yksityinen ja salainen.—Gabriel García Márquez
Scalassa "salainen" kirjoitetaan private[this]
. Jos käytät tätä määrettä muuttujan
tai metodin edessä, niin siihen pääsee käsiksi ainoastaan olio itse. Tällä kurssilla
emme käytä "salaisia" muuttujia oikeastaan lainkaan.
val
/var
-sanan pois jättäminen ja private[this]
Tämä 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:
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...
kokeilu
edessä ei
lukisikaan val
eikä var
, mutta...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 noistä ei ole se, mitä oikeasti Scalan sääntöjen mukaan tapahtuu... mikä selviää kohta, kunhan ensin kerrataan nämä asiat konstruktoriparametreista ja niiden yhteyteen määritellyistä ilmentymämuuttujista:
- Jos konstruktoriparametrin 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 käyttää ilmaisua
private[this]
, niin ilmentymämuuttuja on "salainen", ilmentymäkohtainen. Tällöin sitä voi käyttää vainthis
-oliolle. - Jos konstruktoriparametrin 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. Ilmanval
/var
-sanaa ilmentymämuuttuja ei tule määritellyksi. Ainakaan lähtökohtaisesti. Mutta katso esimerkki alla.
class Luokka( eka: Int, toka: Int) {
val kolmas = toka * toka
def metodi = this.eka
}
eka
n edessä ei lue val
. Tällainen
konstruktoriparametrimuuttuja on lähtokohtaisesti
käytettävissä vain oliota luodessa eikä
jäisi ilmentymämuuttujana olion osaksi...val
- tai
var
-sanaa määriteltyä parametria kuitenkin
käytetään jostakin metodista, kuten tässä,
niin tulee huomaamattomasti määritellyksi
parametrin niminen “salainen”
ilmentymämuuttuja.Äskeinen koodi siis toimii samoin kuin jos parametrimuuttuja olisi
esitelty muodossa private[this] val eka: Int
. O1-kurssilla emme
kirjoita ohjelmia tuohon tyyliin.
Lisätehtäviä: huutokaupat
DutchAuction
-tehtävä
Ota esiin AuctionHouse1-projekti. Toteuta dokumentaation kuvaama luokka
DutchAuction
.
Ohjeita ja vinkkejä:
- Pidä ensinnäkin huolta siitä, että luet kuvauksen
"hollantilaistyyppisistä huutokaupoista" tunnollisesti! Kuvaus
löytyy luokan
DutchAuction
dokumentaatiosta. Ellei sinulla ole kirkasta käsitystä siitä, mitä luokan pitäisi tehdä, on Scala-toteutuksenkin laatiminen piinallista. - Huomaat jo dokumentaatiota lukiessasi, että
DutchAuction
-luokka on joiltain osin aivan samanlainen kuin ylemmän tehtävänFixedPriceSale
. Voit napata koodia ratkaisustasiDutchAuction
-luokkaasi. - Voit käyttää samaa
TestApp
-ohjelmaa pakkauksestao1.auctionhouse.gui
kokeillaksesi ratkaisusi toimivuutta. Voit toki myös kirjoittaa oman tekstipohjaisen testiohjelman. - Kokonaisluvusta saa vastaavan desimaaliluvun...
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Harmittiko kirjoittaa samoja metodeita uudestaan DutchAuction
-luokkaan
tai kopioida niitä tiedostosta toiseen? Verratonta! Olet siis motivoitunut
oppimaan aiheesta, jota käsitellään luvuissa 7.2 ja 7.3 ja jolla moinen
toisto voidaan välttää.
EnglishAuction
-tehtävä
Jatka AuctionHouse1-projektin parissa. Toteuta EnglishAuction
.
Ohjeita ja vinkkejä:
- Varmista taas ensin, että ymmärrät miten "englantilaistyyppisten huutokauppojen" on tarkoitus toimia.
- Käytä apuna annettua pientä
Bid
-luokkaa.EnglishAuction
-luokasta on annettu vain vähän pohjakoodia. - Vaikka tehtävän voi ratkaista käyttäen puskureita, joihin talletetaan kaikki huutokauppoihin tehdyt huudot, niin puskurittakin pärjäät hyvin. Ihan kaikkia tehtyjä huutoja ei tarvitse tallentaa vaatimusten täyttämiseksi...
- ... vaan pohjaksi annetussa koodissa käytetään kahta muuttujaa,
joissa on kaksi ylintä huutoa. Kiinnitä huomiota siihen, millaiset
alkuarvot näille muuttujille on annettu: kun varsinaisia huutoja
ei vielä ole, muuttujissa on huutajattomat alkuhintaa kuvaavat
Bid
-oliot. TestApp
-kokeiluohjelmaa sopii käyttää tässäkin.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Yhteenvetoa
- Logiikkaoperaattoreilla voi yhdistellä totuusarvoja. Niiden avulla voi käsitellä moniosaisia ehtoja: "X ja Y", "X tai Y".
- Lukuun liittyviä termejä sanastosivulla: logiikkaoperaattori, kuormittaa; DRY.
Palaute
Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.
Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Opetustapa, jossa käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.
Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
touches
-metodi palauttaatrue
taifalse
. Vastaavasti myösisLost
-metodi palauttaa jommankummanBoolean
-arvon.