Luku 6.2: Nimettömiä funktioita

../_images/person09.png

Johdanto: literaaleista yleisesti

Tässä luvussa et opi kirjoittamaan kokonaan uudenlaisia ohjelmia vaan uudenlaisia merkintätapoja funktioille. Opitut konstit sujuvoittavat ohjelmointia.

Aloitetaan kuitenkin lukuarvoista.

Jo kurssin ensimmäisellä viikolla opittiin käyttämään muuttujia lukuarvojen tallentamiseen. Muuttujan nimellä voi viitata tiettyyn lukuun, mikä on kätevää, kun samaan arvoon halutaan viitata useasta eri kohdasta ohjelmakoodissa.

Toisaalta esimerkiksi lukuarvoihin ei aina viitata muuttujien nimillä. Jos haluamme korottaa var-muuttujan indeksi arvoa yhdellä silmukassa, niin emme yleensä kirjoita tuota korotusta näin:

val korotus = 1
indeksi += korotus

Kirjoitamme tuon sijaan vain indeksi += 1 käyttäen kokonaislukuliteraalia 1. Literaalihan on ohjelmakoodiin kirjoitettu lauseke, joka ilmaisee tietyn arvon "suoraan" tai "kirjaimellisesti". Esimerkkikäskyssämme käytämme literaalia, koska

  • ihmislukija pystyy mieltämään "yhdellä kasvattamisen" yhdeksi selkeäksi kokonaisuudeksi,

  • literaalin 1 merkityksen käskyssä indeksi += 1 näkee tuosta käskystä vaivattomasti, joten korotus-muuttujan nimi ei selkiytä ohjelmaa,

  • korotus-muuttujaa oletettavasti tarvittaisiin vain yhdessä käskyssä; arvo 1 on tässä "kertakäyttöinen",

  • kun korotus-muuttujaa ei ole, niin koodia lukevan ihmisen ei tarvitse suoda ajatustakaan sille, onko muuttujalla jokin muukin merkitys koodissa kuin sen käyttö rivillä indeksi += korotus, ja

  • literaalia käyttävä ohjelmakoodi on siis lyhyempi olematta vaikeaselkoisempi (pikemminkin päinvastoin).

Siirrytään luvuista funktioihin.

Jo kurssin alussa olet oppinut määrittelemään funktioita, joilla on nimi. Funktion nimellä, joka määritellään def-sanan yhteydessä, voit viitata tiettyyn toimenpiteeseen. (Vertaa: muuttujan nimellä voi viitata tiettyyn lukuun.) Tämä on erityisen kätevää, kun sama toimenpide halutaan määrätä suoritettavaksi useassa eri ohjelmakoodin kohdassa.

Tässä luvussa näet, että voimme myös määritellä nimettömiä, "kertakäyttöisiä" funktioita.

Siihen tarvitsemme funktioliteraaleja.

Funktioliteraalit

Luvussa 6.1 oli esillä seuraava funktio, joka kutsuu parametriksi annettua funktiota kahdesti:

def kahdesti(toiminto: Int => Int, kohde: Int) = toiminto(toiminto(kohde))

Tätä funktiota käytettiin luvun kasvattamiseen kahdesti ja luvun tuplaamiseen kahdesti. Niinpä määriteltiin tällaiset pikkufunktiot:

def seuraava(luku: Int) = luku + 1

def tuplaa(tuplattava: Int) = 2 * tuplattava

Käytimme funktioita luvussa 6.1 näin:

kahdesti(seuraava, 1000)res0: Int = 1002
kahdesti(tuplaa, 1000)res1: Int = 4000

Saman voi tehdä helpomminkin. Olkoon kahdesti-funktio määritelty kuten edellä. Sen sijaan seuraava- ja tuplaa-funktioita emme tarvitse, kun määrittelemme parametriksi välitettävät funktiot "lennosta". Aloitetaan kasvattamalla kahdesti yhdellä:

kahdesti(luku => luku + 1, 1000)res2: Int = 1002

