Tämä kurssi on jo päättynyt.

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.

Oheismoduulit: FlappyBug, Miscellaneous, AuctionHouse1 (uusi).

../_images/person081.png

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)
Kertaus: touches-metodi palauttaa true tai false. Vastaavasti myös isLost-metodi palauttaa jommankumman Boolean-arvon.

Nykyisessä pelissä on kuitenkin turhan helppo edetä lymyämällä ylä- tai alareunassa. Jospa muokkaamme yllä olevaa ehtoa niin, 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.bug.pos.y <= 0)
    true
  else
    this.pos.y >= GroundY
Tappio tulee, jos on osuttu esteeseen.
Tai jos ei ole osuttu mutta jos ollaan liian korkealla.
Tai jos ei olla liian korkeallakaan mutta ollaan liian matalalla.

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 truen vain, jos molemmat osalausekkeista ovat true. Tässä ensimmäinen on mutta toinen ei, joten koko lausekkeen arvo on false.
||- eli tai-operaattori tuottaa truen, jos kumpi tahansa osalausekkeista on true (tai molemmat ovat). Tässä ensimmäinen ei ole, mutta toinen on, joten koko lausekkeen arvo on true.
Termi: osalausekkeita, joita jokin operaattori käsittelee, sanotaan usein operandeiksi (operand). Tässä esitellyistä operaattoreista negaatio-operaattorille ! riittää yksi operandi, kun taas muut logiikkaoperaattorit vaativat kaksi.

Logiikkaoperaattorien väljä evaluointi

&&- ja ||-operaattorien evaluointi on väljää (non-strict). Tässä 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ä!

Oletetaan, että käytössä on muuttujat jakaja ja jaettava kuten edellä ja että jakaja on nolla. Mitkä kaikki seuraavista väitteistä pitävät paikkansa lausekkeeseen jakaja != 0 && jaettava / jakaja < 10 liittyen?

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")
}
Yllä olevassa koodissa ||-operaattori jakaa toisen if-käskyn ehtolausekkeen first < second || result kahteen osaan. Mitä tapahtuu, kun tämä ehtolauseke evaluoidaan?
||-operaattori jakaa kolmannen if-käskyn ehtolausekkeen first > second || second == third kahteen osaan. Mitä tapahtuu, kun tämä ehtolauseke evaluoidaan?
Mitä println-käskyt tulostavat, kun koko koodinpätkä ajetaan?

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
  }
}
Arvioi näiden lyhyempien toteutusten toimivuutta.

FlappyBug-tehtävä (osa 15/17: tappavat reunat)

Vaikeuta FlappyBug-peliä johdannossa suunnitellulla tavalla. Tee se näin:

  1. Lisää Bug-luokkaan parametriton, vaikutukseton metodi isInBounds, jonka palauttama Boolean-arvo kertoo, onko ötökkä sallitulla pelialueella eli nollakoordinaatin ja maanpinnan välissä (rajat poislukien). Käytä sopivaa logiikkaoperattoria.
  2. Muokkaa Game-luokan isLost-metodia niin, että se ilmoittaa pelin päättyneeksi myös silloin, jos ötökkä on sallitun pelialueen ulkopuolella. Käytä logiikkaoperaattoreita ja isInBounds-metodia.

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

Verityyppitehtävä

Tehtävänanto

Miscellaneous-moduulista on pakkaus o1.blood ja siellä luokka BloodType. Toteuta se moduulin Scaladoc-dokumentaation mukaiseksi.

Ohjeita ja vinkkejä

  • Tässä yksi esimerkki siitä, miten luokan pitäisi toimia:

    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ä BloodTypelle 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- ja match-valintakäskyjä (ainakin muissa kuin toStringissä)?
    • Kutsu muita saman luokan metodeita. Lisävinkki: myös viittauksen this-olioon voi antaa parametriksi metodille.

Palauttaminen

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

Tehtävä: FixedPriceSale

Moduulin 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 moduuli 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 Project-näkymässä oikealla napilla pakkauksen nimeä o1.auctionhouse . Valitse New → Scala Class.
    • Syötä luokalle nimi: FixedPriceSale.
    • Tiedoston alkuun pitäisi automaattisesti ilmestyä tarvittava package-määrittely.
  • On hyvä ajatus käyttää Option-tyyppistä ilmentymämuuttujaa, jonka alkuarvo on None. Jos teet niin, muista että sinun täytyy merkitä sille tietotyyppi (kuten luvussa 4.3). Siis: muuttujanNimi: Option[Tyyppi].

    Lisävinkkejä ilmentymämuuttujista

    NäytäPiilota

    Pari kolmesta konstruktoriparametrista on syytä määritellä ilmentymämuuttujiksi. Näet nuo julkiset ilmentymämuuttujat dokumentaatiosta.

    Lisäksi tarvitset pari ilmentymämuuttujaa: yhden, jossa pidät kirjaa esineen vähenevästä aukioloajasta, ja toisen, jossa pidät kirjaa ostajan nimestä.

    Koska ostajaa ei välttämättä ole, sopii jälkimmäisen tyypiksi Option[String].

    Viimeksi mainittuja ilmentymämuuttujia ei ole mainittu dokumentaatiossa, vaan ne ovat osa sisäistä toteutusta. Tee niistä yksityisiä ja nimeä ne haluamallasi tavalla.

  • 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ähän järjestykseen). Voit valita järjestyksen itse; tässä on yksi ehdotus, joka edennee suunnilleen helpoimmasta vaikeimpaan: toString, daysLeft, buyer, isExpired, isOpen, advanceOneDay, buy.

    Vinkkejä metodien toteutuksesta

    NäytäPiilota

    Lue Scaladoc tarkasti. Huomaa esimerkiksi, että open ja expired eivät ole toistensa suoria vastakohtia, vaikka esine ei voikaan olla molempia yhtäaikaisesti.

    Suurin osa metodeista on vaikutuksettomia. Niiden tulee vain palauttaa ilmentymämuuttujien arvoja tai selvittää jokin ilmentymämuuttujiin liittyvä tieto. Nämä metodit voi toteuttaa yksinkertaisilla yhden rivin lausekkeilla.

    Esineillä on kaksi vaikutuksellista metodia: advanceOneDay muuttaa jäljellä olevien päivien lukumäärää ja buy ostajaa. Nuo tiedot ovat tallessa ilmentymämuuttujissa. Muista kummassakin metodissa tarkistaa, onko esine auki; voit käyttää isOpen-metodia.

  • 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 testisovelluksen TestApp, jonka avulla voit luoda ja koekäyttää FixedPriceSale-olioita vuorovaikutteisesti tällaisessa ikkunassa:

    ../_images/auctionhouse1_gui.png

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ä.

