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

Luku 1.7: Omien funktioiden luominen

Tästä sivusta:

Pääkysymyksiä: Miten funktio käyttää sille välitettyjä parametreja, ja miten se muutenkin saa tehtävänsä hoidettua? Miten kirjoitan itse Scala-funktion, joka hoitaa jonkin tehtävän?

Mitä käsitellään? Funktioiden määritteleminen koodiksi: def, parametrimuuttujat, funktion runko, arvon palauttaminen, paikalliset muuttujat. Funktion suorituksen vaiheet; kutsupino ja kehykset.

Mitä tehdään? Luetaan ja tehdään harjoituksia. Ohjelmoidaan itse REPLin ulkopuolellakin.

Suuntaa antava työläysarvio:? Pari tuntia tai yli. Monille tähän mennessä haastavin luku. Ensiaskelet koodin kirjoittamisen parissa voivat olla haparoivia, vaikka tässä ei vielä mitään erityisen monimutkaista tehdäkään.

Pistearvo: A90.

Oheismoduulit: Aliohjelmia.

../_images/sound_icon.png

Muuta: Eräissä kohdissa on kaiuttimista tai kuulokkeista hyötyä. Aivan pakolliset ne eivät ole.

../_images/person01.png

Johdanto

Tässä luvussa käsittelemme osin samoja funktioita kuin edellisessä ja osin uusia, mutta nyt katsomme myös funktiot toteuttavaa ohjelmakoodia.

Käyttääksesi tämän luvun funktioita itse REPLissä, käynnistä REPL kuten edellisessä luvussa: varmista, että Project-näkymässä on valittuna Aliohjelmia-moduuli, kun painat Ctrl+Shift+D. (Vaihtoehtoisesti sekin toimii, että avaat Aliohjelmia-moduulin sisältämän kooditiedoston aktiiviseksi editoriin ja käynnistät sitten REPLin.)

Löydät lukuun liittyvän ohjelmakoodin moduulin sisältä tiedostosta o1/aliohjelmia/aliohjelmia.scala.

Funktion määrittely

Tarkastellaan luvusta 1.6 tuttua keskiarvo-funktiota, jota käytetään REPLissä näin:

keskiarvo(2.0, 5.5)res0: Double = 3.75

Jotta tuo käsky toimisi, pitää keskiarvofunktion ensin olla määritelty. Tämän funktion määrittely on varsin yksinkertainen, mutta siinä tulee jo esiin useita tärkeitä asioita.

Funktion toimintaperiaatteen voi kuvata suomeksi näin: "Kun keskiarvo-funktiota kutsutaan, se muodostaa palautusarvon laskemalla ensimmäisen parametrin arvo yhteen toisen parametrin arvon kanssa ja jakamalla summan kahdella". Alla on koodinpätkä, joka määrittelee keskiarvofunktion juuri tähän tapaan mutta Scala-kielellä.

(Tästä eteenpäin esimerkit vähitellen monimutkaistuvat, ja monet niistä näytetään seuraavanlaisissa laatikoissa, joissa erilaiset ohjelman osat on korostettu värein. IntelliJ’hän tekee Scala-koodille samoin, joskin eri väreillä.)

def keskiarvo(eka: Double, toka: Double) = (eka + toka) / 2

Funktion määritelmä alkaa avainsanalla def (englannin sanasta define), jonka perään kirjoitetaan ohjelmoijan valitsema funktion nimi. Scalassa funktiot, kuten muuttujatkin, on tapana nimetä pienellä alkukirjaimella.

Tässä ilmoitamme, että "silloin kun keskiarvo-funktiota kutsutaan, pitää antaa parametreiksi kaksi Double-arvoa". Yleisemmin sanoen: tässä määritellään funktion parametrimuuttujat (virallisemmin muodolliset parametrit; formal parameters) ja niiden tietotyypit.

Parametrimuuttujille määritellään nimet. Tässä ensimmäisen parametrimuuttujan nimi on yksinkertaisesti eka ja toisen toka.

Huomaa yhtäsuuruusmerkki, jonka perään kirjoitetaan...

... funktion varsinainen toteutus eli funktion runko (function body). Funktion runko toteuttaa sen algoritmin, jolla funktion tehtävä saadaan hoidettua. Tässä tapauksessa runkona on yksi aritmeettinen lauseke, joka määrää, minkä arvon keskiarvo-funktio palauttaa kutsuttaessa.

Huomaa, että parametrimuuttujat eka ja toka todella ovat muuttujia, tarkemmin sanoen val-muuttujia. Parametrimuuttujat eivät saa arvojaan muiden muuttujien lailla sijoituskäskyllä vaan funktiokutsusta, mutta muuten niitä voi käyttää aivan tuttuun tapaan.

Funktiokutsun vaiheet ja kutsupino

Seuraavassa animaatiossa näet jälleen vaiheittain muuttuvan diagrammin tietokoneen muistiin tallennetuista tiedoista. Tällä kertaa katsomme funktion suorituksen sisään, ja näet esimerkiksi parametrimuuttujien toimintaperiaatteen paremmin. Katso tämä animaatio teksteineen perusteellisesti! Se esittelee jatkon kannalta erittäin tärkeitä uusia käsitteitä.

Animaation kuvittama pieni ohjelmanpätkä ei tee mitään mullistavaa, kunhan vähän koekäyttää keskiarvofunktiota:

Animaatiossa näit kehyksiä (frame). Kehykset muodostavat kutsupinon (call stack tai vain stack), joka todella toimii kuin pino: kun kutsu käynnistyy, lisätään kutsupinoon uusi kehys, ja kun kutsu päättyy arvon palauttamiseen, poistetaan tuo kehys pinon päältä. Tietokone siis pitää kutsupinon kehyksissä kirjaa siitä, mitä funktiokutsuja on kullakin hetkellä ohjelman suorituksen aikana käynnissä ja mitä tietoja näihin kutsuihin liittyy. Aktiivisena on aina pinon kulloinkin päällimmäinen kehys, kun taas alemmat kehykset (joita äskeisessä esimerkissä oli vain yksi) odottavat, että niiden käyttö voi jatkua, kunhan viimeksi kutsutun funktion suoritus on päättynyt.

Toivottavasti animaatio jo selkeytti funktiokutsujen vaiheita. Joka tapauksessa kutsupinon ja kehysten merkitys korostuu pian, kunhan käsittelemme vähän monimutkaisempia funktioita.

Funktiokutsu — siis mikä?