kahdesti-funktion ensimmäinen parametri on määritelty funktioliteraalilla (function literal). Funktioliteraali kuvaa nimettömän funktion (anonymous function). Tämän literaalin voi lukea: "eräs nimetön funktio, joka ottaa luku-nimisen parametrin ja palauttaa arvon luku + 1". kahdesti-metodille välitetään parametriksi viittaus tällaiseen nimettömään funktioon.

Funktioliteraalin merkkinä on oikealle osoittava nuoli. Sen vasemmalla puolella mainitaan parametrit (joita on tässä vain yksi) ja oikealla puolella on funktion ohjelmakoodi (tässä vain yksi summalauseke).

Tässä taas kahdesti tuplaaminen nimetöntä funktiota käyttäen:

kahdesti(n => 2 * n, 1000)res3: Int = 4000

Funktioliteraali määrittelee nimettömän funktion, joka palauttaa kaksi kertaa parametriarvoaan suuremman kokonaisluvun.

Silloin kun se ei aiheuta suurempaa epäselvyyttä, funktioliteraalien parametrimuuttujat nimetään usein lyhyesti, jotta literaali ei kasva kovin isokokoiseksi. Tässä voisi nimen n sijaan toki olla myös nimenä tuplattava tms.

Funktioliteraalimerkinnöistä

Nuolesta: Funktioliteraalin merkitsemisessä käytetään samanlaista nuolta, jollaista käytetään funktioarvojen tyyppien kuvaamiseen (esim. kahdesti-funktiossahan on parametrin tyypiksi merkitty kaksoispisteen perään Int => Int).

Parametrien tyypeistä: Funktioliteraalien parametrien tyyppejä ei usein tarvitse kirjoittaa, koska ne ovat pääteltävissä käyttöyhteydestä. Esimerkiksi yllä ei ollut pakko erikseen määritellä, mikä on parametrin luku tietotyyppi literaalissa luku => luku + 1. On automaattisesti pääteltävissä, että kyseessä on Int-arvo, koska tuo literaali välitetään parametriksi kahdesti-funktiolle, joka edellyttää, että sille annetaan nimenomaan Int => Int-tyyppinen funktioparametri.

On siis yleistä, että funktioliteraalien parametrimuuttujien tyypit ovat automaattisesti pääteltävissä. Silloin, kun ne eivät ole, ne voi merkitä aivan samalla tavalla kuin nimettyihinkin funktioihin. Tällöin on käytettävä sulkeita ja kaksoispistettä. Esimerkiksi literaalin luku => luku + 1 voi kirjoittaa "auki" näin: (luku: Int) => luku + 1.

Sulkeista: Sulkeet voi jättää pois funktioliteraalissa mainitun parametrin ympäriltä silloin, kun funktio ottaa yhden parametrin eikä sen tyyppiä ole erikseen mainittu. Näinhän teimme esimerkeissämme ylempänä. Kun parametreja on monta, ovat sulkeet pakolliset (mistä esimerkkejä alla). Sulkeet saa kyllä aina kirjoittaa.

Nimetön funktio arvona

Tutkitaan vielä REPLissä sitä, miten funktioliteraalit määrittelevät nimettömiä funktio-olioita.

Tässä ensin yhdelläkasvatusliteraali:

(luku: Int) => luku + 1res4: Int => Int = <function1>

REPL vahvistaa, että funktioliteraalin arvo on tyyppiä Int => Int.

Parametrityypin Int määritteleminen erikseen on tässä REPL-esimerkissä pakollista, koska tästä asiayhteydestä ei ole muuten selvää, mikä tyyppi on kyseessä. (Tyyppi voisi muuten olla myös esimerkiksi Double tai String.)

Seuraavan literaalin arvo puolestaan on kaksiparametrinen funktio, joka palauttaa parametriensa pyöristetyn osamäärän. Se on tyyppiä (Double, Double) => Int:

(x: Double, y: Double) => (x / y).round.toIntres5: (Double, Double) => Int = <function2>

Viittaukset nimettömään funktioon

Funktioliteraalin määrittelemä funktio on olio. Sitä voi käsitellä kuten muitakin olioita.