Moduulissa 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

  1. Lue moduulin Scaladoc-dokumentaatiosta nyt ainakin luokkien yleiskuvaukset sekä kuvaukset Interval-luokan metodeista length ja toString. Ne toteutetaan tässä vaiheessa.
  2. Luo uusi tiedosto Interval-luokkaa varten.
  3. 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ä?
  4. Laadi metodi length. Käytä apuna Moment-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ä tee time-ilmentymämuuttujasta julkista.
  5. Laadi metodi toString. Se on vähän monimutkaisempi. Vinkkejä:
    • Hyödynnä aiemmin laatimaasi metodia.
    • Käytä if-käskyjä ja else-osioita erottaaksesi toisistaan tarvittavat kolme tapausta.
    • Muista, että merkkijononkin voi "kertoa", esim. "ho" * 3 tuottaa "hohoho".
  6. Siirry TimeTest-ohjelmaan, jolle on pohjustus valmiina. Lisää sinne käskyjä, joilla testaat Interval-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!

  1. Laadi Interval-luokan metodi nimeltä isLaterThan. Huomaa, että metodinimeä on kuormitettu (luku 4.1) ja tällaisia metodeita on kaksi. Yksi ottaa Moment-tyyppisen parametrin ja toinen Interval-tyyppisen. Laadi näistä nyt ensinmainittu. Käytä apuna erästä olemassa olevaa metodia.
  2. Laadi sitten toinen Interval-luokan isLaterThan-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 yhdelle isLaterThan-metodeista.
  3. Lisää taas TimeTest-testiohjelmaasi uusia käskyjä ja aja se. Kutsu testiohjelmasta molempia isLaterThan-metodeita ja varmista, että ne toimivat.
  4. Lisää myös Moment-luokkaan sieltä vielä puuttuva isLaterThan-metodi, joka ottaa Interval-tyyppisen parametrin.

Vaihe 3/4: logiikkaoperaatioita

Toteuta lisää metodeita Interval- ja Moment-luokkiin:

  1. Toteuta se Interval-luokan contains-nimisistä metodeista, joka ottaa parametriksi Moment-olion. Hyödynnä logiikkaoperaattoreita ja aiemmin laadittuja metodeita.
  2. Toteuta toinenkin contains-metodi. Hyödynnä tässäkin logiikkaoperaattoreita ja aiemmin laadittuja metodeita. Huomaa lisäksi yllä kerrattu asia metodinimien kuormittamisesta.
  3. Toteuta Moment-luokan isIn-metodi. Se tekee käytännössä "saman asian toisin päin" kuin eräs aiemmin laatimasi metodi, joten sen voi toteuttaa yksinkertaisella tavalla.
  4. Toteuta Interval-luokan overlaps-metodi. Logiikkaoperaattoreista ja edellä laadituista metodeista on jälleen apua. Muistithan huomioida kaikki erilaiset tapaukset?
  5. 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.

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

Kuinka yksityinen on private?

Kysyttyä: toimiiko tuo Moment tosiaan? timehä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
Muuttuja 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:

Aika selkee homma. Eli jos ei tee konstruktoriparametreista ilmentymämuuttujia oliota luodessa niin ohjelma kaatuu jos myöhemmässä vaiheessa — — yrittää käyttää metodia joka yrittää käyttää konstruktorimuuttujia, 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...

... konstruktoriparametrin 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 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ää vain this-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. 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 konstruktoriparametrimuuttuja on lähtokohtaisesti 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 “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-moduuli. 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än FixedPriceSale. Voit napata koodia ratkaisustasi DutchAuction-luokkaasi.
  • Voit käyttää samaa TestApp-ohjelmaa pakkauksesta o1.auctionhouse.gui kokeillaksesi ratkaisusi toimivuutta. Voit toki myös kirjoittaa oman tekstipohjaisen testiohjelman.
  • Kokonaisluvusta saa vastaavan desimaaliluvun...
    • luvun 1.3 konstein: 1.0 * kokonaisluku
    • tai seuraavan luvun 5.2 konstein: kokonaisluku.toDouble

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

WETWET?

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-moduulin 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!

Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.

Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.

Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.

Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.

Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee- ja Kelmu.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Tapa, jolla käytämme O1Libraryn työkaluja (kuten Pic) yksinkertaiseen graafiseen ohjelmointiin, on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.

Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki. Pääkehittäjänä on tällä hetkellä Markku Riekkinen, jonka lisäksi A+:aa ovat kehittäneet kymmenet Aallon opiskelijat ja muut.

A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen ovat luoneet Nikolai Denissov, Olli Kiljunen ja Nikolas Drosdek yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.

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

Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.

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