Luvussa 1.2 mainittiin, että sanalla "ohjelma" viitataan joskus staattiseen ohjelmakoodiin ("Ohjelmassa on 100 riviä.") ja joskus siihen dynaamiseen prosessiin, joka syntyy, kun ohjelma ajetaan ("Ohjelma tallensi käyttäjän syöttämät tiedot.").

"Funktiokutsu" on moniselitteinen samalla tavoin. Joskus sillä tarkoitetaan ohjelmakoodiin kirjoitettua lauseketta: "Rivin 15 funktiokutsussa on välimerkkivirhe." Joskus taas viitataan siihen prosessiin, jota äskeinen animaatio esitteli ja joka syntyy funktiota suoritettaessa ohjelma-ajon aikana: "Funktiokutsu päättyi, ja palautettiin arvo 4."

Molemmista merkityksistä on hyvä tietää, kun luet kurssimateriaalia ja muuta ohjelmointiin liittyvää.

Funktionmäärittelemisharjoitus

Laaditaan pieni vaikutukseton funktio, jota voi käyttää vaikkapa näin (kunhan funktio on ensin toteutettu):

huuda("Jipii")res1: String = Jipii!
val tulos = huuda("Hahaa")tulos: String = Hahaa!
val kovempaa = huuda(tulos)kovempaa: String = Hahaa!!

Alla on pohja funktion toteutukselle. Siitä on "piilotettu" viisi kohtaa laittamalla kunkin niistä tilalle kolme kysymysmerkkiä.

??? ???(lause: ???) = ??? + ???

Vaihda kysymysmerkkien tilalle muut koodinpätkät niin, että syntyy esimerkin mukaisesti toimivan huuda-funktion määrittely. Kukin kysymysmerkkikohta korvautuu eri koodinpätkällä. Käytä mallina ylempänä annettua keskiarvo-funktion määrittelyä.

Kirjoita alle toimiva toteutus huuda-funktiolle (eli tuo kysymysmerkkirivi, josta olet korvannut kysymysmerkit muulla koodilla):

Ohjelmointitehtävä: metreiksi

Tehtävänanto

Isossa-Britanniassa ja joissakin sen entisissä siirtomaissa käytetään edelleen laajasti mittayksikköinä tuumaa ja jalkaa. Yksi tuuma on 2,54 cm, ja yksi jalka on 12 tuumaa. Mittoja on tapana ilmaista näitä yksiköitä yhdistelemällä. Esimerkiksi noin 180-senttisen ihmisen pituuden voi sanoa olevan 5 jalkaa ja 11 tuumaa.

Toteuta vaikutukseton Scala-funktio, joka

  • on nimeltään metreiksi,

  • ottaa ensimmäiseksi parametriksi jalkojen lukumäärän Double-arvona,

  • ottaa toiseksi parametriksi tuumien lukumäärän samaan tapaan, ja

  • laskee ja palauttaa Double-arvon, joka kertoo montako metriä annetut jalat ja tuumat ovat yhteensä (esim. parametriarvojen ollessa 5 ja 11 palautetaan 1.8034).

Etene seuraavissa vaiheissa: pohjustus, koodin kirjoittaminen, testaus. Tarvittaessa korjaa ja testaa uudestaan, kunnes funktio toimii. Alla on lisäohjeita kustakin vaiheesta.

Työn pohjustus

Ota IntelliJ’ssä esiin moduuli Aliohjelmia ja sieltä tiedosto o1/aliohjelmia/aliohjelmia.scala. Huomaat tiedoston alkupäässä merkityn kohdan, johon tässä luvussa kirjoitetaan funktioita.

Koodin kirjoittaminen

  1. Varmista ensin, että ymmärrät täsmälleen, mitä funktion on tarkoitus tehdä! Varo, ettei tämä periaatteessa itsestään selvä vaihe unohdu. Yksinkertaiset huolimattomuusvirheetkin ongelman kuvauksen lukemisessa voivat aiheuttaa kipuja.

  2. Kirjoita funktion koodi merkittyyn paikkaan. Tämä tehtävä ratkeaa yhdellä koodirivillä.

    1. Aloita kirjoittamalla def ja funktion nimi.

    2. Kirjoita parametrimäärittelyt. Nimeä parametrimuuttujat itse mielekkäällä tavalla.

    3. Kirjoita funktion runko. Tässä se on yksi aritmeettinen lauseke, joka tekee halutun yksikkömuunnoksen. (Älä tulosta arvoa vaan palauta se!)

    IntelliJ’n virheilmoituksista

    Koodia editoidessasi huomaat, että silloin tällöin näkyviin ilmestyy punaisia merkintöjä, mm. sahalaitaisia alleviivauksia: error_underline. Myös osa koodista voi värjäytyä punaiseksi. Tämä on IntelliJ’n tapa ilmoittaa havaitsemistaan ongelmista.

    Älä häkelly, vaikka näitä merkintöjä ilmestyisi heti, kun alat koodia kirjoittaa. IntelliJ nimittäin varoittelee koodin nykytilan perusteella, ja keskeneräinen koodi tuottaa helposti virheilmoituksia. Esimerkiksi siinä vaiheessa, kun olet kirjoittanut vasta def-sanan ja nimen, IntelliJ jo älähtää, koska funktiomäärittely on vielä puutteellinen.

    Nämä punaiset merkinnät ovat suuri apu ohjelmoijalle, ja niihin on kyllä syytä suhtautua vakavuudella, mutta vasta sitten, jos niitä on jäljellä, kun olet saanut metreiksi-funktion mielestäsi oikein kirjoitettua.

    Tarkempia virheilmoituksia voi katsoa jättämällä hiiren kursorin virheellisinä korostettujen kohtien päälle. Ikävä kyllä virheilmoitustekstien tulkinta voi olla tähän mennessä opitun perusteella vaikeaa; kurssimateriaalin esimerkeistä voi olla tässä vaiheessa enemmän apua virheiden korjaamisessa.

  3. Huolehdi, ettei koodiin jää IntelliJ’n tekemiä punaisia virhemerkintöjä. Kitke erityisesti välimerkkivirheet. Käytitkö ainakin kaarisulkeita, pilkkua ja yhtäsuuruusmerkkiä? Ja pistettä desimaalierottimena? Kirjoititko muuttujien nimet kaikissa kohdissa juuri samalla tavalla?

Koodin kääntäminen