Funktio itse on nimetön, mutta muuttujaan voi sijoittaa viittauksen funktio-olioon:

val kokeilufunktio = (x: Double, y: Double) => (x / y).round.toIntkokeilufunktio: (Double, Double) => Int = <function2>

Tällöin funktiota voi kutsua muuttujan nimeä käyttäen:

kokeilufunktio(10, 4)res6: Int = 3

Voidaan myös määritellä toinen muuttuja, joka viittaa samaan funktioon:

val pyoristaOsamaara = kokeilufunktiopyoristaOsamaara: (Double, Double) => Int = <function2>

Tällöin funktiota voi kutsua myös tämän toisen muuttujan nimellä:

pyoristaOsamaara(100, 8)res7: Int = 13

Funktiot olioina

Luvussa 5.3 näit, että Scala-olioille voi määritellä apply-nimisen metodin, joka toimii eräänlaisena "olion oletusmetodina". Esimerkiksi kutsu jokuOlio(parametrit) on lyhennysmerkintä kutsulle jokuOlio.apply(parametrit). Oliota, jolla on apply-metodi, voi näin käyttää "vähän kuin se olisi funktio".

Kaikilla Scalan funktio-olioilla on apply-metodi, ja seuraava rivi tekee saman kuin edellinenkin esimerkki:

pyoristaOsamaara.apply(100, 8)res8: Int = 13

Olionäkökulmasta ajateltuna funktion kutsuminen on siis funktio-olion apply-metodin kutsumista.

Miksi funktioliteraaleja ja nimettömiä funktioita?

Funktioliteraalien hyvät ja huonot puolet ovat samansuuntaisia kuin vaikkapa kokonaislukuliteraalienkin. Eräitä hyviä puolia ovat nämä:

  • Ei tarvitse määritellä nimiä jokaiselle eri pikkufunktiolle, jota käytetään vain kerran.

  • Nimettömän funktion määrittelyn voi kirjoittaa juuri siihen kohtaan koodissa, jossa funktiolla tehdään jotain (esim. kohtaan, jossa se välitetään kahdesti-funktiolle parametriksi). Tämä voi helpottaa koodin lukemista varsinkin silloin, kun nimetön funktio on lyhyt.

  • Nimettömiä funktioita käyttämällä koodia voi saada lyhemmäksi. Lyhyys ei ole itsetarkoitus mutta mukavaa silloin, kun se ei hankaloita lukemista.

Heikkouksia ja rajoituksia:

  • Nimettömillä funktioilla ei ole nimiä, mikä voi tilanteesta riippuen tehdä ohjelmasta hankalaselkoisemman.

  • Tottumattomalle funktioliteraalien kirjoittaminen ja lukeminen voivat tuottaa alkuvaikeuksia (mutta merkintätapoihin kyllä tottuu).

  • Monille funktioille tarvitaan nimet (esim. olioiden julkisille metodeille), joten nimettömät funktiot eivät kelpaa kaikkeen.

  • Jos haluamme kutsua samaa nimetöntä funktiota useasti, niin on määriteltävä muuttuja (siis nimi), jonka kautta se onnistuu.

Nimettömiä funktioita täytyy käyttää harkiten. Yksi oivallinen käyttö on pienten kertakäyttöfunktioiden välittäminen parametreiksi toiselle funktiolle kuten yllä.

Nimettömien funktioiden käyttö on erittäin yleistä, ja siihen kannattaa totuttautua. Myös tällä kurssilla niitä käytetään jatkossa paljon.

Termi: lambda

Funktioliteraaleja kutsutaan usein lambdalausekkeiksi (lambda expression) ja nimettömiä funktioita lambdafunktioiksi (lambda function). Sanaa "lambda" käytetään useassa ohjelmointikielessä myös funktioliteraalien määrittelemiseen; esimerkiksi Python-kielinen lauseke lambda luku: luku + 1 vastaa Scalan lauseketta luku => luku + 1.

Lambda-sanan käytölle tässä yhteydessä on historialliset syyt. Nämä lausekkeet ovat peräisin matemaatikko Alonzo Churchin kehittämästä lambdakalkyylistä, joka vaikutti tietojenkäsittelytieteen kehitykseen käänteentekevästi.

Tuon matemaattisen mallin merkintätapoihin kreikan aakkonen lambda taas päätyi lähes kirjaimellisesti hatusta vetämällä: Church kirjoitti luku => luku + 1 sijaan ŷ.y+1 eli käytti ns. hattua kirjaimen päällä, mutta tuo ei ilmeisesti 1930-luvun latojalta sujunut, joten tämä nykäisi hattua hieman vasemmalle: ^y.y+1. Siitä se sitten luettiin lambda-kirjaimeksi Λ.

Funktioliteraaleja käytössä

Useat luvun 6.1 esimerkeistä voi kirjoittaa näppärästi uusiksi funktioliteraaleilla. Tarkastellaan muutamaa tapausta.

Esimerkki: tabulate

Käytimme tabulate-funktiota vektorin alustamiseen näin:

Vector.tabulate(10)(tuplaa)res9: Vector[Int] = Vector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)

Sama hoituu kätevästi nimettömällä funktiolla ilman erillistä summafunktiota:

Vector.tabulate(10)(indeksi => 2 * indeksi)

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

Välikommentti -lyönneistä

Välilyönnit funktioliteraalien ympärillä joskus selkiyttävät ohjelmakoodia, kun parametrifunktion määrittelevä koodinpätkä erottuu selvemmin.

Vector.tabulate(10)( indeksi => 2 * indeksi )

Kurssimateriaalissa käytetään välilyöntejä tähän tapaan usein. Voit itsekin tehdä niin oman harkintasi mukaan.

Esimerkkejä: onkoJarjestyksessa ja useita parametreja

Luvussa 6.1 laadittiin tämä korkeamman asteen funktio:

def onkoJarjestyksessa(eka: String, toka: String, kolmas: String, vertaa: (String, String) => Int) =
  vertaa(eka, toka) <= 0 && vertaa(toka, kolmas) <= 0

Ja nämä sen parametriksi sopivat vertailufunktiot:

def vertaaPituuksia(jono1: String, jono2: String) = jono1.length - jono2.length
def vertaaIntArvoja(jono1: String, jono2: String) = jono1.toInt - jono2.toInt
def vertaaMerkkeja(jono1: String, jono2: String) = jono1.compareToIgnoreCase(jono2)

Niitä käytettiin esimerkiksi näin:

onkoJarjestyksessa("Java", "Scala", "Haskell", vertaaPituuksia)
onkoJarjestyksessa("Java", "Scala", "Haskell", vertaaMerkkeja)
onkoJarjestyksessa("200", "123", "1000", vertaaIntArvoja)

Funktioliteraalien avulla viimeksi mainitut rivit voisi kirjoittaa myös näin, ilman että tarvitsemme erillisiä vertaamisfunktioiden määrittelyjä:

onkoJarjestyksessa("Java", "Scala", "Haskell", (j1, j2) => j1.length - j2.length )
onkoJarjestyksessa("Java", "Scala", "Haskell", (j1, j2) => j1.compareToIgnoreCase(j2) )
onkoJarjestyksessa("200", "123", "1000", (j1, j2) => j1.toInt - j2.toInt )

Sulkeet pitää muistaa.

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

Esimerkki: findAll ja metodikutsu funktioparametrissa

Luvussa 6.1 AuctionHouse-luokalle laadittiin findAll-metodi, jolla voi hakea esineitä parametrifunktiona kuvatun kriteerin perusteella.

class AuctionHouse(val name: String):

  private val items = Buffer[EnglishAuction]()

  def findAll(checkCriterion: EnglishAuction => Boolean) =
    val found = Buffer[EnglishAuction]()
    for currentItem <- this.items do
      if checkCriterion(currentItem) then
        found += currentItem
      end if
    end for
    found.toVector

  // ... muita metodeita ...

end AuctionHouse

Sitä käytettiin kokeeksi näin:

def checkIfOpen(candidate: EnglishAuction) = candidate.isOpen