Ennen kun tietokone voi ajaa Scala-koodisi (REPLissä tai muutenkaan), sen on käännettävä (compile) tuo koodi muotoon, jonka se pystyy suorittamaan. Kääntäessä kone myös tarkistaa, että koko koodi noudattaa kaikkia kielen "pelisääntöjä". Samalla voi ilmetä virheitä, jotka pitää korjata ennen kuin koodia voi käyttää.

IntelliJ kääntää koodisi automaattisesti aina kun käynnistät jonkin ohjelman joko aivan ensimmäistä kertaa tai muokkausten jälkeen. Voit myös koska tahansa pyytää IntelliJ’tä kääntämään ohjelmasi varmistaaksesi, ettei siinä ole automaattisesti havaittavia virheitä. Kokeile tätä nyt.

Valitse IntelliJ’n valikosta Build → Build Module 'Aliohjelmia' tai kätevämmin vastaavalla pikanäppäimellä F10. (IntelliJ’ssä käännöksen käynnistystoiminnon nimenä on "build", joka viittaa yleisesti ohjelman käyttövalmisteluun.)

Pienen hetken jälkeen tapahtuu jompikumpi seuraavista.

Joko:

  • Esiin ei tule virheilmoituksia vaan vain melko huomaamaton teksti vasempaan alakulmaan: Build completed successfully. Jos näin käy, hyvä, mutta teepä silti nyt tahallasi jokin pieni välimerkkivirhe koodiisi ja paina uudestaan F10, niin näet toisenkin tapahtumainkulun.

Tai:

  • Esiin ponnahtaa Build-välilehti ja sinne yksi tai useampia virheilmoituksia. Tämä virheilmoitusluettelo on yleensä samankaltainen (mutta joissain tapauksissa täydellisempi) kuin editorin punaisella merkityt virheet. Koodissa on korjattavaa. Korjaa ja paina uudestaan F10.

Virheiden korjaamisesta ja kääntämisestä

Ennen kuin yrität ajaa ohjelmaasi, huolehdi aina ensin siitä, että olet poistanut IntelliJ’n värittämät virheet. Muuten seuraava vaihe eli testaaminen ei onnistu lainkaan!

IntelliJ on sen verran ovela, että se osaa näyttää useimmat ohjelma-ajon estävät virheet näkyvät "etukäteen" editorissa. Osa tällaisista virheistä voi kuitenkin ilmetä vasta kun koko koodi käännetään.

Koska IntelliJ kääntää koodisi automaattisesti — esim. kun käynnistät REPLin — ei sinun välttämättä tarvitse erikseen käynnistää käännöstä. Pienellä vaivalla voit kuitenkin koska vain painaa F10 ja näin tarkistuttaa koodisi kieliopillisuuden.

Testaus

  1. Käynnistä REPL tuttuun tapaan Ctrl+Shift+D huolehtien, että siihen latautuu juuri Aliohjelmia-projekti. Huomaa, että testataksesi uutta koodia tarvitset uuden REPL-session.

  2. Kokeile kutsua metreiksi-funktiotasi erilaisilla parametriarvoilla. Toimiiko se oikein? Jos ei, niin palaa yllä olevaan kappaleeseen Koodin kirjoittaminen (sen kohtaan yksi, ei suoraan kohtaan kaksi!). Pyydä tarvittaessa assistentilta apua.

    (P.S. Huomasitko, että piti palauttaa metrejä eikä senttimetrejä?)

    REPLin "buuttaamisesta"

    Aina, kun olet muuttanut kooditiedostoasi ja haluat testata sitä REPLissä, on uusi versio ladattava REPLiin. Voit joko sulkea REPLin ja käynnistää sen uudestaan, tai painaa REPLin vasemman yläkulman Rerun-kuvaketta rerun. Jos haluat sitten antaa aiempia käskyjä uudestaan, löydät ne ylä- ja alanuolella kelailemalla.

Palauttaminen

Kun funktiosi toimii, ole iloinen ja palauta ratkaisusi IntelliJ’n kautta.

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

Vapaaehtoista lisätreeniä

Jos haluat, voit kehittää ohjelmointirutiiniasi tällä helpolla treenitehtävällä, joka muistuttaa edellistä tehtävää.

Laadi seuraavat funktiot samaan aliohjelmia.scala-tiedostoon:

  • Funktio kuutionTilavuus, jolle annetaan ainoaksi parametriksi kuution sivun pituus ja joka palauttaa tuonkokoisen kuution tilavuuden. Parametri ja palautusarvo ovat Double-tyyppisiä.

  • Funktio kuutionAla, joka samaan tapaan laskee ja palauttaa annetun sivunmitan perusteella kuution pinta-alan.

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

Funktioiden määritteleminen REPLissä

Äskeisessä tehtävässä kirjoitit ja tallensit funktion erilliseen tiedostoon IntelliJ’n editorissa. Samoin tehdään tulevissa tehtävissä. On silti vaihtoehtoisesti mahdollista kirjoittaa funktioiden määrittelyjä suoraan REPLiin:

def keskiarvo(eka: Double, toka: Double) = (eka + toka) / 2keskiarvo(eka: Double, toka: Double): Double
keskiarvo(2, 1)res2: Double = 1.5

Tämä toinen tapa on pikkukokeiluissa näppärä. IntelliJ’n editorin käyttö kuitenkin valmistaa paremmin tuleviin tehtäviin, ja se on tehtävien palauttamisen kannalta kätevämpää. Editorilla luomasi koodi jää myös itsellesi talteen.

Omissa kokeiluissasi voit hyvin määritellä funktioita myös REPLissä.

Funktionlaatimisharjoitusta ja modulo-operaattori

Johdanto modulo-operaattoriin

Tutuimpien aritmeettisten operaattorien lisäksi ohjelmoinnissa käytetään melko usein myös modulo-operaattoria, joka merkitään Scalassa %. Tätä operaattoria käytetään useimmiten kokonaislukujen kanssa; voidaan esimerkiksi laskea lausekkeen 25 % 7 arvo.

Kokeile modulo-operaattorin käyttöä itse REPLissä erilaisilla arvoilla. Vastaa sitten seuraavaan pikkukysymykseen ja laadi muutama funktio, joissa sovellat tätä operaattoria.

Mikä seuraavista vastaa modulo-operaattorin toimintaa parhaiten?

Parillisuusesimerkki

Laitetaan tietokone tutkimaan luvun parillisuutta. Parillisuuden tutkimisesta voi olla hyötyä vaikkapa silloin, jos halutaan kohdentaa jokin toimenpide joka toiseen kohteeseen, esimerkiksi värittää suuren taulukon parilliset rivit eri taustavärillä kuin parittomat. Tässä käytämme parillisuutta vain lisäesimerkkinä funktion laatimisesta ja modulo-operaattorista.