def checkIfHandbag(candidate: EnglishAuction) = candidate.description.toLowerCase.contains("handbag")

@main def findAllTest() =
  val house = AuctionHouse("ReBay")
  house.addItem(EnglishAuction("A glorious handbag", 100, 14))
  house.addItem(EnglishAuction("Collectible Easter Bunny China Thimble", 1, 10))
  println(house.findAll(checkIfOpen))      // finds both auctions
  println(house.findAll(checkIfHandbag))   // finds only the first auction

Miten toteuttaisit findAllTest-ohjelman nimettömillä funktioilla? Oletetaan siis, että nimettyjä funktioita checkIfOpen ja checkIfHandbag ei ole. Miten kirjoittaisit kaksi viimeistä riviä, joilla findAll-metodia kutsutaan, funktioliteraaleja käyttäen?

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

Esimerkki: repeatForEachElement

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

Esimerkki: kuvankäsittelyä funktioliteraaleilla

Lisää vanhaa koodia luvusta 6.1:

def blueGradient(x: Int, y: Int) = Color(0, 0, x.toDouble / (size - 1) * Color.Max)blueGradient(x: Int, y: Int): Color
Pic.generate(size, size, blueGradient)res10: Pic = generated pic

Nuo käskyt voi korvata tällä yhdellä:

Pic.generate(size, size, (x, y) => Color(0, 0, x.toDouble / (size - 1) * Color.Max) )res11: Pic = generated pic

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

Entä tämä kuvangenerointiesimerkki?

def artwork(x: Int, y: Int) =
  if x * x > y * 100  then Red
  else if x + y < 200 then Black
  else if y % 10 < 5  then Blue
  else                     Whiteartwork(x: Int, y: Int): Color
Pic.generate(size, size * 2, artwork)res12: Pic = generated pic

Mitä monimutkaisemmaksi funktioliteraali muodostuu, sitä epätodennäköisempää on, että se selkiyttää ohjelmaa. Esimerkiksi tämä on jo aika mutkikas, kirjoitti sen sitten yhdelle tai monelle riville.

Pic.generate(size, size * 2, (x, y) => if x * x > y * 100 then Red else if x + y < 200 then Black else if y % 10 < 5 then Blue else White )res13: Pic = generated pic

Välisana

Nyt osaat kirjoittaa funktioliteraaleja, joilla määritellään nimettömiä funktioita. Nimettömiä funktioita esiintyy hieman eri muodoissa monissa eri ohjelmointikielissä muttei kaikissa. Niitä käytetään erityisen runsaasti funktionaalisessa ohjelmoinnissa, josta kerrotaan lisää luvussa 11.2.

Tämän luvun loppuosa liittyy lähinnä Scala-kieleen. Opit toisen tavan kirjoittaa funktioliteraaleja Scalalla.

Nimettömiä parametreja

Monessa pienessä funktioliteraalissa ei oikeastaan ole paljonkaan väliä, minkä nimen parametrimuuttujalle valitsee. Voit kirjoittaa minkä tahansa seuraavista, ja lukijalle käy joka tapauksessa ilmi, että kyseessä on parametriarvoaan yhtä suuremman luvun palauttava funktio.

luku => luku + 1
x => x + 1
jokin => jokin + 1

Siksi Scala tarjoaa mahdollisuuden jättää nimen määrittelemättä kokonaan ja samalla kirjoittaa funktioliteraaleja entistä tiivimmässä muodossa.

Tarkastellaan taas literaalia luku => luku + 1, jonka oleellinen sisältö on, että johonkin annettuun arvoon lisätään ykkönen. Voimme määritellä nimettömän funktion keskittyen vain tähän oleellisimpaan sisältöön. Täsmälleen samanlaisen funktion saa määriteltyä näin lyhyellä merkinnällä:

_ + 1

Alaviiva tarkoittaa tuossa nimetöntä parametria. Koska alaviivoja on tässä literaalissa yksi, kyseessä on yksiparametrinen funktio. Parametriarvo lasketaan yhteen ykkösen kanssa, ja lopputulos on nimettömän funktion palauttama arvo.

Tässä lyhyemmässä ilmaisutavassa siis ei tarvita oikealle osoittavaa nuoltakaan lainkaan. Pelkkä alaviiva riittää kertomaan, että kyseessä on funktioliteraali.

Lyhennettyjä funktioliteraaleja voi käyttää vaikkapa näin:

kahdesti( _ + 1 , 1000)       // palauttaa 1002
kahdesti( 2 * _ , 1000)       // palauttaa 4000

Ylempänä lueteltiin nimettömien funktioiden hyviä ja huonoja puolia verrattuna nimettyihin funktioihin. Lyhennetyssä "alaviivanotaatiossa" nuo hyvyydet ja huonoudet korostuvat entisestään. Hyvällä maulla käytettyinä myös lyhennetyt funktioliteraalit voivat parantaa koodin luettavuutta. Aluksi ne voivat näyttää liki maagisilta, mutta niihin tottuu harjoittelun myötä nopeasti.

Parametrien tyypeistä

Kuten lyhentämättömienkin funktioliteraalien tapauksessa myös lyhennetyistä funktioliteraaleista saa usein jättää parametrien tyypit pois, koska ne ovat pääteltävissä käyttökontekstista. Näin on tehty kaikissa yllä olevissa esimerkeissä.

Parametrien tyypit voi myös mainita erikseen. Esimerkiksi äsken mainitun literaalin _ + 1 voisi kirjoittaa myös (_: Int) + 1. Tyyppien merkitseminen nimettömiin parametreihin kuitenkin helposti tekee koodista vaivalloista luettavaa, emmekä käytä tuota tapaa tällä kurssilla.

Useita nimettömiä parametreja

Nimettömällä funktiolla voi olla useitakin nimettömiä parametreja. Tällöin ensimmäinen funktioliteraaliin kuuluva alaviiva viittaa ensimmäiseen funktiolle välitettyyn parametriarvoon, toinen toiseen ja niin edelleen.

Esimerkiksi nämä käskyt ovat täysin toisiaan vastaavat:

Vector.tabulate(3, 5)( (rivi, sarake) => rivi + sarake )
Vector.tabulate(3, 5)( _ + _ )

Samoin nämä yllä nähdyt funktioliteraalit:

onkoJarjestyksessa("Java", "Scala", "Haskell", (j1, j2) => j1.length - j2.length )
onkoJarjestyksessa("Java", "Scala", "Haskell", (j1, j2) => j1.compareToIgnoreCase(j2) )
onkoJarjestyksessa("200", "123", "1000", (j1, j2) => j1.toInt - j2.toInt )

voi kirjoittaa näinkin:

onkoJarjestyksessa("Java", "Scala", "Haskell", _.length - _.length )
onkoJarjestyksessa("Java", "Scala", "Haskell", _.compareToIgnoreCase(_) )
onkoJarjestyksessa("200", "123", "1000", _.toInt - _.toInt )

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

Vielä vähän merkintätapojen vertailua

Ajatellaan tilannetta, jossa meillä on alkiokokoelma, jonka kunkin alkion haluamme tulostaa. Tässä on kolme suomenkielistä käskyä:

  • Pisin: "Toista jokaiselle alkiolle: tulosta x, missä x on nyt käsiteltävä alkio."

  • Keskipitkä: "Toista jokaiselle alkiolle: tulosta kyseinen alkio."

  • Ytimekäs: "Toista jokaiselle alkiolle: tulosta."

Suomeksi viestittäessä valinta tällaisten käskyjen välillä voidaan tehdä tilanteen ja oman maun mukaan.

Scala on ohjelmointikieleksi joustava, ja voimme antaa vastaavan käskyn Scalallakin eri tavoilla. Kun käytössä on luvun 6.1 repeatForEachElement-funktio ja lukuja sisältävä vektori, niin kaikki seuraavista tekevät saman asian:

  • Pisin: repeatForEachElement(vektori, x => println(x) )

  • Keskipitkä: repeatForEachElement(vektori, println(_) )

  • Ytimekäs: repeatForEachElement(vektori, println)