Nyt laadittavan vaikutuksettoman funktion olisi tarkoitus palauttaa nolla merkiksi siitä, että annettu luku on parillinen. Muutoin se palauttaa 1 tai -1 annetun luvun etumerkin mukaan. Siis näin:

parillisuus(100)res3: Int = 0
parillisuus(2)res4: Int = 0
parillisuus(103)res5: Int = 1
parillisuus(7)res6: Int = 1
parillisuus(-7)res7: Int = -1

Modulo-operaattoria käyttäen funktio on helppo toteuttaa: jaettaessa luku kahdella saadaan jakojäännökseksi nolla vain, jos luku oli parillinen. Tässä toteutus:

def parillisuus(tutkittava: Int) = tutkittava % 2

Voit määritellä tämän funktion itse ja kokeilla sitä REPLissä. Tai siirtyä suoraan seuraavaan tehtävään.

Shakkilautatehtävä: johdanto

../_images/shakki1.png

Olkoon shakkilaudan ruudut numeroitu järjestyksessä 1–64 alkaen vaikkapa vasemmasta yläkulmasta, vasemmalta oikealle rivi kerrallaan. Lisäksi laudan rivit ja sarakkeet (eli shakkikielessä linjat) on kummatkin numeroitu 1–8. Oikealla on kuva.

Jos tiedossa on ruudun numero, vaikkapa 35, niin miten saamme tietokoneen määrittämään ruudun rivi- ja sarakenumerot? Yksi mahdollisuus on laatia kaksi vaikutuksetonta funktiota, joita voi sitten käyttää seuraavasti:

rivi(35)res8: Int = 5
sarake(35)res9: Int = 3

Funktiot voi määritellä näin:

def rivi(ruutu: Int) = ((ruutu - 1) / 8) + 1

def sarake(ruutu: Int) = ((ruutu - 1) % 8) + 1
../_images/shakki2.png

Shakkilautatehtävä: tehtävänanto

Mitä jos ruudut olisikin numeroitu 0–63 ja rivit ja sarakkeet 0–7, kuten viereisessä kuvassa? Laadi vaikutuksettomat funktiot rivi ja sarake, jotka toimivat tähän tapaan:

rivi(34)res10: Int = 4
sarake(34)res11: Int = 2

Ohjeita ja vinkkejä:

  • Toimi samaan tapaan kuin metreiksi-tehtävässä.

  • Kirjoita nämäkin funktiot aliohjelmia.scala-tiedostoon.

  • Jos ymmärrät yllä annetut versiot, joissa numerointi alkaa ykkösestä, niin tehtävä ei ole vaikea. Ratkaisut ovat nollasta alkavalla numeroinnilla samansuuntaiset mutta yksinkertaisemmat.

  • Testaa REPLissä ennen kuin palautat.

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

Usea käsky funktiossa

Laaditaan kokeeksi funktio, jonka olisi tarkoitus tulostaa joka kutsukerralla kolme vakioriviä tekstiä sekä neljäntenä rivinä päätösrivi, joka annetaan funktiolle parametriksi. Funktiota voisi käyttää näin:

haukiOnKala("T. Kalatalouden Keskusliitto")Kun hauki on vähärasvainen, sitä voidaan säilyttää pakastettuna jopa 6 kuukautta.
Vertailun vuoksi mainittakoon, että haukea rasvaisemman lahnan vastaava
säilymisaika on vain puolet eli 3 kuukautta.
T. Kalatalouden Keskusliitto
haukiOnKala("Sen pituinen se, ja tosipa se lienee.")Kun hauki on vähärasvainen, sitä voidaan säilyttää pakastettuna jopa 6 kuukautta.
Vertailun vuoksi mainittakoon, että haukea rasvaisemman lahnan vastaava
säilymisaika on vain puolet eli 3 kuukautta.
Sen pituinen se, ja tosipa se lienee.

Funktion määrittely on seuraava.

def haukiOnKala(loppukaneetti: String) =
  println("Kun hauki on vähärasvainen, sitä voidaan säilyttää pakastettuna jopa 6 kuukautta.")
  println("Vertailun vuoksi mainittakoon, että haukea rasvaisemman lahnan vastaava")
  println("säilymisaika on vain puolet eli 3 kuukautta.")
  println(loppukaneetti)

haukiOnKala-funktion rungossa on useita käskyjä peräkkäin. Tällöin koodirivit kirjoitetaan omille riveilleen tähän tapaan. Tässä siis määritellään, että kaikki neljä println-käskyä suoritetaan peräjälkeen, kun haukiOnKala-funktiota kutsutaan.

Tämä on ensimmäinen esimerkki, jossa näemme usealle riville jakautuvan sisäkkäisen rakenteen ohjelmakoodissa. Tällaiset rakenteet sisennetään (indent) kuten tässä.

Entä palautusarvo?

haukiOnKala-funktiomme palauttaa sisällöttömän Unit-arvon eli niinsanotusti "ei palauta arvoa". Tämä johtuu siitä, että funktion palauttama arvo määräytyy sen mukaan, millaisen arvon viimeisenä suoritettava funktion rungon käskyistä tuottaa. Tässä tapauksessa se on viimeinen println-käsky, joka tuottaa vain sisällöttömän Unit-arvon (kuten luvussa 1.6 todettiin) . Samainen Unit-arvo toimii nyt myös println-funktiokutsun sisältävän haukiOnKala-funktion palautusarvona.

Rivitys ja sisennykset funktion koodissa

Äsken tuli esille, että funktion koodi jaetaan joskus usealle riville ja runko joskus sisennetään. Tämä on tärkeämpää kuin ehkä heti arvaisi. Katsotaanpa tätä vielä tarkemmin.

Sisennysten merkitys

Sisennyksiä käytetään ohjelmointikielissä laajasti. Joissakin kielissä sisennykset ovat täysin vapaaehtoiset eivätkä vaikuta ohjelman toimintaan, mutta sisentämistä pidetään silti hyvänä tyylinä, koska sisennykset korostavat ohjelman rakennetta ja helpottavat lukemista. Toisissa kielissä sisennyksillä myös aidosti vaikutetaan ohjelman rakenteeseen ja toimintaan.

Scala 3 kuuluu jälkimmäiseen ryhmään: sisennykset eivät ole pelkkä tyyliseikka. Edellinen funktiomme ei toimisi, jos sen kirjoittaisi vaikkapa näin sekavasti sisentäen:

// Ei toimi.
def haukiOnKala(loppukaneetti: String) =
    println("Kun hauki on vähärasvainen, sitä voidaan säilyttää pakastettuna jopa 6 kuukautta.")
  println("Vertailun vuoksi mainittakoon, että haukea rasvaisemman lahnan vastaava")
println("säilymisaika on vain puolet eli 3 kuukautta.")
    println(loppukaneetti)

Ero kieliversioiden välillä

Äskeinen teksti pätee Scala-kielen nykyversioon 3, jota käytämme. Kielen aiemmissa versioissa sisennykset olivat vapaaehtoiset. Tällöin oli käytettävä ylimääräisiä sulkumerkkejä funktion rungon rajaamiseen, minkä lisäksi sisennykset oli kuitenkin tapana kirjoittaa.

Saatat törmätä vanhalla kieliversiolla kirjoitettuun koodin nettilähteissä tai vanhoissa kirjoissa. Vanhasta tyylistä kerrotaan hieman lisää tämän sivun lopussa ja tyylioppaassamme.

Sisennysten koko

Scalassa on yleensä tapana käyttää kahden merkin levyistä sisennystä, kuten alkuperäisessä koodissamme ja tulevissa esimerkeissä. Muunkinkokoinen on sisennys mahdollinen, kunhan on johdonmukainen. Tämä seitsemällä välilyönnillä sisennetty koodi toimii kyllä, vaikkei noudatakaan yleistä käytäntöä:

// Toimii, mutta tyyli on tässä poikkeava.
def haukiOnKala(loppukaneetti: String) =
       println("Kun hauki on vähärasvainen, sitä voidaan säilyttää pakastettuna jopa 6 kuukautta.")
       println("Vertailun vuoksi mainittakoon, että haukea rasvaisemman lahnan vastaava")
       println("säilymisaika on vain puolet eli 3 kuukautta.")
       println(loppukaneetti)

Jos sinusta tuntuu siltä, että kahden välilyönnin sisennys on luettavuuden kannalta liian vähän, voit kyllä käyttää koodissasi esimerkiksi neljää.

Milloin sisennän?

Koodin jakaminen sisennetyille riveille oli haukiOnKala-esimerkissä tarpeen, koska halusimme tulostuskäskyjä samaan funktioon useita peräkkäin. Itse asiassa saman saa kyllä tehdä, vaikka funktio olisi pienempikin. Esimerkiksi keskiarvo-funktion voi kirjoittaa kummalla tahansa seuraavista tavoista.

Tapa 1 (ei rivitystä):

def keskiarvo(eka: Double, toka: Double) = (eka + toka) / 2

Tapa 2 (rivitys):

def keskiarvo(eka: Double, toka: Double) =
  (eka + toka) / 2

Monet noudattavat Scala-ohjelmia kirjoittaessaan seuraavia periaatteita:

  • Jos funktion runko koostuu useasta peräkkäisestä käskystä, rivitetään aina kuten haukiOnKala-funktiossa ja tavassa 2 yllä.

  • Samoin jos funktiolla on tilaa muuttavia vaikutuksia (esim. se tulostaa jotain), suositaan tapaa 2, vaikka runko olisi yksirivinenkin.

  • Tapaa 1 voi käyttää, jos runko koostuu yhdestä rivistä, joka ei vaikuta ohjelman tilaan. Tuo keskiarvo-funktio on esimerkki funktiosta, jolle tämä pätee. (Kuitenkin jos rivistä tulisi näin todella pitkä, on silti parempi vaihtaa riviä yhtäsuuruusmerkin jälkeen.)

Tiivistettynä siis: valitaan tapa 1 vain, jos koodista tulee näin yksittäinen, vaikutukseton rivi, joka ei ole ylipitkä.

Kurssimateriaalissakin on tavallisesti noudatettu tätä käytäntöä. Voit itse toimia samoin. Tyylisäännöt voivat aluksi tuntua sekavilta, mutta älä anna sen haitata oman koodin kirjoittamista. Jos se tuntuu helpommalta, voit aivan mainiosti tehdä niin, että käytät tapaa 2 kaikkiin itse laatimiisi funktioihin. Aina saa rivittää ja sisentää!

Mistä tiedän, onko funktio vaikutuksellinen?

Kuvittele tilanne: Ohjelman suoritus on jossakin tietyssä tilassa: muistissa on tallessa joitakin tietoja ja näytöllä näkyy jotakin. Kutsutaan funktiota. Funktion suoritus tulee valmiiksi ja tuottaa palautusarvon, ja hiukan aikaa kuluu. Ollaanko muilta osin samassa tilanteessa kuin ennen kutsun suorittamista?

  • Jos kutsuttu funktio on esimerkiksi keskiarvo-funktio, niin ollaan: saatiin tulos, mutta mikään muu ei ole toisin. Kyseessä on vaikutukseton funktio.

  • Jos se taas on esimerkiksi println, niin ei olla: nyt ohjelma on tuottanut rivin tulostetta. Kyseessä on vaikutuksellinen funktio.

Lisäksi voit miettiä tähän tapaan: jos ohjelmalla olisi jo ollut tiedossa kyseisen funktion palautusarvo, olisiko funktiota edes tarvinnut kutsua? Vaikutuksettoman keskiarvofunktion tapauksessa ei olisi: olisi sama korvata kutsu keskiarvo(5, 10) literaalilla 7.5. Vaikutuksellisen tulostusfunktion println tapauksessa taas mitään ei saada tulostettua, ellei funktiota kutsuta.

Funktion rungon lopun voi merkitä erikseenkin

Joskus on kiva selkeyden vuoksi kirjata oikein näkyvästi, mihin funktion koodi loppuu. Scala tarjoaa mahdollisuuden kirjoittaa rungon perään end-rivin joka toimii loppumerkkinä (end marker). Aiemmat esimerkkifunktiomme voi kirjoittaa näinkin:

def keskiarvo(eka: Double, toka: Double) =
  (eka + toka) / 2
end keskiarvo
def haukiOnKala(loppukaneetti: String) =
  println("Kun hauki on vähärasvainen, sitä voidaan säilyttää pakastettuna jopa 6 kuukautta.")
  println("Vertailun vuoksi mainittakoon, että haukea rasvaisemman lahnan vastaava")
  println("säilymisaika on vain puolet eli 3 kuukautta.")
  println(loppukaneetti)
end haukiOnKala

Loppumerkki alkaa sanalla end. Kun kyseessä on funktion loppu, kuten tässä, kirjoitetaan perään funktion nimi. Huomaa, että loppumerkkejä ei sisennetty syvemmälle, vaan ne ovat linjassa def-sanojen kanssa.

Loppumerkkien tarkoitus on auttaa lukijaa, ei muokata ohjelman toimintaa. Yleensä funktion koodin lopun kyllä huomaa ilmankin, joten loppumerkkejä ei useimmiten kirjoiteta. Tässä oppikirjassakaan emme tapaa niitä funktioiden perään kirjoitella; saat kyllä, jos haluat.

Loppumerkeillä on muutakin käyttöä, mistä lisää myöhemmin.

Säestystehtävä

Tehtävänanto

Laadi kaksiparametrinen vaikutuksellinen funktio nimeltä saesta, joka säestää tulostamaansa tekstiä äänellä. Molemmat funktion parametrit ovat merkkijonoja: funktio tulostaa ensimmäisen vastaanottamistaan merkkijonoista ja soittaa toisen.

Funktiota pitää voida käyttää näiden esimerkkien tapaan.

saesta("Nänänänä nänänänä nänänänä nänänänä BÄTMÄÄN!",
       "[49]<" + "(d<g>)(d<g>)(db<g>)(db<g>)(c<g>)(c<g>)(c#<g>)(c#<g>)" * 2 + "[54]>(FG)-.(FG)---/160")Nänänänä nänänänä nänänänä nänänänä BÄTMÄÄN!
saesta("... and introducing acoustic guitar",
       "[26]    >EF#A---G#---F#---E---F#G#---------EF#A---G#---F#---E---F#EF#---------/192")... and introducing acoustic guitar

Rivinvaihdot ovat esimerkin REPL-syötteissä vain estämästä rivien ylipituutta. Ne eivät ole välttämättömät.

Ohjeita ja vinkkejä

  • Kirjoita ratkaisusi samaan aliohjelmia.scala-tiedostoon kuin aiemmissakin tämän luvun tehtävissä.

  • Valitse parametrimuuttujille jotkin järkevät nimet.

  • Tässä tehtävässä ei ole käytännön merkitystä sillä, kummassa järjestyksessä laitat println- ja play-käskyt funktion runkoon. Voit itse valita, kunhan teet molemmat.

    • Soittamisfunktio on määritelty niin, että äänet soitetaan taustalla, eikä tietokone jää odottamaan soiton loppua ennen kuin suorittaa seuraavan käskyn. Tuloste siis ilmestyy ripeästi ruudulle, vaikka laittaisit soittokäskyn ennen tulostuskäskyä.

  • Noudata yllä annettua tyyliohjetta ja jaa koodisi usealle riville. Huomaa välimerkkisäännöt.

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

Työkaluja funktioiden toteuttamiseen

Äskeisistä esimerkeistä jo selvisi, että funktion rungossa voi käyttää aiemmissa luvussa nähtyjä ohjelmointitekniikoita: aritmeettisia operaattoreita sekä println- ja play-funktioita. Muitakin tuttuja käskyjä voi käyttää. Hieman jäljempänä tässä luvussa katsotaan esimerkiksi, miten funktion sisään voi määritellä uusia muuttujia. Lisää funktioiden toteuttamiseen sopivia käskyjä opit kurssin mittaan (esim. vaihtoehtojen välillä valitseminen, käskyjen toistaminen, yms.).

Hyvin yleistä on käyttää olemassa olevia funktioita uuden funktion toteutuksessa. Esimerkiksi kahden pisteen (x1,y1) ja (x2,y2) etäisyyden laskevassa funktiossa voi hyödyntää scala.math-pakkauksen hypotenuusafunktiota:

def etaisyys(x1: Double, y1: Double, x2: Double, y2: Double) = hypot(x2 - x1, y2 - y1)

Myös aiemmin kohdattuja o1-pakkauksen kuvafunktioita (luku 1.3) voi hyödyntää omien funktioiden määrittelyissä.

Kuvan muodostava funktio

Tässä funktio, jolla voi tuottaa erikokoisia punaisia "pallon kuvia":

def punapallo(koko: Int) = circle(koko, Red)

Käyttöesimerkki:

val pallura = punapallo(50)pallura: Pic = circle-shape
val isompi = punapallo(300)isompi: Pic = circle-shape
show(isompi)

Pikkutehtävä: pystypalkkeja

Laadi aliohjelmia.scala-tiedostoon funktio pystypalkki, joka palauttaa kuvan sinisestä suorakaiteesta, joka on kymmenen kertaa niin monta pikseliä korkea kuin se on leveä.

Funktion tulee ottaa vastaan yksi Int-tyyppinen parametriarvo, palkin leveys. Tässä pari käyttöesimerkkiä:

val palkinKuva = pystypalkki(80)palkinKuva: Pic = rectangle-shape
show(palkinKuva)show(pystypalkki(180))

Nimeä parametrimuuttuja järkevästi. Testaa funktiotasi REPLissä show-funktiota apuna käyttäen. Huomaa, että funktiosi ei pidä kutsua show-funktiota ja laittaa kuvaa näkyviin, vaan palauttaa kuva. Tällöin funktiota voi käyttää show-käskyn kanssa yhdessä kuten esimerkissä yllä.

Voit kerrata tarvittavia työkaluja luvusta 1.3.

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

Pikkutehtävä: palkkien kuormittaminen

Jatka edellistä tehtävää ja laadi toinenkin pystypalkki-niminen funktio. Älä siis poista alkuperäistä vaan lisää toinen samanniminen funktio, joka ottaa kaksi parametria: leveyden (Int) ja värin (Color). Sitä on tarkoitus käyttää tähän tyyliin:

val mustaPalkki = pystypalkki(80, Black)mustaPalkki: Pic = rectangle-shape
show(mustaPalkki)

Tämä jälkimmäinen versio palkkifunktiosta on siis värin suhteen joustavampi (abstraktimpi) mutta vaatii käyttäjältään yhden lisäparametrin. Sekin tuottaa suorakaiteen, joka on kymmenen kertaa niin korkea kuin leveä.

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

Huomasimme, että Scala sallii meidän määritellä useita samannimisiä funktioita samaan yhteyteen. Moista sanotaan funktion nimen kuormittamiseksi (tai ylikuormittamiseksi; overloading). Tämä on kuitenkin sallittua vain, jos noilla samannimisillä funktioilla on erilaiset parametriluettelot.

Kuormitetut funktiot (tässä: kaksi pystypalkki-funktiotasi) ovat toisistaan täysin erilliset. Se kumpi tulee kutsutuksi, riippuu siitä, millaisia parametrilausekkeita käytät funktiota kutsuessasi.