Kaksi jälkimmäistä ovat käytännössä vain lyhempiä tapoja kirjoittaa ensimmäinen, pisin versio.

Tässä toinen esimerkki. Siinä oletetaan, että olio viittaa johonkin sellaiseen olioon, jonka metodi nimeltä toimi ottaa parametrikseen kokonaisluvun:

  • Pisin: repeatForEachElement(vektori, alkio => olio.toimi(alkio) )

  • Keskipitkä: repeatForEachElement(vektori, olio.toimi(_) )

  • Ytimekäs: repeatForEachElement(vektori, olio.toimi)

Kun mikä tahansa ilmaisuista toimii, valinnan voi tehdä koodin luettavuuden perusteella. Riippuu tilanteesta ja lukijasta, mikä ilmaisuista on paras. Ytimekkäimmässä tavassa on vain välttämätön; sen edut lienevät suurimmat, kun parametriksi välitetty funktio on todella tuttu (kuten println on). Siihen verrattuna keskipitkä tapa korostaa koodin lukijalle sitä, että kukin alkioista tulee vuoron perään käytetyksi parametriarvona: se käy suoremmin ilmi ilmaisusta olio.toimi(_) kuin ilmaisusta olio.toimi. Pisin tapa alleviivaa parametrimuuttujaa ja antaa ohjelmoijan nimetä sen.

Kaikkia kolmea ilmaisutapaa esiintyy tämän kurssin materiaalissa ja muissa Scala-ohjelmissa. Onkin hyvä tuntea ne kaikki, vaikka omissa ohjelmissasi suosisitkin yhtä niistä.

On tiettyjä yhteyksiä, joissa lyhyempiä merkintätapoja ei voi käyttää, vaan on käytettevä pisintä, nuolellista ilmaisua. Tästä kerrotaan lisää seuraavassa luvussa 6.3.

Ohjenuora

Voit aina käyttää pitkiä funktioliteraaleja, joissa parametrit on nimetty. Se ei ole koskaan virhe. Käytä lyhyempiä vain, jos tuntuu siltä, että haluaisit kirjoittaa lyhyemmin.

Mitä monimutkaisempi funktiokutsu, sitä selkeämpää on käyttää nimettyjä parametreja. Kuitenkin yksinkertaisissa tilanteissa alaviivan käyttö on usein sekä näppärää että selkeää.

Yhteenvetoa

  • Kuten esimerkiksi merkkijonoja ja lukujakin, myös funktioita voi kuvata literaaleina eli "ohjelmakoodiin kirjaimellisesti kirjoitettuina arvoina". Funktioliteraaleilla määritellään nimettömiä funktioita.

  • Harkiten käytettyinä nimettömät funktiot voivat kätevöittää koodin kirjoittamista ja parantaa ohjelman luettavuutta.

  • Nimettömät funktiot ovat hyödyllisiä varsinkin silloin, kun funktion määrittelyyn on tarvetta viitata vain yhdessä kohdassa kyseistä ohjelmakoodia. Tyypillinen käyttötapaus on "kertakäyttöisen" funktion välittäminen parametriksi korkeamman asteen funktiolle.

  • Scalassa on kaksi tapaa kirjoittaa funktioliteraaleja. Pidemmässä, jossa käytetään nuolta =>, parametrit nimetään. Lyhyemmässä, jossa käytetään alaviivaa _, paitsi itse funktio myös parametrit ovat nimettömiä.

  • Lukuun liittyviä termejä sanastosivulla: funktioliteraali, nimetön funktio, nimetön parametri.

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, Kai Bukharenko, Nikolas Drosdek, Kaisa Ek, Rasmus Fyhrqvist, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Kaappo Raivio, Timi Seppälä, Teemu Sirkiä, Onni Tammi, Joel Toppinen, Anna Valldeoriola Cardó ja Aleksi Vartiainen.

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

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

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.

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

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

Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki; sitä ovat kehittäneet kymmenet Aallon opiskelijat ja muut.

A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.

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

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

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