Pikkutehtäviä: funktioiden tulkintaa

Vastaa kunkin funktion kohdalla kaikki ne vaihtoehdot, jotka kuvaavat, millainen funktio on kyseessä. Tässä ensimmäinen funktio:

def etaisyys(x1: Double, y1: Double, x2: Double, y2: Double) = hypot(x2 - x1, y2 - y1)
def punapallo(koko: Int) = circle(koko, Red)
def kokeilu1(luku: Int) =
  println("Luku on: " + luku)

Huomaa seuraavan kokeilufunktion parametrimuuttujan tyyppi: parametriarvoksi tulee antaa viittaus kokonaislukuja sisältävään puskuriin.

def kokeilu2(lukuja: Buffer[Int]) =
  lukuja(0) = 100
def kokeilu3(luku: Int) =
  println("Luku on: " + luku)
  luku + 1

Mikä seuraavista kuvaa parhaiten sitä, miten funktiokutsun suorittaminen alkaa (siinä tavallisessa ohjelmien suoritusmallissa, joka on tässä luvussa esitelty)? Kertaa tarpeen mukaan luvun alun animaatiosta. Voit myös pohtia, millaisissa tilanteissa on merkitystä sillä, evaluoidaanko kaikki parametrilausekkeet ennen funktiokutsun alkua vai vasta funktiokutsun aikana.

Paikalliset muuttujat

Esimerkki: verofunktio

Laaditaan vaikutukseton funktio, jolla voi määrittää tuloista verotettavan summan yksinkertaisessa verotusjärjestelmässä:

  • Tiettyyn tulorajaan asti kaikista tuloista maksetaan tietyn perusprosentin verran veroa.

  • Tulorajan ylittävistä tuloista maksetaan (korkeamman) lisäprosentin verran veroa.

Haluttaisiin, että laadittavaa funktiota verot voisi kutsua antaen kokonaistulot, tulorajan sekä perus- ja lisäprosentit parametreiksi kuten tässä:

verot(50000, 30000, 0.2, 0.4)res12: Double = 14000.0
verot(25000, 30000, 0.2, 0.4)res13: Double = 5000.0

Funktion voisi toteuttaa yhdellä rivillä, jolle kirjoitettaisiin koko palautusarvon määrittävä lauseke. Kuitenkin hieman selkeämpi ratkaisu saadaan laittamalla välituloksia muuttujiin:

def verot(tulot: Double, tuloraja: Double, perusprosentti: Double, lisaprosentti: Double) =
  val perusosa = min(tuloraja, tulot)
  val lisaosa = max(tulot - tuloraja, 0)
  perusosa * perusprosentti + lisaosa * lisaprosentti

Tässä määritellään kaksi paikallista muuttujaa (local variable) ja sijoitetaan niille välituloksia arvoiksi.

Kun funktio koostuu useasta käskystä, niin viimeinen käsky määrää, mitä funktio palauttaa. Tässä tapauksessa palautetaan viimeisellä rivillä olevan aritmeettisen lausekkeen arvo. Kaksi aiempaa riviä vain valmistelevat tämän viimeisen lausekkeen evaluointia.

Paikalliset muuttujat ovat käytettävissä vain funktion omasta ohjelmakoodista, eivät muualta. Funktion kutsujan ei tarvitse niistä tietää, eikä hän voi niitä funktion ulkopuolelta käyttää, vaikka tietäisikin. Esimerkiksi verot-funktiota voi kutsua autuaan tietämättömästi siitä, minkä nimisiä paikallisia muuttujia — jos mitään — sillä on. Ja seuraava yritys REPLätä funktion sisäisiä muuttujia ei siis onnistu, oli verot-funktiota kutsuttu aiemmin tahi ei:

lisaosa * lisaprosentti-- Error:
  |lisaosa * lisaprosentti
  |^^^^^^^
  |Not found: lisaosa

Itse asiassa paikalliset muuttujat eivät ohjelma-ajon aikana ole edes olemassa kuin kyseistä funktiota suoritettaessa. Ne luodaan funktiokutsua vastaavaan kutsupinon kehykseen, ja niille varattu muisti vapautuu muuhun käyttöön funktiokutsun päättyessä. Tämä konkretisoituu seuraavassa animaatiossa:

Pikkutehtäviä: paikalliset muuttujat

Tarkastele, miten välituloksia varten luodut paikalliset muuttujat ja parametrimuuttujat kuvattiin animaatiossa. Arvioi sitten, pitääkö seuraava väite paikkansa vai ei: "Parametrimuuttujat ovat paikallisia muuttujia nekin."

Vastaa kunkin funktion kohdalla kaikki ne vaihtoehdot, jotka kuvaavat, millainen funktio on kyseessä.

def verot(tulot: Double, tuloraja: Double, perusprosentti: Double, lisaprosentti: Double) =
  val perusosa = min(tuloraja, tulot)
  val lisaosa = max(tulot - tuloraja, 0)
  perusosa * perusprosentti + lisaosa * lisaprosentti
def kokeilu4(sana: String) =
  var luku = 1
  println(sana + ": " + luku)
  luku = luku + 1
  println(sana + ": " + luku)
  luku = luku + 1
  println(sana + ": " + luku)
  luku
def kokeilu5(aluksi: Int) =
  var luku = aluksi
  luku = luku + 1
  luku = luku + 1
  luku = luku + 1
  luku

Otetaan tehtäväksi laatia hieman luvussa 1.6 käyttämääsi kaanon-funktiota muistuttava mutta toteutukseltaan yksinkertaisempi funktio, jolla voi soittaa tietyn melodian kahdella eri soittimella peräjälkeen (ei päällekkäin). Funktio toimisi näin:

val duetto = kahdella("g#e--f#c#----", 26, 66, 5)duetto: String = [26]g#e--f#c#----     [66]g#e--f#c#----
play(duetto)

Funktio palauttaa merkkijonon, johon sisältyy ensimmäisenä parametrina annettu merkkijono kahdesti.

Toinen ja kolmas parametri määräävät soittimet. kahdella-funktiomme tekee niiden perusteella palauttamaansa merkkijonoon hakasuljemerkinnät, jotka play-funktio osaa tulkita ohjeeksi soittaa pätkä ensin yhdellä sitten toisella instrumentilla.

Viimeinen parametri määrää, kuinka pitkä tauko väliin laitetaan.

Tässä on kahdella-funktiolle melkein toimiva toteutus, joka löytyy myös tiedostosta aliohjelmia.scala.

def kahdella(melodia: String, eka: Int, toka: Int, tauonPituus: Int) =
  val melodiaEkalla = "[" + eka + "]" + melodia
  val melodiaTokalla = "[" + toka + "]" + melodia
  val tauko = " " * tauonPituus
  val kahdestiSoitettuna = melodiaEkalla + tauko + melodiaTokalla

Tässä toteutuksessa on ohjelmointivirhe, joka on aloittelijalle tyypillinen. Tutki koodia huolellisesti; voit myös kokeilla käyttää sitä REPLissä. Valitse seuraavista väittämistä kaikki paikkansa pitävät.

Miten äskeisen vian voi korjata? Valitse yksi tai useampia väittämiä. Voit myös korjata funktion tiedostoon, jos haluat.

Kurssiarvosanatehtävä

Tehtävänanto

Laadi vaikutukseton funktio, joka määrittää erään kuvitteellisen kurssin kokonaisarvosanan osasuoritusarvosanojen perusteella. Esimerkkikurssillamme kokonaisarvosana on tällöin tehtäväarvosanan (0–4), tenttibonuksen (0 tai 1) ja aktiivisuusbonuksen (0 tai 1) summa; kuitenkaan se ei voi ylittää viitosta.

Funktion on oltava seuraavan spesifikaation mukainen.

  • Sen nimi on kurssiarvosana.

  • Se ottaa parametreikseen tehtäväarvosanan ja tentti- ja aktiivisuusbonukset kokonaislukuina (tässä järjestyksessä).

  • Se palauttaa kurssin kokonaisarvosanan väliltä 0–5 samaten kokonaislukuna. (Ei tulosta vaan palauttaa.)

Sinun tulee huolehtia siitä, että vaikka osa-arvosanojen summa olisi yli 5, niin kokonaisarvosana ei ole. Kuitenkaan sinun ei tässä tehtävässä tarvitse huolehtia siitä, mitä tapahtuu, jos joku antaakin funktiolle parametriksi virheellisen arvon (esim. negatiivisen tehtäväarvosanan tai liian suuren tenttibonuksen).

Ohjeita ja vinkkejä

Kirjoita taas aliohjelmia.scala-tiedostoon.

Noudata myös samaa työprosessia, joka on tässä vielä kerran kertauksena:

  1. Varmista ensin, että ymmärrät täsmälleen, mitä funktion on tarkoitus tehdä!

  2. Toteuta funktio vaiheittain: nimi, parametrimuuttujat (nimeä järkevästi!) tyyppeineen, funktion runko.

  3. Alusta uusi REPL-sessio.

  4. Koekäytä funktiotasi eri parametriarvoilla ja palaa kohtaan 1 tarvittaessa.

  5. Palauta vasta, kun olet tyytyväinen koodisi toimintaan.

Eräästä luvun 1.6 esittelemästä ja tässäkin luvussa esiintyneestä matemaattisesta funktiosta on hyötyä. (Jos kokeilet noita funktioita REPLissä, muista import scala.math.*. Tiedostoon aliohjelmia.scala tuo import on jo kirjattu, joten siellä nuo käskyt ovat käytössä.)

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

package-määrittelyistä

Muokkaamasi tiedoston alussa on rivi, jossa lukee package-alkuinen merkintä:

package o1.aliohjelmia

Merkintä on varsin itseselitteinen: package-sanan avulla kirjataan kunkin Scala-kooditiedoston alkuun, mihin pakkaukseen tuon tiedoston sisältö kuuluu.

package-määrittelyt ovat tarpeellisia mutta rutiininomaisia. Niitä ei useimpiin tämän kurssimateriaalin koodinpätkiin ole kirjoitettu mukaan. Oheismoduulien koodissa ne ovat, ja löydät tällaisen merkinnän valmiina monesta kurssin tiedostosta.

Tiedoksi: kieliversioista

Tässä luvussa olemme kirjoittaneet koodia, joka näyttää tältä:

def verot(tulot: Double, tuloraja: Double, perusprosentti: Double, lisaprosentti: Double) =
  val perusosa = min(tuloraja, tulot)
  val lisaosa = max(tulot - tuloraja, 0)
  perusosa * perusprosentti + lisaosa * lisaprosentti

Vastaavasti kirjoitamme jatkossakin. Kuitenkin jos poukkoilet internetin Scala-sivuilla, törmäät myös koodiin, joka näyttää tältä:

def verot(tulot: Double, tuloraja: Double, perusprosentti: Double, lisaprosentti: Double) = {
  val perusosa = min(tuloraja, tulot)
  val lisaosa = max(tulot - tuloraja, 0)
  perusosa * perusprosentti + lisaosa * lisaprosentti
}

Erona on siis tuossa nuo aaltosulkeet. Muitakin eroja voi tulla vastaan.

Kyse on kieliversioista. Käytämme ajantasaista Scalan kolmosversiota, joka julkaistiin vuonna 2021. Moni muu lähde ei vielä käytä. Vanhoissa Scala-versiossa aaltosulkeita oli käytettävä runsaasti, ja koodi näytti muutenkin hieman toisenlaiselta. Asiasta kertoo vähän lisää tyylioppaamme, johon kannattaa tutustua kurssin alkupuolella muttei välttämättä heti. Esimerkiksi kolmoskierroksen paikkeilla käy hyvin.

Yhteenvetoa

  • Funktioiden määrittelyyn sisältyvät funktion nimi, parametrimuuttujat tietotyyppeineen sekä funktion runko eli sellaisen algoritmin toteutus, joka hoitaa funktiolle määrätyn tehtävän.

  • Kun ohjelmaa suoritetaan, tietokone varaa funktiokutsun alkaessa muistista ns. kehyksen.

    • Funktion parametriarvot tallennetaan muuttujiin, jotka sijaitsevat kehyksessä.

    • Kehykseen voi myös määritellä muita muuttujia funktion ohjelmakoodissa. Tällaisia muuttujia sanotaan paikallisiksi.

    • Kehyksistä muodostuu kutsupino (josta lisää seuraavassa luvussa).

  • Lukuun liittyviä termejä sanastosivulla: funktio, funktiokutsu, funktion runko, loppumerkki; kehys, kutsupino; paikallinen muuttuja, parametrimuuttuja, kuormittaa.

Seuraavassa kaaviossa on mukana eräitä tärkeimpiä funktioiden sisäiseen toimintaan liittyviä käsitteitä.

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, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, 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.

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

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