1. Johdanto

2. Koneet

3. Raaka-ainetta koneeseen

4. Luvut koneiden raaka-aineina

5. Merkit ja merkkijonot koneiden raaka-aineina

6. Hahmontunnistusta ja kanaherkkuja

7. Kanaherkkupusseista listoihin

8. Filter-kone

9. Map-kone 

10. Fold-kone 

Lähteet 

Lukusuosituksia

 

1. Johdanto

ATK:n paluu

Ohjelmointi on katsottu Suomessa niin tärkeäksi asiaksi, että aihe lisättiin Suomen peruskoulujen opinto-ohjelmiin 2010-luvun lopulla. Opettajille on jo hyvän aikaa järjestetty erilaisia koulutuksia aiheesta. Varsinkin matematiikan opettajien keskuudessa on pohdittu, millainen ohjelmointityyli tukee matemaattisen ajattelun kehittymistä.

Funktionaalinen ohjelmointityyli on vastaus moneen 2000-luvun ohjelmointiongelmaan. Se ei tee ohjelmoinnista yhtään sen helpompaa kuin ennenkään, mutta funktionaalisen filosofian avulla on helpompi suunnitella laadukkaampia ohjelmistoja. Näitä ajatuksia jaamme lukijoiden kanssa koiraystäviemme Selman ja Paten heiluttaessa tahtipuikkoa. Unohtamatta urheaa eurasier-urosta Topia, jonka koirakoulutodistusta ruodimme kirjasen viimeisessä luvussa.

Algoritminen ajattelu ja reseptit

Lukuisissa verkkokeskustelussa tai aiheeseen liittyvissä artikkeleissa viljellään sanaparia algoritminen ajattelu kuvaamaan sitä logiikkaa tai vaiheita, joiden lopputuloksena ohjelmakoodin nähdään syntyvän. Vaiheiden tai askelten korostaminen on omiaan johtamaan siihen, että oppija alkaa nähdä ongelmanratkaisun koodiriveinä, joita suoritetaan algoritmin vaiheiden mukaisesti. Hyvin usein algoritmista ajattelua verrataan ruokareseptiin – ruoka-annos syntyy reseptin mukaisesti ja reseptin mukaan toimiminen nähdään ajassa etenevinä erillisinä tapahtumina.

Reseptirinnastukseen liittyy kuitenkin ongelma, joka piilee sen pykälittäisyydessä. Rinnastus korostaa työvaiheita ja tekemistä – otetaan puuro uunista ja ripotellaan kanelia päälle. Tiedon käsittelyyn liittyvien ongelmien helposti tulkittavat kuvaukset eivät kuitenkaan esitä peräkkäin suoritettavia toimintosarjoja vaan pikemminkin kuvaavat ongelman sellaisenaan. Kyse ei nykyaikaisissa tiedonkäsittelytehtävissä ole niinkään tietokoneelle annettavista käskyistä, vaan tiedonkäsittelysuunnitelman kuvaamisesta.

Ylhäältä alas tulkittavista ohjeista yhteistoiminnan kuvaamiseen

Ohjelmoinnin ymmärtämiseksi ja algoritmisen ajattelun oivaltamiseksi ei tarvita muuttujia tai mekaaniseen tiedonkäsittelyjärjestelmään viittaavia käsitteitä. Kun algoritmia suunnitellessa keskustelu kääntyy muistiin ja muuttujiin, olemme jo kulkeneet hyvän matkaa kohti käsitemaailmaa, joka liittyy tietokoneen toimintaan, ei niinkään itse tiedonkäsittelyongelman kuvaamiseen.

Kun opetus- tai oppimistilanteissa tulee tarve käyttää sellaisia ilmaisuja kuin "laittaa numero jemmaan" tai "laittaa näppäimistöltä kirjoitettu sana muistiin" olemme edenneet jo hyvän matkaa kohti käsitteitä, joita ei nykyaikaisessa ohjelmointityössä useinkaan tarvita.

Tämä ei kuitenkaan tarkoita, että reseptivertauskuva olisi erityisen huono malli algoritmisen ajattelun kuvaamiseksi, vaan sitä, että reseptivertauskuva on huono ongelmanratkaisun kuvausmalli, jos sen kuvausvoima halutaan ulottaa myös sellaisiin tiedonkäsittelytilanteisiin, joihin se soveltuu huonosti. Tässä kirjasessa algoritminen ajattelu ymmärretään erityisesti ohjelmoijan työkaluna, ei kaikenkattavana ajattelumallina, jolla ratkaista ongelma kuin ongelma.

Onko ohjelmointi viisasta rinnastaa resepteihin, jos rinnastus kuvaa huonosti vallitsevaa todellisuutta? Yhtäältä reseptivertaus paljastaa hieman liikaa tiedonkäsittelyn konehuoneesta ja toisaalta piilottaa aivan liiaksi sen maailman, jossa moni tiedonkäsittelyn ammattilainen tällä hetkellä toimii. Miten reseptivertaus istuu esimerkiksi siihen tiedonkäsittelyn todellisuuteen, johon kuuluu oleellisena osana jatkuva tietovirtojen käsittelyn ohjaaminen? Vastaus: ei mitenkään.

Lisäksi reseptivertaus korostaa pieniä vaiheita, yksityiskohtia, tekemistä ja toimintaa: uunin laittaminen päälle, maidon laittaminen lämpiämään hellalle, ruoka-aineiden sekoittaminen oikeassa suhteessa keskenään, uunin lämpiämisen odottaminen 200 asteeseen ja niin edelleen. Reseptivertaus korostaa yksittäisiä vaiheita yhden ongelman ratkaisemiseksi ja jättää hyvin vähälle huomiolle sen, että ohjelmien kuvaaminen on nykyään enenevässä määrin yhdessä toimivien tietoa käsittelevien yksiköiden(funktio) yhteistoiminnan kuvaamista.

Oleellista tietojärjestelmien laadun näkökulmasta ei liene nostaa yhtä sen paremmin kuin toistakaan paradigmaa jalustalle, vaan valita parhaiten soveltuva paradigma pragmaattisesti käyttötilanteen mukaan. Jos niin peruskoulussa kuin yliopistoissa ja korkeakouluissakin pääasiassa tarjottavat rinnastukset ohjaavat vahvasti imperatiiviseen paradigmaan funktionaalisen paradigman kustannuksella, edellä mainittua valintaa ei päästä tekemään. Siksi tarvitaan tietoa myös muista vaihtoehdoista.   

Resepteistä tiedonkäsittelykoneisiin

Reseptirinnastuksen rinnalla tai jopa sen tilalla voisimme käyttää tiedon käsittelyn kuvaamiseen tiedonkäsittelykonetta. Jokainen kone saa jotain työstettävää ja jokainen kone myös saa jotain aikaiseksi.

Koneen työstämä raaka-aine on aina tietoa jossain muodossa ja koneen lopputulos on niin ikään tietoa. Ohjelmointi voitaisiin ymmärtää reseptien sijaan tiedonkäsittelykoneiden kuvaamisena. Tiedonkäsittelykoneiden, joiden tuotanto riippuu siitä, millaista on syötettävä raaka-aine eli tieto.

Idea ei ole uusi, sillä funktiokoneen käsitettä on käytetty matematiikan opetuksessa jo pitkään ja se tuntuu sopivalta rinnastukselta myös johdatellessa ajatuksia funktionaaliseen ohjelmointiin.

Kuten television kokkikilpailuista tiedämme kokin tila voi vaikuttaa siihen, millainen soppa syntyy, vaikka raaka-aineet olisivat kaikilla yhtä hyvät. Kokki on voinut olla kilpailua edeltävänä iltana juhlimassa ja kisapäivänä päänsärky on yllättänyt.

Funktionaalisessa ohjelmoinnissa ei ole tilaa. Hieman kärjistäen voidaan sanoa, että koska tilaa ei ole, ei ole päänsärkyäkään.

"Käymme yhdessä ain, käymme aina..."

Ohjelmoinnin rinnastaminen tiedonkäsittelykoneeseen ruuanvalmistusreseptien sijaan toimii myös siksi, että sen avulla on helppo kuvata asioiden tapahtumista rinnakkain. Tehtaaseen rakannettavan uuden tuotantolinjan rinnastaminen rinnakkaiseen suorittamiseen lienee käytännön opetustilanteessa helpompaa kuin selittää neljä ”tilallista” kokkia valmistamassa rinta rinnan samaa ateriaa. Ongelma kumpuaa siitä, että jokainen kokki on erilainen: yhdellä on päänsärky, toisella on käsi kipeä, kolmannella on huimausta ja neljäs on hiukan huppelissa.

Kokkien sijaan tehtaaseen voidaan rakentaa samanlaiset rinnakkain toimivat tehtaan lattiaan pultatut tuotantolinjat koneineen. Jokainen kone on aina ”iskussa” – samoilla raaka-aineilla koneista saadaan aina samat tuotokset. Koneella ei ole huonoja päiviä.  

Rinnakkaisuuden kuvaaminen on erityisen tärkeää tietokonelaitteistojen kehittyessä niin, että ne tekevät yhä enemmän asioita samaan aikaan. Nykyaikaisissa tiedonkäsittelylaitteistoissa on monia rinnakkaisia tuotantolinjoja. Tietokoneita on viisasta ohjelmoida sellaisin välinein, jotka parhaiten soveltuvat kyseisiä ohjelmia suorittaviin laitteisiin.

Rinnakkaisuuden tai samanaikaisuuden korostaminen tämän kirjasen esipuheessa voi alkuun kuulostaa lennokkaalta, mutta sitä se ei suinkaan ole. Rinnakkain suorittavat ja siis useita tuotantolinjoja edustavat tietokoneen osat ovat arkipäivää aina suurista tietokoneista sormenpään kokoisiin laitteisiin.  

Tyylillä on väliä

Ohjelmointityylin valinnalla on siis väliä. Imperatiivinen tyyli on edelleen usein ainoa vaihtoehto, jos meidän pitää kirjoittaa niin sanottuja matalan tason ohjelmia, jotka ohjaavat välittömästi tietokoneen toimintaa. Imperatiivinen, komentava ohjelmointityyli on hyvä valinta myös silloin, jos pitää saada aikaan ohjelmia, joiden suorituskyvyn tulee olla erinomaisen hyvä. Käskyjä ja komentoja korostavan ohjelmointityylin mannekiiniksi ruokaresepti kokkeineen istuu siis mainiosti, sillä sellaisessa ohjelmointityylissä on aina tila mukana.

Jos sen sijaan meidän pitää kirjoittaa ohjelma mahdollisimman lyhyesti ja ilmaisuvoimaisesti niin, että ohjelmakoodin merkitys on mahdollisimman helposti tulkittavissa, valintamme on funktionaalinen ohjelmointityyli. Funktionaalinen ohjelmointityyli on usein nopein, selkein ja helpoin tapa kuvata jonkin tiedonkäsittelyyn liittyvän ongelman ratkaisu.

Esimerkiksi tekoälyyn tai tilastomatematiikkaan liittyvät ongelmat eivät ratkea kovinkaan helposti niin, että ratkaisua synnytetään pohtimalla koodin etenemistä ja yksittäisen koodirivin suoritusta ja miettimällä kuinka monta muuttujaa ongelmaa ratkaistaessa tarvitaan. Tiedon käsittelyyn liittyvät ongelmat ratkeavat usein helpommin kuvaamalla ne funktionaalisella tyylillä.  

Eräs funktionaalisen ohjelmoinnin kiistattomista vahvuuksista on ohjelmakoodin imperatiivista ohjelmointityyliä helpompi tulkittavuus. Asia ilmenee hyvin esimerkiksi korkeamman asteen funktioiden muodossa: map, filter ja fold (reduce) funktiot ovat hyvä esimerkki tästä.

Imperatiivisessa ohjelmointikielessä manipuloidaan joukkoja tyypillisesti silmukan avulla. Tässä kohtaa näemme koodissa tyypillisesti for-silmukan tai vastaavan toistorakenteen. Koodin luettavuusongelmaksi muodostuu, että näemme saman näköisen for-silmukan oli kyse sitten tiedon suodattamisesta (filter) tai jokaista alkiota koskevasta muunnoksesta(map) tai jostain muusta, kuten fold(reduce) operaatiosta. For-rakenne ei sinällään anna koodin lukijalle tietoa tiedonkäsittelyoperaation luonteesta. Alla oleva kuva selventää asiaa.

Koodin selkeydellä ja luettavuudella on suora yhteys tietojärjestelmän kehitys- ja ylläpitokustannuksiin. Jos koodia on vaikea tulkita, sitä on myös vaikea muuttaa ja kehittää. Pilvilaskennan yleistyessä palvelittomat ratkaisut (esimerkiksi AWS lambda) ovat erinomainen sovellusalue funktionaaliselle ohjelmointityylille: pysyvyysratkaisu, esimerkiksi relaatiotietokanta, ja funktiot ovat erillään eikä muuttujille sinänsä ole tarvetta. Tilanhallinta on jätetty taustajärjestelmän huoleksi ja ohjelmistosuunnittelija voi keskittyä olennaiseen – laatimaan helposti tulkittavia ja ylläpidettäviä sovelluksia.      

Tämän tekstin tarkoituksena ei ole väheksyä imperatiivista ohjelmointityyliä sen enempää kuin korostaa funktionaalisen ohjelmointityylin erityisluonnettakaan. Kirjasen kirjoittaja ymmärtää myös funktionaalisen ohjelmointityylin ongelmat, jos ohjelmointityyliä sovelletaan sellaiseen käyttötilanteeseen, johon se ei sovi. Tekstin tarkoituksena on herättää lukija ajattelemaan ohjelmointia tiedon käsittelyn näkökulmasta ja pohtimaan millainen ohjelmointityyli omiin tarpeisiin ja projekteihin sopisi parhaiten.  

Matematiikan oppimisesta on paitsi iloa myös hyötyä

Funktionaalinen ohjelmointityyli tukee eri ohjelmointityyleistä parhaiten matemaattisen ajattelun kehittymistä ja on paras tapa oppia sekä ohjelmointia että matematiikkaa samalla kertaa. Tämä johtuu siitä, että funktionaalinen ohjelmointityyli on yleisesti tunnetuista ohjelmointityyleistä luonteeltaan lähinnä matematiikkaa – kaiken perustana on funktion käsite.

Funktionaalisia ohjelmointikieliä on kuitenkin useita ja matemaattisen ajattelun kehittämisen kannalta olisi hyvä valita yhtäältä parhaiten matemaattista ajattelua tukeva ja toisaalta mahdollisimman yksinkertainen kieli. Kieli, joka tukee erinomaisesti sekä ohjelmoinnin että matematiikan taitojen kehittymistä on Haskell. Selma-koira ja minä voimme vakuuttaa, että Haskell-kielen ainoa sivuvaikutus on, että sitä opiskellessa oppii matematiikkaa ihan huomaamatta.

Haskell-kieli kouluttaa käyttäjänsä esittämään ongelman ratkaisun selkeästi ja yksinkertaisesti. Kieli saa hyvän selkänojan matematiikasta. Haskell-ohjelmassa asiat esitetään lambda-laskennan periaatteiden mukaan laiskasti evaluoitavina lausekkeina.

Teksti johdattelee sinut Selma-koiran ja hänen ystäviensä myötä Haskell-kieleen ja funktionaaliseen tapaan ajatella niin algoritmeista kuin ohjelmoinnista yleensä. Osoitamme Selman ja hänen koiraystäviensä kanssa, että funktionaalinen ohjelmointi Haskell-kielellä on paitsi hauskaa myös ajatteluun haastavaa. Kirjanen voi toimia pontimena myös funktionaalisen ohjelmointityylin opiskeluun yleensä, sillä lähes kaikki muut funktionaaliset kielet tai muut kielet, joissa on funktionaalisia ominaisuuksia, ovat saaneet ideansa funktionaalisesta ohjelmointikielestä, kuten Haskell-kielestä.

Tervetuloa koiramaiseen porukkaamme pohtimaan funktionaalista ohjelmointia!

Luopioisissa 19.6.2019
Juuso Vuorinen

Koska niin koirat kuin ihmisetkin tekevät viljalti virheitä ja toisinaan niistä oppivatkin, olisi mukavaa, jos laittaisit teosta koskevat kommentit, kehitysehdotukset ja muut ideat sähköpostitse osoitteeseen:Tämä sähköpostiosoite on suojattu spamboteilta. Tarvitset JavaScript-tuen nähdäksesi sen..

2. Koneet

Tiedon käsittelyn kuvaamista ei voi automatisoida

Tiedon käsittelyn taito lienee eräs tärkeimmistä ohjelmoijan perustaidoista. Tietoa käsitellessä tarvitsemme työkaluja - ne käsitteet, joiden varassa tiedonkäsittelykoneemme toiminta kuvataan.

Tiedon käsittelyn vaiheiden kuvaaminen ei siis tapahdu itsestään. Se on jotain, mitä emme voi automatisoida, koska se on automatisaation raaka-ainetta. Tiedon käsittelemiseksi meidän tulee määritellä, miten annetusta tiedosta luodaan uutta tietoa. Tätä käsittelyketjua voidaan kuvata tehtaassa toimivilla koneilla, jotka saavat jotain mielekästä aikaan.

Funktio tiedonkäsittelykoneena

Koneesta voimme käyttää myös nimitystä funktio. Funktiolle on ominaista, että jos haluamme saada arvon (koneen valmistama tai tuottama asia) meidän tulee antaa argumentti (raaka-aine).

Funktion soveltuvuus tiedonkäsittelykoneistomme peruskäsitteeksi on hyvä juuri siksi, että sen avulla tiedon käsittelyyn liittyvät kuvaustarpeemme tulevat erittäin hyvin tyydytetyiksi suhteessa nykyaikaisen tietojenkäsittelyn vaatimuksiin. Abstraktio on sopivasti irti tietokoneesta fyysisenä laitteena mutta riittävän käytännöllinen tarjoamaan yksiselitteisen ja helposti tulkittavan tavan kuvaamaan tiedon käsittelyä.

Jos vertaamme funktionaalista ja imperatiivista lähestymistapaa, huomaamme, että funktionaalinen ohjelmointityyli sopii koneineen imperatiivista tyyliä paremmin juuri tiedonkäsittelyongelmien kuvaamiseen ja ratkaisemiseen. Ero on hiukan sama kuin numeroilla ja tukkimiehen kirjanpidolla.

Tukkimiehen kirjanpidolla kirjaa pitävä pärjää ilman numeroita. Jos tukkimies vie kirjanpitonsa arabialaisia numeroita ymmärtävälle kirjanpitäjälle, voi kirjanpidon laatiminen osoittautua vaikeaksi.

Kun kirjanpitäjä yrittää tulkita tukkimiehen kirjanpitotositteissa näkyviä lukumääriä, hän joutuu ensin laskemaan ”viiden niput” yhteen. Sen jälkeen kirjanpitäjän on vielä lisättävä edelliseen lukuun jäljelle jäävien viivojen summa. Voi olla, että kirjanpitäjä joutuu tekemään useita laskutoimituksia ja laittamaan lukuja muistiin päästäkseen lopulta oikeisiin tuloksiin.

Laskiessaan ”viiden nippuja” kirjanpitäjälle voi tulla helposti virhe, sillä joku ”nipuista” voi jäädä laskematta tai kirjanpitäjä laskee vahingossa yhden ”viiden nipun” liikaa. Toisiaan lähellä olevien viivojen yhteen laskeminen on sekin vaivalloista ja virhealtista.

Voinemme hieman kärjistäen sanoa, että funktionaalista ohjelmointityyliä suosiva käyttää arabialaisia numeroita siinä, missä imperatiivisesti ohjelmoiva turvautuu tukkimiehen kirjanpitoon.

Ensimmäinen koneemme

”Katsotaanpa Pate sort-koneen kuvaa ja ohjelmakoodia”, sanoo Selma tomerasti. ”Ohjelmakoodin avulla voin järjestää leluni aakkosjärjestykseen:

import Data.List.sort
selmanLelut = ["Änkkäri","Serla-Orava","Lussu Änkkäri"]
lelutAakkosjärjestyksessä = sort selmanLelut

”Mitä kuvat oikein tarkoittavat?”, kysyy Pate.

”Odotahan, niin kerron”, jatkaa Selma. ”Kahteen suuntaan osoittavan nuolen vasemmalla puolella annostelemme raaka-aineet koneeseen sellaisinaan ja nuolen oikealle puolella olevassa kuvassa raaka-aineet annostellaan viittaamalla raaka-aineita tarkoittavaan nimeen.”

”No mitä rivit tarkoittavat?”, jatkaa Pate taas uteliaasti.

”Kuulehan, niin kerron”, sanoo Selma. ”Ensimmäinen rivi kertoo, että haluamme käyttää valmista konetta(funktiota) sort, joka osaa järjestää sanalistan aakkosjärjestykseen. Jos saatavilla on jo olemassa olevia koneita, joilla saadaan aikaan haluttu lopputulos, niin sellaisia koneita kannattaa jokaisen koiran käyttää sen sijaan, että rakentaisimme koneet alusta asti itse. Sort on esimerkki tällaisesta valmiista koneesta. Jotta saamme koneen käyttöömme, meidän pitää import-sanan avulla kertoa, että haluamme ottaa käyttöön listojen käsittelyyn erikoistuneen sort-nimisen koneen.    

Voimme ajatella toisen ja kolmannen rivin asiat kahtena eri koneena. Toisella rivillä yhtäsuuruusmerkin oikealle puolella oleva kone(funktio) tuottaa listan sille annetuista alkioista siinä järjestyksessä, jossa ne ohjelmakoodissa hakasulkujen välissä luetellaan. Kolmannella rivillä yhtäsuuruusmerkin oikealle puolella oleva kone(funktio) tuottaa järjestyksessä olevan listan niistä alkioista, jotka ensimmäinen kone tuotti.

Toisella rivillä kuvataan ohjelmakoodin avulla lista, joka sisältää kolme alkiota: suosikkileluni. Kuten ohjelmakoodista näkyy, listan alkiot erotetaan toisistaan pilkuilla ja listan molemmissa päissä on hakasulkeet. Lisäksi jokaisen lelua kuvaavan sanan ympärillä on lainausmerkit. Miltä tämä kaikki Pate näyttää?”, kysyy Selma.

”Nyt en kyllä yhtään tajua”, sanoo Pate Selmalle. ”Miksi toisen ja kolmannen rivin koneet ovat yhtäsuuruusmerkin oikealla puolella niin eri näköisiä, vaikka molemmat ovat koneita? Kolmannen rivin koodin ymmärrän kyllä hyvin. Siinähän pyydetään selvästi annostelemaan sanalista sort-nimiselle koneelle ja sitten vain odotamme koneen käynnistämistä. Mutta missä on se kone(funktio) toisella rivillä, koska siinähän on vain yksi pötkö hakasulkeiden välissä: ["Änkkäri","Serla-Orava","Lussu Änkkäri"]?”, ihmettelee Pate. ”Missä on se kone, jonkin nimi voisi olla vaikkapa luoLista?”, jatkaa Pate Ihmetellen.  

”Kuulehan Pate, niin kerron”, sanoo Selma. ”Sanoimme, että nimi selmanLelut edustaa listaa, joka sisältää lelut alkuperäisessä järjestyksessä. Emme kuitenkaan näe ensimmäisen ohjelmarivin yhtäsuuruusmerkin oikealle puolella erillistä nimettyä funktiota, kuten luoLista. Mistä tässä sitten on kyse? Olenko minä Pate huijannut sinua? En ole. Hakasulkeet edustavat tässä tapauksessa konetta, joka luo listan luetelluista leluista.

Yhtäsuuruusmerkin oikealla puolella on siis aina kone tai koneen nimi. Yhtäsuuruusmerkin vasemmalla puolella on aina koneen nimi.

Käsin kirjoitettu leluluettelo on siis listan luovan funktion raaka-aine. Näin ollen yhtäsuuruusmerkin oikealla puolella oleva on kuin onkin funktio ja vasemmalla puolella nimi, joka edustaa funktiota eli funktion nimi.

Yhden kysymyksen Pate vielä haluaisin sinulle esittää. Mitä tapahtuisi, jos kirjoittaisit koodin x = x ja sitten käynnistäisit koneen kirjoittamalla komentoriville x ja painaisit returnia?”

”Nytpä kysyitkin visaisen kysymyksen Selma. Odotahan niin mietin. Jos komentoriville kirjoitetaan nyt x, niin se varmasti käynnistää koneen, jonka nimi on x. Se kone on sellainen, että se käynnistää saman nimisen koneen uudelleen, mikä johtaa taas koneen käynnistämiseen uudelleen, ikuisesti. Voisiko siinä käydä niin, että kone jää ikuisesti "tuottamaan" vain ja ainoastaan sen, että konetta käynnistetään ikuisesti maailman loppuun asti?”

”Hyvä Pate!”, tokaisee Selma. ”Niin siinä tosiaankin käy. Kone ei varsinaisesti tuota mitään järkevää, mutta se jää kuitenkin ikuisesti käyntiin. Kokeillaanpa:

> x = x

> x

Voit nyt pysäyttää jumiin jääneen ohjelman suorituksen, jotta pääset jatkamaan ohjelmointiharjoituksia. Asia voi tuntua oudolta, mutta palaamme siihen myöhemmin. Sitä ennen sinä Pate ansaitset kyllä kanaherkun, kun olet niin urheasti jaksanut selvittää asioita.”

Tuotantoketju ja koneiden keskinäiset riippuvuudet

”Kuulehan Pate. Funktio on siinä mielessä fiksu kone, että sen (tiedon)tuotantokoneisto käynnistyy vain, jos jokin muu kone(funktio) tilaa siltä raaka-ainetta. Toisen koneen raaka-ainetarve käynnistää ensimmäisen koneen tuotannon. Ensimmäinen kone (funktio) ei tuota mitään varastoon, vaan sen käynnistyminen on täysin riippuvainen siitä, tarvitaanko sen koneita (funktioita) osana jotain toista tuotantoprosessia (muita funktioita).

Voimme ajatella, että selmanLelut on koneen nimi. selmanLelut niminen kone tuottaa listan, joka sisältää kolme Selma-koiran parasta lelua. Alla olevasta ohjelmakoodista näemme, kuinka koneen nimi selmanLelut on nyt korvattu itse koneella hakasulkeineen päivineen. sort-koneen nimen jälkeen on siis alkuperäinen lelulista kirjoitettuna sellaisenaan. On oleellista ymmärtää, että koneen nimen voi aina korvata sillä koneella, jota nimi edustaa. Tästä syystä alla oleva kuvaus koneesta antaa saman tuotoksen kuin edellinenkin kuvaus:

1 import Data.List

2 lelutAakkosjärjestyksessä = sort ["Änkkäri","Serla-Orava","Lussu Änkkäri"]

> lelutAakkosjärjestyksessä

=> ["Lussu Änkkäri","Serla-Orava","Änkkäri"]

Yhtäsuuruusmerkin vasemmalla puolella oleva nimi on siis lyhyempi tapa ilmaista yhtäsuuruusmerkin oikealla puolella oleva. Selman mielestä ei ole viisasta toistaa joka paikassa kolme alkiota sisältävää listaa, vaan on hauskempi kirjoittaa sen sijaan nimi selmanLelut siellä, missä tarvitaan ["Änkkäri","Serla-Orava", "Lussu Änkkäri"]. Palatkaamme siis tutkimaan alkuperäistä ohjelmakoodiamme:

1 import Data.List.sort

2 selmanLelut = ["Änkkäri","Serla-Orava","Lussu Änkkäri"]

3 lelutAakkosjärjestyksessä = sort selmanLelut

> lelutAakkosjärjestyksessä

=> ["Lussu Änkkäri","Serla-Orava","Änkkäri"]

”Kirjoittamalla komentokehotteeseen funktion(koneen) nimen lelutAakkosjärjestyksessä ja painamalla etutassulla return-näppäintä tulemme käynnistäneeksi (ainakin) kaksi konetta.”, jatkaa Selma. ”Joko Pate arvaat mitkä kaksi konetta käynnistyvät ja missä järjestyksessä?”, kysyy Selma.

”Tämä on helppoa”, jatkaa Pate. ”Ensin käynnistyy tietenkin se kone, joka luo selmanLelut listan ja kun se lista on luotu, niin lista annostellaan tuolle sort-koneelle, joka sitten luo uuden järjestetyn listan, joka sitten näkyy tuossa ruudullakin. Meidän pitää tietenkin ensin mennä tuotantoketjun alkupäähän ja käynnistää tuotanto ensimmäisestä tuotannon vaiheesta eli luoda alkuperäinen lista. Jotta sort-kone voi käsitellä lelulistaa, pitää lelulista siis ensin luoda. Tämä on ihan helppoa päättelyä tällaiselle lempääläläiselle vehnäterrierille”, jatkaa Pate ja iskee Selmalle silmää.

”Aivan oikein meni!”, vastaa Selma. ”Sinustahan on tulossa oikea koodivelho, etten paremmin sanoisi”, Selma päättää.  

”No voi olla, etten ihan vielä tänään käy velhosta, mutta ehkä jo muutaman kuukauden päästä. Mitä muuten tapahtuu, jos kirjoitan komentokehotteeseen selmanLelut ja painan etutassulla return-näppäintä?”, jatkaa Pate juttua.

”Mitä arvelisit?”, sanoo Selma. ”No ehkä selmanLelut käynnistäisi sen koneen, joka luo tuon alkuperäisen listan. Kokeilleenpa, miten siinä käy”, jatkaa Pate.

> selmanLelut

=> ["Änkkäri","Serla-Orava","Lussu Änkkäri"]

”No tuo olikin äkkiä kokeiltu”, hihkuu Pate. ”Kirjoitin nimen selmanLelut ja painoin return-näppäintä etutassulla. Ohjelmointi on selvästi mun juttu”, jatkaa Pate innoissaan.

"Onko ohjelmoinnissa kyse siis niinkin yksinkertaisesta asiasta kuin koneen piirustusten kuvaamisesta ja piirustusten (koneen malli) yhdistämisestä ja lopulta tuotantoketjun käynnistämisestä?", kysyy vuorostaan Pate äimistyneenä. "Kyllä vain", vastaa Selma. "Ohjelmointi ja varsinkin suuren tietomäärien käsittelyyn liittyvä ohjelmointi on juuri sitä piirustusten eli koneita ja tehtaita kuvaavien mallien yhdistelyä. Jonkin koneen käynnistäminen ei kuitenkaan ole varsinaisesti ohjelmointia, sillä kaikki on jo valmiiksi ohjelmoitu, kun kone käynnistetään. Ei autotehtaankaan suunnittelutyö sisällä vaihetta, jossa auton ostaja käynnistää auton ja ajaa sillä lemmikkieläinliikkeeseen ostamaan vinkuleluja ja kanaherkkuja”, päättää Selma yhteenvetonsa.   

Ei miten, vaan mitä

"Meidän koiramaisiin tiedonkäsittelykoneisiimme liittyy vielä yksi piirre, josta haluan sinulle Pate kertoa. Me emme ole kiinnostuneet käykö koneemme sähkö- vai polttomoottorilla. Meidän koneissamme huomio kiinnittyy aina siihen, millaisia raaka-aineita ne käyttävät ja millaisia tuotoksia niistä syntyy. Tämän kun Pate vielä muistat, niin olet jo pitkällä ajatuksenjuoksussasi matkalla kohti taitoja, joiden avulla nykyaikaisia tiedonkäsittelykoneita luodaan.", päättää Selma.

Ensimmäisen esimerkin kolmannen rivin ohjelmakoodista ilmenee hyvin funktionaalisen ohjelmoinnin perusajatus: Selma-koira ei ole funktionaalisesti ohjelmoidessaan kiinnostunut siitä, mitä tietokoneen muistissa tapahtuu, kun lelulistaa järjestetään. Selmaa kiinnostaa se, että lelut saadaan ylipäänsä järjestykseen. Vaikka tutkisimme sort-koneen Haskell-kielistä lähdekoodia sort-niminen kone ei paljasta koneen käyttäjälle, miten lelut järjestetään tietokoneen näkökulmasta ja mitä muistiosoitteissa tapahtuu, kun järjestyalgoritmiin annostellaan järjestettävä lista – lähdekoodissa ei ole muuttujia. Sort-niminen kone tarvitsee raaka-aineekseen listoja ja se tuottaa järjestettyjä listoja. ”Lista sisään, lista ulos, siitä syntyy lopputulos”, riimittelee Pate.

Kolmannella koodirivillä nähdään sama ilmiö kuin toisellakin. Yhtäsuuruusmerkin vasemmalle puolelle kirjoitetaan sana, jonka avulla lyhennetään yhtäsuuruusmerkin oikealla puolella oleva. Selma voi nyt käyttää ilmaisua lelutAakkosjärjestyksessä lyhentämään yhtäsuuruusmerkin oikealla puolella olevan yhdeksi nimeksi: lelutAakkosjärjestyksessä. Kirjoittamalla lelutAakkosjärjestyksessä komentorivikehotteeseen saadaan näkyville koneen tuotos, aakkosjärjestykseen järjestetty lista:

> lelutAakkosjärjestyksessä

=> ["Lussu Änkkäri","Serla-Orava","Änkkäri"]

Funktion arvon selvittäminen

Aiemmin todettiin, että funktio saa jotain ja siitä tulee ulos jotain. Jotta funktiosta saataisiin jotain ulos, on funktion arvo selvitettävä. Funktion arvo selvitetään(evaluoidaan) vain, kun sitä tarvitaan. Pate voi pyytää tietokonetta selvittämään mikä on selmanLelut lista aakkosjärjestyksessä kirjoittamalla komentokehotteeseen nimen lelutAakkosjärjestyksessä, jonka jälkeen näemme listan aakkosjärjestyksessä. Koneiden käynnistäminen nimeä käyttämällä johtaa siis siihen, että koneet alkavat tuottaa jotain mielekästä eli arvoja.

"Onko tässä arvonselvittelyhommassa siis kyse jonkin koneen käynnistämisestä ensin ja sitten kun se on käynnistetty, niin muutkin koneet saattavat käynnistyä, mutta vain silloin, jos ne jotenkin liittyvät käynnistyneen koneen touhuihin vai miten se nyt oikein liittyy koneisiin?" kysyy Pate hämillään. "Kyllä vain", vastaa Selma. "Kyse on juuri siitä. Konetta ei ole mielekästä käynnistää ennen kuin joku haluaa jossain käyttää koneen tuotoksia olivatpa ne sitten koiran nappuloita, kanaherkkuja tai vaikkapa metelöiviä änkkärileluja. Meille ei kukaan lemmikkiruokavalmistaja valmista kanaherkkuja, ellemme me niitä halua ja mehän haluamme", jatkaa Selma ja lipoo kanaherkkuja kaipaavia huuliaan.

”Ja tästä lähtien voimme Pate kutsua koneitamme laiskoiksi, sillä ne eivät tee mitään, ellei niiden tuotoksia joku jossain tarvitse. Laiskuus on hyvä motto meille koirillekin, ainakin tällaisina kuumina kesäpäivinä”, sanoo Selma laiskasti.   

Kun pyydämme konettamme muuntamaan alkuperäisen lelulistan uudeksi järjestetyksi lelulistaksi, saamme lopputuloksena arvon(koneen tuotos), joka on Selman lelulista aakkosjärjestyksessä. Esimerkkikoodissa yhtäsuuruusmerkin vasemmalla puolelle oleva nimi lelutAakkosjärjestyksessä edustaa lelulistalle tehtyä muunnosta alkuperäisestä lelulistasta järjestettyyn lelulistaan. Kirjoittamalla nimen lelutAakkosjärjestykessä komentokehotteeseen ja painamalla return-näppäintä käynnistämme kaksikoneisen tehtaamme.  

Aina, kun pyydämme tietokonetta selvittämään arvon, jota edustaa nimi lelutAakkosjärjestyksessä, tietokone selvittää ensin selmanLelut nimeen liittyvän arvon, sillä selmanLelut on osa arvonselvittelyketjua(koneita tuottamassa kukin omia tuotoksiaan ja siirtämässä tuotoksia ketjussa seuraavaan koneeseen). Halumme saada lelut aakkosjärjestykseen laukaisee siis tuotantoketjun, joka alkaa siitä, että alkuperäisestä lelulistasta luodaan arvo ja päättyy siihen, että alkuperäisestä listasta muodostettu arvo annostellaan sort-nimiseen koneeseen(funktioon), joka taas muodostaa uuden arvon eli järjestetyn listan.   

Koneiden maailmasta tiedämme, että tuotannon eri vaiheissa tarvittavien koneiden kokoonpano riippuu haluamastamme lopputuloksesta. Funktiot edustavat tehdastuotannon maailmassa tuotantoketjuun kuuluvia koneita. On siis valittava sopivat koneet halutun lopputuotoksen mukaan. Tähän voimme käyttää olemassa olevia koneita tai rakentaa omia.

Sort-funktio tietää, miten se saa listan järjestykseen. Meidän on vain annettava funktiolle tieto siitä alkuperäisestä listasta, josta tulisi muodostaa uusi, aakkosjärjestyksessä oleva lista. Tuolle tiedolle on myös toinen nimi: funktion argumentti. Voidaan ajatella, että itse sort-funktio voi sisältää piilossa olevia funktioita(koneita), joiden yhteistoiminnan avulla järjestämätön tieto saadaan järjestetyksi. Kone tarvitsee raaka-ainetta eli argumentin tai useampia. Kone ei kuitenkaan paljasta sisäistä logiikkaansa koneen käyttäjälle. Se paljastaa vain kaksi seikkaa. Yhtäältä sen, mitä raaka-ainetta se tarvitsee ja toisaalta sen, mitä se tuottaa.

Funktion argumentti on siis tehtaiden ja koneiden maailmassa raaka-ainetta, jota koneeseen annostellaan, jotta koneesta saataisiin haluttu lopputulos. On hyvä huomata, että yhden koneen tuotos on toisen koneen raaka-aine. Sellukoneen raaka-aine on puu ja sellukoneen tuottama selluloosa on puolestaan paperikoneen raaka-aine. Yhdistämällä sellu- ja paperikoneen yhdeksi suuremmaksi tuotantoyksiköksi saamme koneen, jonka raaka-aine on puu ja tuotos paperi.

Lienemme nyt pohtineet riittämiin tiedonkäsittelykoneisiin liittyviä peruskäsitteitä, joiden avulla voimme rakentaa mitä mielikuvituksellisimpia tuotantolaitoksia. Nämä käsitteet ovat: funktio, funktion argumentti, funktion nimi ja funktion arvon selvittäminen eli funktion evaluointi.

Seuraavassa luvussa käsittelemme tarkemmin funktion argumentteja eli koneen raaka-aineita. Päädymme tutkimaan miten useampia funktioita (koneita) yhdistelemällä ja uusia parametreja määrittelemällä saadaan aikaan monimutkaisempia funktioita, joiden avulla voimme ratkaista yhä vaikeampia ongelmia.

 

3. Raaka-ainetta koneeseen

Raakaa-aineita moneen lähtöön

”Kuten varmaan jo huomasit Pate, sort-koneelle(funktiolle) syötettävä raaka-aine näyttää olevan lista. Se on lista siksi, että olemme käyttäneet sen määrittelemiseen hakasulkuja ja pilkkuja erottamaan listan alkiot toistaan. Raaka-aine eli tässä tapauksessa lista -tyyppinen arvo saadaan aikaan funktiolla. Hakasulut edustavat listan muodostavaa funktiota.

Kuten aiemmin totesimme, raaka-ainetta voidaan kutsua myös argumentiksi. Joskus tarvitsemme tuotantoprosessissamme useita eri raaka-aineita. Jos koneemme tarkoitus olisi laskea kaksi lukua yhteen, meidän pitäisi antaa yhteenlaskukoneellemme nämä luvut, samalla periaatteella kuin annoimme sort-koneelle listan leluja.”   

Usean eri raaka-aineen annosteleminen koneelle

”Mutta hei!”, huudahtaa Pate hengästyneenä. ”Tarkoitat siis sitä, että me voisimme antaa niitä eri raaka-aineita siinä listassa? Sittenhän meidän olisi helppo antaa mihin tahansa koneeseen niin paljon erilaisia raaka-aineita kuin ikinä haluamme? Jos haluamme laskea kaksi kokonaislukua yhteen, annamme yhteenlaskukoneelle listan, jossa on kaksi kokonaislukua. Voisiko se toimia niin?”, jatkaa Pate innoissaan.

”Idea on kyllä hyvä ja sen saisi toimimaan, sillä kaksi kokonaislukua ovat keskenään samantyyppisiä asioita”, sanoo Selma. ”On nimittäin niin, että listalla on oltava aina samantyyppisiä asioita. Siellä on aina joko leluja tai koirien syntymävuosia mutta ei koskaan molempia samaan aikaan. Samalla listalla ei siis saa olla sekä sanaa ”Änkkäri” että lukua 2015. Ymmärrätkö nyt miksi koneeseen meneviä erityyppisiä raaka-aineita ei voi syöttää koneeseen yhdessä ja samassa listassa?”, jatkaa Selma.

”Aaah. Ymmärrän”, sanoo Pate. ”Se on siis sama asia kuin että ostoslistallakin on vain niitä asioita, joita kaupasta ostetaan eikä esimerkiksi autojen rekisterinumeroita tai tuttujen koirien puhelinnumeroita? Jos haluamme rakentaa tehtaaseemme koneen, joka saa kaksi ihan erityyppistä raaka-ainetta, meidän on käytettävä kahta argumenttia. Ok nyt mä tajusin”, toteaa Pate.

”Kyllä. Olet ymmärtänyt aivan oikein Pate.”, jatkaa Selma.

”Voimme siis välittää koneillemme erityyppisiä raaka-aineita(argumentit). Argumentit voivat olla yksittäisiä, vaikkapa lukuja tai leluja Selman lelulistalta. Argumentti voi olla myös lista -tyyppinen, kuten olemme nähneet. Lista-tyyppisessä argumentissa voimme välittää useita yksittäisiä arvoja, mutta yksittäiset arvot voidaan välittää myös kahden eri argumentin avulla. Argumentin tyyppi on täysin riippuvainen koneesta(funktio), johon olemme raaka-ainetta(argumentit) annostelemassa.”, valistaa Selma.

”Ahaa…”, tuumii Pate. ”Eli jos haluaisimme laskea vaikkapa kymmenen luvun keskiarvon, olisi ihan viisasta antaa keskiarvonlaskukoneelle lista lukuja sen sijaan, että meillä olisi kymmenen argumenttia, joiden avulla välittäisimme raaka-aineet koneellemme? Näinkö se siis on?”

”Se on just näin”, toteaa Selma. ”Argumentteja pitää pohtia ihan samalla tavalla kuin joudumme pohtimaan sitä, millaisia putkia meidän pitää koneeseemme rakentaa, jotta saamme raaka-aineet syötettyä koneen sisään. Sementintekokoneeseen olisi hyvä tulla yksi putki, jossa kulkee vettä ja toinen putki, jossa kulkee hiekkaa ja kolmas putki, jossa kulkee ainetta, joka sitoo muut aineet kovaksi sementiksi. Emme kuitenkaan voi välittää koneelle vettä ja hiekkaa samassa putkessa, sillä putki voisi mennä tukkoon. Puhumattakaan, että johtaisimme veden ja sementtijauheen samassa putkessa. Kun tuotanto pysähtyisi, voisimme olla varmoja, että putki tukkeutuu ja sementti kovettuu putken sisään.  Erityyppisille aineille pitää olla jokaiselle omat putkensa.”

”Onpa ohjelmointi hauskaa, sehän on kuin suunnittelisi tehtaita ja koneita vaan päivät ja yöt! Minä laittaisin ainakin omaan sementintekokoneeseeni hienot kromatut putket, joita pitkin olisi raaka-aineiden hyvä virrata koneeseen”, innostuu Pate.

Tyypillisiä ohjelmoinnin raaka-aineita

”Millaisia raaka-aineita ohjelmoidessa yleensä sitten tarvitaan?”, kysyy Pate.

 ”Aika usein tarvitaan kokonaislukuja ja tekstiä, kuten Selman lelujen nimiä tai henkilöiden nimiä”, vastaa Selma. ”Joskus voidaan argumenteiksi tarvita myös desimaalikuluja tai vaikkapa murtolukuja. Lisäksi lähes aina tarvitaan totuusarvoja True ja False.

Erilaisista alkioista koostuvia joukkoja kutsutaan tyypeiksi. Tyypit ovat monille ohjelmointikielille jotain hyvin tyypillistä. Laajemmissa ohjelmissa käytetään paljon erilaisia itse määriteltyjä tyyppejä, joilla voidaan kuvata erilaisia asioita, kuten autoja, ihmisiä, ilmapalloja, kirjanpidon vientejä, palkkaa tai mitä tahansa muuta.”

”Miksi emme heti opettelisi miten ilmapallo -tyyppi määritellään? Mikään ei ole hauskempaa kuin ilmapallojen hätyyttäminen. Hauskinta niissä on se sähkö, joka saa saa takkuisemmankin huskyn karvat suoristumaan.”, veistelee Pate.

”Itse määriteltyjen tyyppien käyttäminen on kyllä hauskaa ja mielekästäkin. On kuitenkin niin, että funktionaalisen ohjelmoinnin perusasiat voi oivaltaa kyllä ilman niitäkin. Myönnän, että itse määritellyt tyypit avaavat kokonaan uuden maailman, sillä käytännön ohjelmointitöissä tarvitaan tämän tästä itse määriteltyjä tietotyyppejä. Lupaan palata asiaan myöhemmissä luvuissa tai viimeistään koulutuksissamme.”

”Ok!”, sanoo Pate ja jää miettimään, miten ilmapalloja voisi kuvata omilla tietotyypeillä.


4. Luvut koneiden raaka-aineina

 ”Kokonaislukujen tyyppi voi sisältää äärettömän määrän kokonaislukuja tai rajatumman lukujoukon”, aloittaa Selma. ”Kokonaislukujen tyypille on ominaista, että lukuja voidaan käyttää raaka-aineena erilaisille koneille, kuten yhteenlaskukoneelle. Kirjoitamme ensin koneen(funktion) nimen, joka on yhteenlaskun tapauksessa +. Laitamme vielä koneen nimen sulkeisiin ja lisäämme loppuun yhteenlaskettavat eli raaka-aineet(argumentit) välilyönnillä erotettuina.

>(+) 2 3

=> 5

Rakenne on täysin sama kuin sort-esimerkissämme siltä osin, että rivin alussa on koneen nimi ja raaka-aineet seuraavat:

> sort selmanLelut

Konetta tarkoittava nimi sattuu yhteenlaskukoneen tapauksessa olemaan + eikä kokonainen sana, kuten sort. Koodirivit eroavat myös niin, että + koneeseen syötetään kaksi lukua ja sort-koneeseen yksi lista. Koneisiin siis annostellaan erilaiset raaka-aineet.

Koska sort-koneen nimen jälkeen on selmanLelut -koneen nimi, niin voidaan ajatella myös, että + -koneen jälkeen tuleva luku 2 onkin itse asiassa sellaisen koneen nimi, joka tuottaa arvon 2. Kakkonenkin voidaan Pate siis ajatella koneeksi.”

”Mistä voidaan tietää, että nimi selmanLelut tarkoittaa listaa eikä jotain muuta?”, kysyy Pate mietteliäänä. ”Eihän selmanLelut nimi paljasta mitään rakenteestaan – hakasulkeita tai muita listaan viittaavia piirteitä ei näy. Miten tämä nyt oikein toimii?”

”No johan sinä Pate kysymyksen lykkäsit. Lyhyt vastaus on, että viisas ohjelmointikieli osaa päätellä jo koneiden suunnitteluvaiheessa (koodia kirjoittaessa) ennen koneiden käynnistämistä, että selmanLelut on lista, joka sopii raaka-aineeksi sort-nimiseen koneeseen. Pitkä vastaus olisi niin pitkä, että siihen pitäisi palata kokonaan toisessa kirjassa.”

”Voitaisiinko luvut 2 ja 3 niin ikään korvata joillain nimillä, jotka tarkoittavat joitakin lukuja? Onko siis niin, että mikä tahansa nimi kelpaisi, kunhan se tarkoittaisi jotain lukua?”

”Juuri niin Pate. Voimme määritellä kaksi nimeä ekaluku ja tokaluku ja korvata yhteenlaskukoneessa luvut 2 ja 3 näillä nimillä. Katsohan Pate.”

> ekaluku = (-) 5 2

> tokaluku = (*) 5 4

> (+) ekaluku tokaluku

=> 23

”Ja kuulehan Pate vielä tämä. On myös yhdentekevää, kuinka monen muun eri koneen työvaiheen jälkeen (+) -koneeseen syötetyt raaka-aineet annostellaan. Pääasia on, että edellisistä koneista saadaan oikean tyyppisiä raaka-aineita yhteenlaskukoneeseen eli +-nimiseen koneeseen. Esimerkissä ekaluku syntyy vähennyslaskukoneen lopputuloksena ja tokaluku syntyy kertolaskukoneen tuloksena. Koska sekä kertolaskukoneesta että vähennyslaskukoneesta saadaan lopputuloksena lukuja, sekä ekaluku että tokaluku kelpaisivat yhteenlaskukoneenkin eli jo meille tutun +-koneen raaka-aineiksi. Katsopa tätä, Pate. Eikös olekin mukavan ketjumaista?”

> yhteenlaskukoneenTulos = (+) ekaluku tokaluku

> yhteenlaskukoneenTulos

=> 23

”No todellakin on”, tuumaa Pate. ”Se tässä nyt kiinnostaa, että miten me saataisiin tuo yhteenlaskukoneenTulos toiseen koneeseen jatkokäsittelyyn. Miten ketjua jatkettaisiin niin, että luku yhteenlaskukoneenTulos voitaisiin taas annostella raaka-aineena johonkin sellaiseen koneeseen, joka osaisi sen käsitellä? Miten esimerkiksi yhteenlaskukoneen lopputulos saataisiin ketjutettua koneeseen, joka kertoisi yhteenlaskukoneen lopputuloksen kahdella. Ja jos olisi vielä niin, että haluaisimme käyttää yhteenlaskukoneen lopputulosta useammassa eri paikassa ohjelmakoodia, meidän kannattaisi nimetä tuo yhteenlaskukone. Kerrohan Selma minulle, miten se temppu tehtäisiin, niin lupaan tarjota sinulle jäätelön illalla.”

”Näin se Pate menisi. Katsohan tänne!

> kahdellaKertoja = (*2) yhteenlaskukoneenTulos

> kahdellaKertoja

=> 46

Kerrotaanpa sama ohjelmoinnin käsittein ilman koneita. Nyt Pate tarkkana kuin porkkana!

kahdellaKertoja on nimi, jota käyttämällä voimme kertoa kahdella funktion yhteenlaskukoneenTulos arvon.

yhteenlaskukoneenTulos on nimi, jota käyttämällä voimme laskea yhteen nimiä ekaluku ja tokaluku tarkoittavat arvot.

ekaluku on nimi, joka tarkoittaa arvoa, joka saadaan kun 5:stä vähennetään 2.

tokaluku on nimi, joka tarkoittaa arvoa, joka saadaan kun 5 kerrotaan 4:llä.

”Ja kuulehan Pate vielä tämä. Kuten esimerkistä näet, kun kirjoitat komentokehotteeseen kahdellaKertoja ja painat etutassullasi return-näppäintä, tehtaan koneisto käynnistyy. Ensin selvitetään tietysti arvot ekaluku ja tokaluku, koska niitä tarvitaan seuraavassa vaiheessa. Sen jälkeen selvitetään arvo yhteenlaskukoneenTulos ja viimeisenä selvitetään arvo kahdellaKertoja.”

”Onko ohjelman toiminta siis eräänlaista koneiden tuotosten selvittelyä ja tuotosten siirtämistä aina seuraavaan käsittelyvaiheeseen?”, kysyy Pate uteliaana.

”Se on Pate juuri sitä”, vastaa Selma. ”Mutta käsittelyvaiheesta toiseen siirryttäessä pitää tyyppien kanssa olla erityisen tarkkana. Jos kone odottaa, että se saa tiettyä putkea pitkin vettä, niin kone menee takuulla rikki, jos samassa putkessa johdetaankin koneeseen hiekkaa. Meidän pitää siis miettiä hyvin tarkkaan, mitä mihinkin koneeseen menee ja mitä mistäkin koneesta tulee ulos. Tämä siksi, että loppujen lopuksi meillä on aina koneita, jotka toimivat yhdessä muiden koneiden (funktioiden) kanssa. Muista, että yhden koneen lopputulos on jonkin toisen koneen raaka-ainetta!

Yhteenlaskukoneen nimi + voidaan lisätä myös raaka-aineiden väliin:

> 2+3

=> 5

Tämä helpottaa ohjelmakoodin tulkintaa, sillä yhteenlaskumerkki + on totuttu näkemään argumenttien välissä eikä sulkeissa kahden argumentin edessä.

Kokonaisluvut ovat hyvää raaka-ainetta myös jako- ja kertolaskukoneille. Kokonaisluku sopii raaka-aineeksi myös koneelle, jonka lopputuloksena on koneeseen raaka-aineena syötetty luku, jonka etumerkin kone muuttaa. Plussista tulee miinuksia ja miinuksista plussia.  

Kokonaisluku on sopivaa raaka-ainetta myös koneelle, joka osaa korottaa kokonaisluvun haluttuun potenssiin. Kokonaisluku käy raaka-aineeksi myös koneelle, jonka tuotoksena on neliöjuuri. Kuulostaako tämä järkevältä Pate?”

”Kyllä, kaikki tähän mennessä tuntuu koiranjärkeen käyvältä. Yksi asia kuitenkin on, mitä en ollenkaan tajua. Nyt näyttää siltä, että me voimme käyttää kahdellakertomiskonetta vain ja ainoastaan niin, että raaka-aineena annostellaan aina yksi ja sama yhteenlaskukoneenTulos. Eikö olisi järkevämpää, jos voisimme annostella kahdellaKertoja-koneeseen muutakin kuin tuon iänikuisen yhteenlaskukoneenTulos-koneen tuottaman arvon?”

”Nyt olet todellakin asian ytimessä”, vastaa Selma innostuneesti.

 ”Totta kai voimme ja se käy helposti. Näin se käy.”

> kahdellaKertoja = (*2)

> kahdellaKertoja 5

=> 10

”Siis mitäs tuo nyt sitten oikein tarkoittaa”, ihmettelee Pate. ”Miten tuo oikein toimii.”

”Se toimii ihan niin kuin muutkin koneet tähän asti. Katsohan. Kahdellakertomiskoneemme kahdellaKertoja saa komentoriviltä annosteltuna raaka-aineena toisen kerrottavan 5 ja se tuottaa esimerkin tapauksessa arvon 10.

Funktio on funktio myös ohjelmoidessa

Koulumatematiikasta Pate muistanet yksinkertaisen funktion y = 2x tai toisin ilmaistuna f(x) = 2x. Kun edellä mainittuun funktioon sovelletaan arvoa 5 näyttää se matematiikan kielellä tältä: f(5) = 2 * 5 = 10.

Jos nyt muutamme funktion nimen f nimeksi kahdellaKertoja, matemaattinen esitys on kahdellaKertoja (x) = 2x. Jos vielä lisäämme kertomerkin matemaattiseen esitykseen saamme kahdellaKertoja (x) = 2*x. Jos nyt vertaamme ohjelmakoodia ja funktion määrittelyä huomaamme, että ne muistuttavat toisiaan.

> kahdellaKertoja = (*2)

kahdellaKertoja (x) = 2*x

”Ne todellakin näyttävät aika lailla samoilta, mutta kyllä tuossa Selma eroakin on. Matemaattisessa esityksessä on tuo äksä, mutta ohjelmakoodissamme sitä ei ole. Mistä tämä johtuu?”

”Hyvä kysymys, Pate”, vastaa Selma. ”Se johtuu siitä, että koneemme osaa päätellä asioita. Kun kirjoitamme (*2) se ymmärtää, että tarvitsemme äksän siihen koneeseen raaka-aineeksi toiseksi kerrottavaksi ilman, että asiasta tarvitsee erikseen ohjelmakoodissa mainita. Sen takia sitä ei tarvitse siihen erikseen kirjoittaa. Siitä huolimatta niin halutessamme voimme sen siihen erikseen kirjoittaa. Katsopa, niin vertaillaan taas ohjelmakoodia ja matemaattista esitystä.”

> kahdellaKertoja x = (*2) x

kahdellaKertoja (x) = 2*x

”No näyttävätpä ne tosi samanlaisilta nyt”, hihkuu Pate. ”Nehän ovat melkein kuin kaksi marjaa, mutta pieniä eroja kuitenkin vielä on. Saisiko niitä näyttämään vielä hieman enemmän samannäköisiltä?”

”No kyllä ne saa”, jatkaa Selma. ”Tältä ne lopulta voisivat näyttää”.

> kahdellaKertoja x = 2*x

kahdellaKertoja (x) = 2*x

”Ja jos vielä lisäämme sulut yhtäsuuruusmerkin vasemmalla puolella olevan x:n ympärille saisimme:”

> kahdellaKertoja (x) = 2*x

kahdellaKertoja (x) = 2*x

”Ja nyt sekä tapa merkitä, että merkitys ovat samoja.”, jatkaa Selma. ”Näyttääkö Pate siltä, että tällä ohjelmointikielellä ja matematiikalla olisi jotain yhteistä?”

”No kyllä tosiaankin näyttää!”, huudahtaa Pate innoissaan. ”Tämähän ON matematiikkaa, eikä vain näytä siltä.”

”Just niin, Pate”, jatkaa Selma. ”Niin on, jos siltä näyttää ja nyt tosiaankin näyttää siltä”, jatkaa Selma iskien silmää.

Kahdellakertomiskoneemme on monessa mielessä fiksu. Yhtäältä kahdellakertomiskonetta kuvatessa (ohjelmakoodia kirjoittaessa) ei tarvitse erikseen kirjoittaa äksää tai muutakaan kuvaamaan raaka-ainetarvetta, koska kone itse tietää odottaa jotain raaka-ainetta, koska kahdella kertominen edellyttää toista kerrottavaa. Toisaalta kone osaa myös odottaa tietyn tyyppistä raaka-ainetta, ei mitä tahansa tavaraa - se siis tietää itse millaista raaka-ainetta se tarvitsee, koska se tietää, että kahdella kertominen tarvitsee raaka-aineena luvun.

Kokeile vaikka! Jos yrität antaa sille raaka-aineena luvun sijaan merkin ’a’, tulee ongelmia.”

> kahdellaKertoja 'a'

=> * Couldn't match expected type `Int' with actual type `Char'

”Kuten Pate huomaat, homma ei toimi. Yritimme laittaa hiekkaa vesiputkeen ja juuri sitä me ei saada tehdä. Tietokoneet eivät ymmärrä tällaisia kepposia. Kone haluaa raaka-aineena lukuja ja jos yritämme annostella kahdellakertomiskoneeseen vaikkapa kirjaimen ’a’, se toteaa, että kirjainta ei voi käyttää raaka-aineena sellaisessa koneessa, joka kertoo lukuja kahdella. Fiksu vehnäterrieri ei sotke vesiputkia hiekalla tai hiekkaputkia vedellä. Palaamme tähän asiaan vielä myöhemmin. Lupaan sen Pate!

Koneemme osaa siis itse päätellä monia asioita. Kone ei siis päästä hiekkaa vesiputkeen, vaan estää moisen katastrofin. Sellaisista koneista me koirat aivan erityisesti tykkäämme, sillä kuten tiedät Pate, meille koirille sattuu aika ajoin kaikenlaisia vahinkoja ja kommelluksia. Tämä on meille koirille ihan paras mahdollinen tapa kirjoittaa ohjelmia ja kuvata kaikenlaisia hienoja koneita, sillä koneet saavat aina prikulleen juuri ne raaka-aineet, joita ne osaavat hyödyntää!   

Jos haluamme rakentaa yhteenlaskuKone-nimisen koneen, joka saa raaka-aineena kaksi lukua (argumentit), voisimme tehdä sen näin.

Ensin määrittelemme itse koneen näin 

> yhteenlaskuKone a b = a+b

tai näin

> yhteenlaskuKone' a b = (+) a b

Sitten käynnistämme koneen kirjoittamalla koneen nimen ja antamalla raaka-aineet(argumentit) 5 ja 9:

> yhteenlaskuKone 5 9

=> 13

Näin määrittelimme kaksi argumenttia sisältävän nimen, joka tarkoittaa kahden luvun summaa. Nyt voimme kuitenkin annostella yhteenlaskettavat koneeseen komentoriviä käyttäen luettelemalla ne koneen nimen jälkeen. Voiko tämä Pate enää olla selkeämpää?”

”No ei kyllä voi”, vastaa Pate. ”Tämähän on niin selkää kuin olla ja voi. Nyt yhteenlaskukoneella tai kahdellakertomiskoneella voi laskea millä tahansa luvuilla, eikä vain joillain ennalta sovituilla.”

”Koneemme ovat nyt paljon yleiskäyttöisempiä ja niitä on nyt helppo muidenkin koirien käyttää omissa hauskoissa ohjelmissaan”, jatkaa Pate innoissaan.

”Hyviä kahdellakertomiskoneita ja yhteenlaskukoneita tarvitaan aina”, lisää Pate ja jatkaa: ”Muistan hyvin päivän, jona olin ollut kunnon koira ja totellut koko päivän ja melunnut mahdollisimman vähän. Saman päivän iltana minulle luvattiin hyvästä käytöksestä seuraavan kynsien leikkuun yhteydessä kaksinkertainen kanaherkkuannos. Jos minulla olisi jo silloin ollut kahdellakertomiskone, niin olisin voinut helposti laskea kuinka monta kanaherkkua olisi ollut odotettavissa.

Nyt tilanne on korjaantunut – osaan vaatia vähintään sen, mitä kahdellakertomiskone minulle tietyllä argumentin arvolla tuottaa. Näistä koneistahan on uskomattoman paljon hyötyä ihan käytännön kanaherkkupuuhissakin. Ei huono! Lisää tällaisia hienoja koneita ja ne kromiputket!”, hihkuu Pate.

Lukujen vertaaminen

”Yksi juttu tästä hommasta kuitenkin vielä puuttuu”, tokaisee Pate. ”Miten voisin vertailla viime viikolla ja tällä viikolla syömieni kanaherkkujen lukumääriä? Olisi nimittäin mukava tietää tuliko tällä viikolla syötyä enemmän kanaherkkuja kuin viime viikolla. Meidän vehnäterrierien on hyvä pitää linjoistamme huoli, ettemme joudu dieetille. Kertoisitko Selma, miten voimme ongelman ratkaista.”

”Toki kerron”, jatkaa vuorostaan Selma. ”Lukuja voidaan tietenkin vertailla vertailukoneilla. Lukuja vertailevalle yhtäsuuruudenvertailukoneelle annostellaan kaksi lukua raaka-aineina ja vertailukone tuottaa tosi tai epätosi sen mukaan ovatko raaka-aineet samat vai ei. Vertailukoneita on monenlaisia. Tässä tutustumme niistä vain kahteen.

Vertailukoneen idea on, että se tutkii onko jokin väittämä tosi vai epätosi. Vertailukoneita on erilaisia, mutta varmasti tunnetuin niistä on lukuja vertaileva yhtäsuuruudenvertailukone, joka osaa vertailla ovatko kaksi lukuarvoa samoja.

Sellaisen vertailukoneen nimi on == ja sitä käytetään samalla periaatteella kuin yhteenlaskukonettakin. Yhtäsuuruudenvertailukoneen raaka-aineet ovat samat kuin yhteenlaskukoneessakin eli kaksi lukua, mutta kone tuottaa eri tyyppisiä tuotoksia kuin yhteenlaskukone. Huomaa Pate, että niin yhtäsuuruudenvertailukonetta kuin kaikkia muitakin vertailukoneita voidaan käyttää myös niin, että raaka-aineet annostellaan ennen ja jälkeen koneen nimeä ==.

Yhtäsuuruudenvertailukoneen tuotos on joko arvo epätosi (False) tai arvo tosi (True) sen mukaan, millaiset raaka-aineet vertailukoneeseen milloinkin annostellaan. Katsotaanpa esimerkkiä:

> (==) 1 1

=> True

> (==) 1 2

=> False

> 1==1      -- raaka-aineet koneen nimen ympärillä!

=> True

> 1==2      -- raaka-aineet koneen nimen ympärillä!

=> False

”Joo. Ihan kiva juttu Selma nuo yhtäsuuruudenvertailukoneetkin, mutta minähän halusin sellaisen suurempikuinvertailukoneen, jonka avulla voisimme selvittää, sainko viime viikolla vähemmän kanaherkkuja kuin tällä viikolla. Miten sellainen tehtäisiin?”

”Olin niin innoissani yhtäsuuruudenvertailukoneista, että unohdin Pate koko kysymyksen. No näinhän se menisi”, jatkaa Selma. ”Vaihdetaan == koneen tilalle > kone ja annostellaan raaka-aineet koneeseen”

> (>) 2 1

=> True

> (>) 1 2

=> False

”Äsh! Olisihan tuo nyt pitänyt arvata koiramaisella arvauksella, että vaihtamalla koneen nimeä asia hoituu”, puistelee Pate päätään ja rapsuttaa korvaansa.

”Muista Pate, että on vielä muitakin vertailukoneita. Tunnetuimmat niistä jo nähtyjen lisäksi lienevät /= -kone ja < -kone. /= on erisuuri kuin -kone, joka tuottaa tosi, jos raaka-aineissa on kaksi eri arvoa. < -kone tuottaa tosi, jos ensimmäinen arvo on pienempi kuin toinen.   

 

5. Merkit ja merkkijonot koneiden raaka-aineina

Kokonaisluvut käyvät siis moneen, mutta millaisiin koneisiin kävisivät raaka-aineeksi merkit kuten ’a’ tai ’6’?  Merkit ovat tärkeitä, sillä monissa ohjelmointikielissä teksti muodostuu listasta merkkejä. Yksittäinen merkki on esimerkiksi ’ä’ tai ’3’. Kun ohjelmakoodissa halutaan kuvata merkkiä, laitetaan merkin ympärille heittomerkit. Muista, että yksittäiset merkitkin ovat koneita, vaikkakin aika pieniä sellaisia. Heittomerkit merkin ympärillä kertovat, että kyseessä on kone, joka luo yksittäisen merkin.

Voisimme myös kysyä, mitä sellaisia koneita on tai voisi olla, jotka käyttävät raaka-aineenaan yksittäisiä merkkejä?

Ensimmäisenä tulevat varmaankin mieleen koneet, jotka muuttavat pieniä kirjaimia isoiksi kirjaimiksi tai isoja kirjaimia pieniksi kirjaimiksi. Katsotaanpa miltä yksittäisiä merkkejä käsittelevät koneet näyttävät. Tältä näyttää toLower-niminen kone, joka muuntaa yksittäisen kirjaimen pieneksi kirjaimeksi:

> toLower 'S'

=> 's'

toUpper-niminen kone sen sijaan muuntaa yksittäisen kirjaimen isoksi kirjaimeksi:

> toUpper 's'

=> 'S'

Merkkien vertaaminen

”Tiesitkö Pate, että yksittäisiä merkkejä raaka-aineenaan käyttäviä koneita on lukuisia. Mielenkiintoinen on myös kone, jonka avulla voimme tutkia onko ovatko raaka-aineina koneeseen syötetty kaksi merkkiä samoja. Kyseessä on merkkienyhtäsuuruudenvertailukone, jonne voimme annostella kaksi merkkiä argumentteina ja selvittää ovatko ne samoja.

Sellaisen merkkienyhtäsuuruudenvertailukoneen nimi on == ja sitä käytetään samalla periaatteella kuin lukujenyhtäsuuruudenvertailukonetta. Koneen raaka-aine on kuitenkin erityyppinen kuin lukujenyhtäsuuruudenvertailukoneella. Molemmat koneet tuottavat kuitenkin samoja arvoja tosi tai epätosi.  

Merkkienyhtäsuuruudenvertailukoneen tuotos on aina joko arvo epätosi tai arvo tosi sen mukaan, millaiset raaka-aineet vertailukoneeseen milloinkin annostellaan. Katsotaanpa esimerkkiä:

> (==) 'a' 'a'

=> True

> (==) 'a' 'b'

=> False

”Yksi asia tässä Selma minua vähän vielä ihmetyttää”, sanoo Pate. ”Miksi me käytämme kirjainten vertaamisessa sanaparia yhtä suuri. Emme me kai voi sanoa, että jokin kirjain on isompi kuin jokin toinen kirjain. Vaikka kirjain olisikin ISOLLA kirjoitettu, emme me silti sano, että se on suurempi kuin jokin toinen kirjain. Emme ainakaan samassa tarkoituksessa kuin jos sanomme, että luku 5 on suurempi kuin luku 2. Mistä tässä on oikein kyse. Olisiko parempi sanoa, että kirjaimet ovat samoja tai että kirjaimet eivät ole samoja. En oikein tajua tätä yhtäsuuruusterminologiaa. Avaisitko Selma minulle sitä hieman.”

”Toki avaan kuomaseni”, jatkaa Selma. ”On hauskaa pohtia millä tavalla kaksi kirjainta ja kaksi numeroa voivat olla samoja tai yhtä suuria.

Se on täysin määrittelykysymys. Ihan samalla tavalla sinä Pate voisit laittaa ystäväsi paremmuusjärjestykseen. Onko Topi sinulle parempi kaveri kuin Selma vai ovatko ne sinulle yhtä hyviä kavereita? Kun haluat tietää kumpi kavereistasi on parempi vai ovatko kaverit kenties yhtä hyviä, tarvitset tietysti kaveruudenyhtäsuuruudenvertailukoneen.

Se olisi niin viisas kone, että pystyisi kertomaan kumpi kavereistasi on parempi vai ovatko kaksi kaveriasi ehkä yhtä hyviä. Se olisi varsinaista tekoälyä, eikös vain.”, jatkaa Selma ja iskee silmää. ”Sinun pitäisi Pate kuitenkin määritellä tuo paremmuusjärjestys ystävien välillä. Ystävien laittaminen paremmuusjärjestykseen on kyllä sellaista puuhaa, että se kannattaa tehdä salassa ystäviltä, paitsi tietysti siltä parhaalta ystävältä. Kukapa ei haluaisi tietää olevansa jonkun paras ystävä.

Numeroiden osalta on sovittu, että lukujonossa myöhemmin tulevat luvut ovat suurempia kuin aiemmin tulevat. Jonossa {1,2,3,4…} nelonen on suurempi kuin ykkönen, koska se tulee jonossa myöhemmin. Sama pätee kirjaimiin. Kun vertaamme kirjaimia ’a’ ja ’z’, ’z’ on suurempi, koska se tulee aakkosissa a:n jälkeen {’a’,’b’,…’z’}. Lukujonossa samalla kohtaa olevista luvuista toteamme, että ne ovat yhtä suuria.

> 'a'>'z'

=> False

> 'a'<'z'

=> True

Näillä tiedoilla pääsemme hyvin alkuun ja voimme jo rakentaa hyvin monenlaisia koneita. Koska me koirat emme muista pitkiä salasanoja, niin mehän voisimme Pate rakentaa vertailukoneen, joka vertailisi kahta merkkiä. Koneen voisi nimetä niin, että nimi kuvaisi mahdollisimman hyvin koneen luonnetta, nimettäköön kone salamerkinvertailukoneeksi. Kuulostaako hyvältä Pate? Onko riittävän koiramainen nimi?”

”Kuulostaa ihan huipulta, Selma”, jatkaa Pate. ”Nyt vaan kirjoittamaan ohjelmakoodi!”

> salamerkinvertailukone = (==)'p'

> salamerkinvertailukone 't'

=> False

> salamerkinvertailukone 'p'

=> True

”Sehän toimii!”, hihkuu Pate. ”Jos annan raaka-aineena merkin, jonka kone tietää, saan vastaukseksi tosi. Jos taas annan raaka-aineena merkin, jota kone ei tiedä, saan vastaukseksi epätosi. Tämähän on todellinen tietokone”, hehkuttaa Pate.

”No niinhän se tosiaan onkin”, päättää Selma.  

 Merkkijonot koneiden raaka-aineina

”Merkkien muodostamaa yhden tai useamman merkin listaa kutsutaan merkkijonoksi. Vaikka merkkien listaa kutsutaankin merkkijonoksi, on se silti merkeistä muodostuva lista eikä jono. Sanoja ympäröivät lainausmerkit edustavat funktiota (konetta), joka luo yksittäisistä merkeistä muodostuvan listan. Keksisitkö Pate jotain fiksua käyttöä merkkijonoille?”, kysyy Selma.  

”Voin hyvin keksiäkin”, vastaa Pate miettien. ”Vaikka tassunjälki onkin jokaisen koiran nimikirjoitus, niin olisihan se hienoa, kun voisi vaikka herkkuluuhun painattaa omat nimikirjaimet. Tämän jälkeen kaikki koirat tietäisivät mikä olisi kenenkin luu, eikä tulisi turhia herkkuluuriitoja”, jatkaa Pate. ”Miten sellainen kone sitten pitäisi tehdä?”, kysyy Pate vuorostaan.

”Jos koneeseen annosteltaisiin raaka-aineena etunimi ja sukunimi, voisi kone poimia molemmista ensimmäiset kirjaimet ja tuottaa nimikirjaimet, joiden välissä olisi kirjaimia erottamassa piste. Koska merkkijonot ovat listoja pitäisi koneen osata ottaa molempien listojen alkupäästä alkiot ja yhdistää ne uudeksi merkkijonoksi. Sellaiset nimikirjaimet olisi mukava painattaa herkkuluun kylkeen.

Koska merkkijonot ovat listoja, pitäisi siis ensin löytää kone, joka osaisi tuottaa listan ensimmäisen alkion. Sellainen kone on head-kone. Vieläkö Pate muuten muistat, miten saimme aikaan funktion, joka osasi luoda listan?”

”Eikös ne olleet ne hakasulkeet, joiden väliin lueteltiin ne listan alkiot”, vastaa Pate tomerana. ”Voisimme käyttää kahta head-konetta, joista ensimmäinen saisi raaka-aineena etunimen ja toinen saisi raaka-aineena sukunimen. Kun annostelemme koneet pilkulla erotettuina hakasulkujen väliin, niin meidän pitäisi saada tuotoksena lista, jossa nämä kirjaimet ovat. Sitähän me ajamme takaa eikös vaan? Mutta miltä tuollainen ohjelmakoodi Selma sitten näyttäisi?

”Se näyttäisi tältä”, vastaa Selma.  

> nimikirjainkone e s = [head e,head s]

> nimikirjainkone "Selma" "Erkkilä"

=> "SE"

 ”Mutta hei! Eikös meidän pitänyt myös lisätä piste noiden kirjaimien väliin, vai miten se nyt meni?”, kysyy Pate tarkkaavaisena.

”Ai niin pitikin. Hyvä huomio Pate. Ohjelmakoodi pitää kirjoittaa määritysten mukaan.”, toteaa Selma. ”Ei hätää, näin asia korjataan. Ohjelmakoodin voisi kirjoittaa näin:”

> nimikirjainkone' e s = [head e,'.',head s]

> nimikirjainkone' "Pate" "Ilonen"

=> "P.I"

”Jos innostuisimme tekemään tekstinkäsittelyohjelman, vastaan tulisi varmasti tilanteita, joissa kaksi erillistä sanaa pitäisi liittää yhteen ja muodostaa näistä uusi sana. Silloin saattaisimme tarvita konetta, joka tuottaisi kahdesta merkkijonosta yhden.

Sellainen kone on ++ - kone. ++ -kone saa raaka-aineena kaksi merkkijonoa tuottaen kolmannen. Konetta voi kokeilla näin

> (++) "alkupää" "loppupää"

=> "alkupääloppupää"

tai näin

> "alkupää"++"loppupää"

=> "alkupääloppupää"

Merkkijonojen yhdistäminen on siis kahden listan yhdistämistä. Seuraavassa luvussa tutustumme listoihin tarkemmin. Kun osaamme käsitellä kokonaislukuja, merkkejä, merkkijonoja ja listoja, voimme luoda koiraystäviemme kanssa monenlaisia sovelluksia. Nyt kun Pate tiedät, miten nimikirjainkone tehdään, voit opettaa sen myös Topi-koiralle tai vaikka Orvokille.”

”No ilman muuta opetan nimikirjainkoneen myös Topille ja Orvokille, heti kun iltalenkillä näemme. Ei enää herkkuluuriitoja, vaan rauha maassa koirien kesken”, vitsailee Pate. ”Funktionaaliselle ohjelmoinnille tuntuu löytyvän vaikka kuinka paljon käytännön sovelluksia. Tietävätköhän ihmiset, miten paljon käyttöä näille jutuille oikeasti on? ”, jatkaa Pate ja pistää poskeensa mehevän kanaherkun.  

Merkkijonojen vertaaminen

”Olisi muuten Selma hyvä, jos voisimme kerralla vertailla kokonaisia merkkijonoja, ettei tarvitsisi vertailla niitä aina merkki kerrallaan käyttämällä !! -konetta ja ottamalla jokaisesta merkkijonosta aina !! -koneella yksi merkki ja verrata sitä toisen merkkijonon !! -koneella samasta kohtaa otettuun merkkiin. Se nimittäin on aika kovaa hommaa tällaiselle ahkerammallekin vehnäterrierille. Onko tuohon jotain kätevämpää tapaa?”

”Kyllä vain on ja montakin”, vastaa Selma. ”Jos annostelet koneeseen koiramaisen salasanasi, niin onhan meidän tutkittava onko salasana oikein vai ei. Tuo tutkimus edellyttää koneeseen annostelemasi salasanan vertaamista koneen tuntemaan salasanaan. Tältä se voisi näyttää.”

Määritellään salasanavertailukone näin:

> salasanavertailukone = (==)"kanaherkku"

Annostellaan salasanavertailukoneeseen ”nappulat”.

> salasanavertailukone "nappulat"

=> False

Annostellaan salasanavertailukoneeseen ”kanaherkku”.

> salasanavertailukone "kanaherkku"

=> True

”Kahden merkkijonon yhtäsuuruus voidaan siis selvittää käyttämällä == -konetta aivan samalla tavalla kuin merkkien yhtäsuuruutta tutkittaessa.

Lisää älyä

”Moi taas Selma! Aloitetaanko taas miettimään ohjelmointijuttuja. Mitäpä tänään olisi tarjolla?”

”Moi Pate!”, vastaa Selma iloisesti. ”Tänään voisimme käydä läpi sitä, miten saisimme koneisiimme hiukan enemmän älyä. Mitäs tykkäisit sellaisesta suunnitelmasta?”

”No se sopii hyvin! Meillä koirilla ei ole koskaan liikaa älyä kirjoittaa älykkäitä ohjelmia. Aloitetaan!”

”Näin tehdään”, jatkaa Selma. ”Älykkyys syntyy siten, että koneemme voi saada aikaan erilaisia tuotoksia riippuen siitä, millaisia raaka-aineita niihin annostellaan. Nyt esittelen sinulle herkkujenarviointikoneen, joka tietää, kuinka herkullisia eri herkut ovat. Katsotaanpa ohjelmakoodia.”

arvioiHerkut herkku  

 |herkku=="kanaherkku" = "Erinomainen ja maittava"

 |herkku=="maksalaatikko" = "Aika maukas"

 |herkku=="nappulat" = "Ei niin hyvä, mutta terveellinen"

”Hetkinen. Nyt on taas uusia merkkejä ja kaikenlaista”, haukahtaa Pate. ”Tuon == -nimen minä kyllä tunnistan. Sehän on yhtäsuuruudenvertailukonekone! Lisäksi tuttua on, että tuossa on taas tuo yhtäsuuruusmerkki ja sen oikealla ja vasemmalla puolella on asioita. Mutta mikä kumma on tuo pystyviiva noiden kolmen rivin edessä, sitä minä en ole koskaan nähnytkään.”

”Hyvinpä olet Pate lukenut kirjasen aiemmat osat”, kehuu Selma. ”Tuo pystyviiva on vahti. Sen avulla voimme tutkia arvoja ja päättää, millainen tuotos milläkin arvolla syntyy. Jos vertailukoneen tuotoksena on arvo tosi, niin valitsemme samalla rivillä yhtäsuuruusmerkin oikealla puolella olevan koneen(funktion). Jos raaka-aineena annosteltava asia (funktion argumentin arvo) on ”kanaherkku”, niin yhtäsuuruudenvertailukone selvittää onko ”kanaherkku” sama kuin ”kanaherkku” ja vertailu tuottaa tietysti arvon tosi. Kokeilepa, niin ymmärrät.”  

> arvioiHerkut "kanaherkku"  

=> "Erinomainen ja maittava"

> arvioiHerkut "maksalaatikko"  

=> "Aika maukas"

”Vahtien avulla voimme tutkia arvoja ja ohjata ohjelmamme toimintaa. Katsopa vielä tämä toinenkin esimerkki.”

herkkujaViikossa x 

 |x<1 = "Et ole syönyt ollenkaan herkkuja!"

 |x>=1 && x < 4 = "Olet syönyt jonkin verran herkkuja!"

 |x>=4 = "Olet syönyt aivan liikaa herkkuja!"

 

> herkkujaViikossa 4

=> "Olet syönyt aivan liikaa herkkuja!"

> herkkujaViikossa 0

=> "Et ole syönyt ollenkaan herkkuja!"

”No jopas on taas kaikenlaista!”, huokaisee Pate. ”Mikä on muuten tuo kaksi & merkkiä peräkkäin? Sitä en ole kuunaan nähnyt.”

”Olipa hyvä kysymys Pate!”, sanoo Selma. ”Kuten huomaat && -merkin molemmin puolin on vertailukone. && -merkki tarkoittaa myös vertailukonetta. && tarkoittaa sellaista vertailukonetta, joka tuottaa arvon tosi vain siinä tapauksessa, että sen molemmilla puolilla olevat vertailukoneet tuottavat kumpikin arvon tosi.

&& on siis vertailukone, joka saa kaksi raaka-ainetta, joita voimme kutsua myös totuusarvoiksi (tosi tai epätosi). Meillä on olemassa toinenkin hyvin samantyyppinen vertailukone, jonka nimi on ||. || tuottaa arvon tosi vain siinä tapauksessa, että sen molemmilla puolilla olevista vertailukoneista vähintään yksi tuottaa arvon tosi.

Tämä kaikki näyttää Pate vielä paljon selvemmältä, kun esitämme sen vanhassa tutussa muodossa eli ensin && -kone ja sitten vasta kaksi muuta vertailukonetta. Katsohan tätä!”

|(&&) (x>=1) (x<4) = "Olet syönyt jonkin verran herkkuja!"

Nyt on päivänselvää, että && -koneeseen annostellaan raaka-aineina kaksi vertailukonetta. Sulkumerkkejä näyttää nyt olevan vähän enemmän kuin aikaisemmin, mutta niistä sinun ei kannata tässä kohtaa Pate murehtia. Logiikka paljastuu sinulle sulkeista huolimatta. && -kone tuottaa oman logiikkansa mukaan joko arvon tosi tai epätosi kahden muun koneen tuotosten perusteella.

Kyseessä on siis kolmen koneen pötkö, joista ensimmäiseen annostellaan raaka-aineina kahden sitä seuraavan koneen tuotokset. Eikös ole helppoa!

Myös vahtia eli |-merkkiä voit ajatella Pate koneena. Se on kone, joka selvittää yhtäsuuruusmerkin oikealle puolella olevan arvon vain, jos sen oma raaka-aine eli argumentin arvo on tosi. Tässä on nyt koiramaisen tärkeää huomata, että vahteja voi olla useampia kuin yksi. Jos meillä on kolme vahtia, kuten esimerkissämme, voimme haarautua ohjelmakoodissamme kolmeen suuntaan.

Mietipä tätä! Kun arvioiHerkut nimeä jossain ohjelmakoodissa käytetään, johtaa se siihen, että vain yksi kolmesta eri merkkijonon tuottavasta koneesta valitaan. Arvoja tosi tai epätosi tuottavia koneita kutsutaan myös predikaateiksi. Tarvitsemme siis vahteja, jotta voimme haarautua ohjelmakoodissamme – valita yhden useammasta koneesta, joka tulee seuraavaksi tuotantoketjussamme.

Näillä kahdella totuudenvertailukoneella && ja || ja vahteja ohjelmakoodissasi viljelemällä voit lisätä älyä ohjelmakoodiisi vaikka kuinka paljon.”   

 

6. Hahmontunnistusta ja kanaherkkuja 

”Tiedätkö muuten Pate, montako koirarahaa nuo eri herkut maksavat?”, kysyy Selma.

”En tiedä, koska en joudu niitä koskaan itse maksamaan.”, vastaa Pateja hiukan luimistelee korviaan.  

”Minäpä muuten tiedän. Katsopa, niin näytän taas ohjelmakoodia. Herkkuhinnaston tekeminen on kuin lasten leikkiä.”, hehkuttaa Selma.

hintaKone "kanaherkku" = 70
hintaKone "maksalaatikko" = 38
hintaKone "nappulat" = 22

”Siis onko herkkuhinnasto tosiaankin tuossa? Miten se sitten oikein toimii?”, kysyy Pate.

”No herkkuhinnaston pitää olla tietysti koiramaisen helppokäyttöinen. Näin sitä Pate käytetään. Nyt kun herkkujen hinnat on määritelty, voidaan niitä pyytää kirjoittamalla hinta ja sen jälkeen herkun nimi.”

> hintaKone "kanaherkku"

=> 70

> hintaKone "maksalaatikko"

=> 38

> hintaKone "nappulat"

=> 22

”Se todellakin toimii”, sanoo Pate. ”Se näyttää todella yksinkertaiselta, mutta miten ne noin voi toimia? Miten voi olla kolme konetta, joilla on sama nimi – hintaKone?”

”Kuulehan Pate! Oikeasti tuossa onkin vaan yksi hintaKone-niminen kone. Sitten meillä on kolme vaihtoehtoa, jotka kone tunnistaa eri raaka-aineeksi: ”kanaherkku”, ”maksalaatikko” ja ”nappulat”. Kirjoitetaanpa ohjelmakoodi vielä hieman selkeämmin, niin näet, että siinä todellakin on vain yksi hintaKone-niminen kone.”

hintaKone herkunNimi = case herkunNimi of
     "kanaherkku" -> 70
     "maksalaatikko" -> 38
     "nappulat" -> 25  

”Kuten näet Pate, meillä on todellakin vain yksi kone, jonka nimi on hintaKone”, jatkaa Selma. ”Kone saa raaka-aineenaan herkun nimen, joka voi vaihdella. Ohjelmointikielemme on niin viisas, että se osaa tunnistaa sisään tulevat raaka-aineet ja valitsee oikean hinnan annostellun raaka-aineen perusteella. Osaisitko Pate muuten tehdä koneen, joka kertoisi meille herkun nimen, jos tietäisimme sen hinnan. Minäpä näytän, miten sellainen tehtäisiin, katsohan tänne!”

herkkuKone herkunHinta = case herkunHinta of
     70 -> "kanaherkku" 
     38 -> "maksalaatikko"
     25 -> "nappulat"

-> herkkuKone 70

=> "kanaherkku"

”Nyt on Pate oleellista huomata, että me emme verranneet hintoja tai herkkujen nimiä, kuten vahtien kanssa. Me luotimme hahmontunnistukseen.”

”Häh!”, älähtää Pate. ”Eikös hahmontunnistus ole sitä, että tietokone voi tunnistaa kuvasta koiran ja kertoa sen ihmiselle?”

”No kyllä se sitäkin on”, vastaa Selma. ”Tässä yhteydessä se on kuitenkin keino tehdä juuri tuollaisia temppuja, kuten äsken näimme. Ja hahmontunnistuksen avulla voidaan tehdä muutakin hauskaa. Sen avulla voitaisiin tunnistaa, onko sinulla kanaherkkupussissasi yksi tai useampia kanaherkkuja. Haluatko nähdä, miten se tehdään?”

”No totta kai. Kukapa ei haluaisi tietää kanaherkkujen tarkkaa määrää herkkupussissa. Se on elämän tärkeimpiä asioita”, jatkaa Pate paatoksella.”

”Ok Pate. Koska kanaherkkuja voi olla yksi tai useampia, niin meillä pitää olla pussi, missä herkkuja on. Pussina voimme käyttää aivan hyvin listaa, mutta koska listat ovat niin tärkeä juttu ohjelmoinnissa siirrymme käsittelemään kanaherkkupusseja listojen muodossa seuraavaan lukuun. Näytän myöhemmin miten hahmontunnistuksen avulla voitaisiin selvittää onko kanaherkkupussissa yksi vai useampi kanaherkku tai ei yhtään herkkua. Sitä ennen meidän pitää kuitenkin tutustua listan käsitteeseen.”

 

7. Kanaherkkupusseista listoihin

Listat ja listoihin liittyvät koneet

”Listat ovat Pate ohjelmoijan tärkein työkalu. Listojen kanssa touhuaminen on hauskaa ja helppoakin. Tämä johtuu etenkin siitä, että meillä on paljon hienoja koneita, joihin listoja voi soveltaa. Listan avulla on helppoa järjestellä niin kanaherkkuja kuin monia muitakin asioita. Aloitetaan siis laittamalla kanaherkut pussiin, jota ohjelmassamme voimme luontevasti kuvata listalla.”

> lista0 = []

”Kun haluamme lisätä siihen pari kanaherkkua, teemme näin. Lisätään malliksi kanaherkut yksi kerrallaan. : -kone on siis sellainen kone, jonka avulla voidaan lisätä listaan alkuun uusi alkio.”

> lista1 = "kanaherkku1":lista0

> lista2 = "kanaherkku2":lista1

> lista2

=> ["kanaherkku2","kanaherkku1"]

”Nyt Pate näemme, että listalla on kaksi kanaherkkua. Listaan loppuu voimme lisätä uusia alkioita ++ -koneella. Näin se käy:

> lista3 = lista2 ++ "kanaherkku3"

> lista3

=> ["kanaherkku2","kanaherkku1","kanaherkku3"]

Mutta mitäs jos haluamme tietää kuinka monta herkkua listalla on?”

”Jaa-a”, ihmettelee Pate. ”Voisiko siihen löytyä jokin pussissaolevienkanaherkkujenlukumääränlaskukone, koska kaikkiin muihinkin asioihin on ratkaisuna aina jokin kone?”

”Kyllä vaan Pate”, vastaa Selma. ”Olet todellakin hoksannut jutun jujun kuomaseni, ratkaisu on aina kone, pitää vaan keksiä millainen. Katsopa tätä konetta ja sen kuvaa. Koneeseen menee raaka-aineena lista kanaherkkuja ja kone laskee, kuinka monta kanaherkkua raaka-aineena olevalla listalla on.”

> length kanaherkkulista

=> 2

length-kone on kätevä. Se saa raaka-aineena listan, jonka alkioiden lukumäärän se osaa laskea. Nyt on helppo pysyä kärryillä herkkujen lukumäärästä ja varmistaa, ettei naapurin koira ole käynyt sinun kanaherkkupussillasi salaa.

length -kone ei suinkaan ole ainut listojen kanssa toimiva kone. Muita tärkeitä listojen kanssa toimivia koneita ovat esimerkiksi head- ja tail-koneet sekä !!-kone.

!!-koneella saamme tuotettua listasta tietyn alkion alkaen listan alusta. Listan ensimmäiseen alkioon viitataan luvulla 0, toiseen alkioon luvulla 1 jne. Huomaathan Pate, että !! -kone saa raaka-aineensa koneen edessä ja takana. Koneen nimen eteen laitetaan lista ja koneen nimen jälkeen laitetaan luku ja kone tuottaa järjestyksessä luvun osoittaman alkion.

> ["kanaherkku1","kanaherkku2","kanaherkku3"]!!0

=> "kanaherkku1"

> ["kanaherkku1","kanaherkku2","kanaherkku3"]!!1

=> "kanaherkku2"

Head -koneella saamme tuotettua listan ensimmäisen alkion:

> head ["kanaherkku1","kanaherkku2","kanaherkku3"]

=> "kanaherkku1"

 tail -koneella saamme tuotettua listan loppuosan ilman ensimmäistä alkiota:

> tail ["kanaherkku1","kanaherkku2","kanaherkku3"]

=> ["kanaherkku2","kanaherkku3"]

 Huomaa, että head tuottaa alkion ja tail tuottaa listan.

> head ["kanaherkku1","kanaherkku2","kanaherkku3"]

=> "kanaherkku1"

”Aivan huippua. Mutta nyt annan sinulle Selma sellaisen herkkuongelman, että jos siihen keksit ratkaisun, niin tarjoan sinulle kyllä kanaherkun kanaherkkupussistani. Oletetaan, että pussissani olisi muitakin herkkuja kuin kanaherkkuja. Miten saisimme tietää kuinka monta kappaletta kutakin herkkua olisi? Onnistuisimmeko kirjoittamaan sellaisen ohjelmakoodin?”

”No nytpä pistit pahan Pate!”, jatkaa Selma. ”Eiköhän tuohonkin kuitenkin keinot keksitä, pitää vaan keksiä lisää koneita. Kone, joka voisi ratkaista ongelman on filter-kone. Siihen tutustumme seuraavassa luvussa.”

Listat ja hahmontunnistus

”Kuten lupasin, aion kertoa, miten listoja voi tutkia hahmontunnistuksen avulla. Katsohan Pate seuraavaa esimerkkiä.”

selmanOstoslista = ["Kanaherkut", "Nappulat", "Maksalaatikko","Koiransuklaa"]

tutkiOstoslista [] = "Ostoslista on tyhjä"

tutkiOstoslista [x] = "Ostoslistalla on vain 1 alkio"

tutkiOstoslista (x:xs) = "Ostoslistalla on enemmän kuin 1 alkio"

”Kuten näet, selmanOstoslista listalla on neljä alkiota. Jos tutkiOstoslista -koneelle annostellaan raaka-aineena tyhjä lista eli [], se osaa kertoa, että lista on tyhjä. Jos taas annostelemme tutkiOstoslista -koneelle yhden alkion, jota kuvaa hahmo [x], saamme koneen tuotokseksi ”Ostoslistalla on vai 1 alkio”. Jos lista ei ole tyhjä eikä siinä ole vain yhtä alkiota, valitsemme ”Ostoslistalla on enemmän kuin 1 alkio”, johon viittaa hahmo x:xs. Hahmo x:xs, viittaa ylipäänsä listaan, jossa on vähintään yksi alkio. Kokeile Pate kutsua funktioita tyhjällä listalla, listalla, jossa on arvoja ja listalla, jossa on useampia kuin yksi arvo, niin näet miten se toimii. Tässä tapauksessa voisimme korvata viimeisen hahmon x:xs myös alaviivalla, joka tarkoittaa ”mikä tahansa arvo”, jolloin ohjelmamme näyttäisi tältä:

selmanOstoslista = ["Kanaherkut", "Nappulat", "Maksalaatikko","Koiransuklaa"]

tutkiOstoslista [] = "Ostoslista on tyhjä"

tutkiOstoslista [x] = "Ostoslistalla on vain 1 alkio"

tutkiOstoslista _ = "Ostoslistalla on enemmän kuin 1 alkio"

”Tuohan on tosi kätevää!”, hihkuu Pate. ”Kun käytämme hahmontunnistusta, saamme siis jo hyvissä ajoin vihiä millaista raaka-ainetta koneeseen on tulossa ja osaamme säätää koneemme tuotoksen raaka-aineen luonteen mukaisesti. Jos Ostoslistalla on paljon asioita, voisimme ottaa kauppareissulle ison kassin mukaan. Jos ostoslista on tyhjä, emme tarvitsisi kauppakassia ollenkaan, sillä tuskin lähtisimme kauppaan laisinkaan. Mutta voisiko Selma joskus käydä niin, että menisimme kauppaan ilman kauppalistaa ja tekisimme heräteostoksia? Olisiko mahdollista että päähämme pälkähtäisi vasta kaupassa, mitä haluamme. Niinhän monet ihmiset toimivat. Voisimmeko me koiratkin ohjelmoidessamme toimia hetken mielijohteesta?”

”Nyt olet Pate asian luissa, ytimissä ja ydinluissa”, vastaa Selma. ”Meidän koiramaisissa esimerkeissämme ei ole tähän mennessä ollut hetken mielijohteita. Hetken mielijohdetta voitaisiin kuvata satunnaisuudella. Satunnaisuus on kuitenkin jotain, mitä emme vielä tässä vaiheessa opintojamme tarvitse – tehdään satunnaisia juttuja ja päähänpälkähdyksiä opiskelun myöhemmissä vaiheissa. Lupaan, että senkin aika vielä tulee ja pääsemme toimimaan kuten emäntämme ja isäntämme joskus tuntuvat toimivan, arvaamalla.”

”Ok! Tässä vaiheessa me siis vielä touhuamme koneilla, joiden raaka-aineet määrittelevät, mitä koneesta tulee ulos. Ehkä on parempikin, ettemme vielä lähde laatimaan sattumanvaraista tuotoksia luovia koneita. Onhan se ajatuksena hiukan pelottavakin, sillä onhan se mukava saada kanaherkku aina, kun menee kiltisti pyydettäessä maahan tai istumaan! Emännän tai isännän kehu on tietysti kiva palkinto sekin, mutta kanaherkku on aina kanaherkku”, filosofoi Pate kuin Sokrates ikään.

”Yhtä asiaa haluan vielä hahmontunnistuksesta sinulle Pate teroittaa. Muista, että koodirivien järjestyksellä on merkitystä, kun mietit koneeseesi annosteltavien raaka-aineiden luonnetta. Jos laitat ensimmäiseksi vaihtoehdoksi _ eli ”mikä tahansa hahmo”, niin valituksi tulisi aina ” "Ostoslistalla on enemmän kuin 1 alkio". Tämä johtuu siitä, että lista, joka on tyhjä tai siinä on yksi alkio tai useampia, on aina ”mikä tahansa hahmo”. _ kuvaa siis tässä yhteydessä mitä tahansa hahmoa, joten sitä ei kannata tutkia ensimmäisenä! Saitko ajatuksestani kiinni Pate?”

”Aah. No tuo onkin tärkeä asiaa muistaa. Toimiiko logiikka samalla tavalla, jos tutkisimme hahmojen sijaan arvoja?”, kysyy Pate uteliaana.

”Kyllä vaan”, vastaa Selma. ”Idea on arvoja tutkittaessa aivan sama. Ensin tutkitaan ne kaikkien yleisimmät tapaukset ja loput jätetään loppupäähän”, jatkaa Selma.

”Ai-van”, myhäilee Pate ja jatkaa ”Tästä sainkin hyvän muistisäännön kaikkeen arvojen tutkimiseen. Se pätee myös koiran elämään. Tee ensin tärkeät asiat ja jätä loput loppupäähän. Ja ne asiat, jotka tapahtuvat usein, kannattaa tutkia ensin. Se nopeuttaa ohjelman toimintaa!”

 

8. Filter-kone 

”Laitetaan Pate nyt listalla neljä hyvää herkkua, joista kaksi on samoja.”

herkkuLista = ["kanaherkku","kanaherkku","koiransuklaanappi", "herkkuluu"]

”Meidän pitäisi Pate nyt valita listalta ensin kanaherkut ja laskea montako niitä on. Sitten pitäisi valita listalta koiransuklaanapit ja laskea montako niitä on. Lopuksi pitäisi vielä valita herkkuluut listalta ja laskea, montako niitä on. Jos Pate sinun pitäisi tehdä sellainen valintakone, joka osaisi valita eri herkkuja niin miten sinä sen tekisit?”, kysyy Selma.

”Ensin tietysti kaikki herkut olisivat sekaisin yhdessä isossa herkkupussissa. Sitten valitsisin siitä pussista ensin tietysti parhaimmat eli kanaherkut ja laittaisin ne yhteen tyhjistä pusseista. Sitten valkkaisin koiransuklaanapit ja laittaisin ne toiseen tyhjään pussiin ja lopuksi laittaisin vielä herkkuluut kolmanteen pussiin ja alkuperäisen ison pussin laittaisin tietysti siististi eteisen kaappiin ostosreissua odottamaan. Lisäksi laittaisin jokaiseen pussiin tarran, jossa lukisi mitä herkkuja pussissa on.”

”Nyt kun loisimme ohjelmakoodin, jossa jokaista pussia vastaisi lista ja sitten antaisimme jokaisen listan raaka-aineena length-koneelle, niin eikös meillä sitten olisikin tieto siitä kuinka paljon eri herkkuja on? length-konehan pystyisi laskemaan eri pussien alkioiden eli herkkujen lukumäärät, eikös vaan?”, kysyy Pate.

”Oikeassa olet!”, vastaa Selma. ”Meidän pitää vain keksiä ensin, miten valintakone toimii. Keksitkö sinä Pate?”

”Voisin ainakin yrittää”, jatkaa Pate. ”Meidän pitää jotenkin kertoa valintakoneelle, mitä halutaan valita. Pitää antaa siis jokin ehto, joka voisi olla muotoa ”ota pussista kaikki sellaiset, jotka ovat kanaherkkuja ja laita ne tyhjään pussiin”, vai mitä sanot Selma?”

”Juuri niin Pate. Sinähän olet jo tosi etevä. Katsotaanpa seuraavaksi millaiset koneet saamme näistä ajatuksista aikaiseksi.”

kanaherkkuLista = filter(=="kanaherkku") herkkuLista

”Katsohan yllä olevaa kuvaa Pate. filter-kone on kone, joka käy yksitellen listan alkioita läpi ja soveltaa jokaista alkiota vertailukoneeseen yksitellen. Joka kerta, kun yhtäsuuruudenvertailukone(=="kanaherkku") tuottaa arvon tosi, niin alkio poimitaan vanhalta listalta uudelle listalle. filter -kone saa siis kaksi erilaista raaka-ainetta: vertailukoneen ja listan, joiden alkioita halutaan käytävän läpi ja suodatettavan uudelle listalle.

Seuraavan kuvan alla oleva ohjelmarivi tarkoittaa, että poimitaan listalta kaikki alkiot, jotka sisältävät merkkijonon ”kanaherkku”. filter -koneella luodaan siis uusi lista, jossa on vain alkioita, joissa on merkkijono ”kanaherkku”. Samaa ideaa toistetaan myös herkkuluille ja koiransuklaanapeille.”

 

koiransuklaanappiLista = filter(=="koiransuklaanappi") herkkuLista

herkkuluuLista = filter(=="herkkuluu") herkkuLista

”Nyt kun Pate saimme eri herkut omille listoilleen, on helppo selvittää jokaisen listan alkioiden lukumäärät, tuttuun tapaan.”

kanaherkkuLkm = length kanaherkkuLista

herkkuluuLkm = length herkkuluuLista

koiransuklaanappiLkm = length koiransuklaanappiLista

Kahden koneen yhdistäminen yhdeksi

”No eipä ollut vaikeata”, tokaisee Pate. ”Onko muuten aina tehtävä tuo homma kahdessa vaiheessa eli ensin määriteltävä tuo kanaherkkuLista ja vasta sitten kanaherkkuLkm vai voisimmeko päätyä kanaherkkujen lukumäärään ilman moisia välivaiheita?”

”Kyllä se vaan onnistuu!”, vastaa Selma. ”Nokkelana koirana voit yhdistää herkkujen valintakoneen ja herkkujen lukumäärän laskukoneen yhdeksi koneeksi. Koneeseen annostellaan yhtenä raaka-aineena iso herkkupussi eli lista, jossa voi olla kaikenlaisia herkkuja ja toisena raaka-aineena vertailukone(esim. =="kanaherkku"). Ulos koneesta tulee tietyn herkun lukumäärä isossa pussissa. Tältä näyttäisi kone,"jolla voimme selvittää kuinka monta kanaherkkua herkkupussissa on.”

kanaherkkuLkm = length(filter(=="kanaherkku")herkkuLista)

”Ohjelma voidaan Pate ajatella myös matematiikasta tuttuna sievennyksenä. Ensin lasketaan: filter(=="kanaherkku") herkkuLista, joka antaa vastaukseksi ["kanaherkku","kanaherkku"]. Seuraavassa vaiheessa vastaus annetaan raaka-aineeksi length -koneelle: length(["kanaherkku","kanaherkku"]). Sen jälkeen length -kone vuorostaan tuottaa listan alkioiden lukumäärän eli luvun 2.

Toisin sanoen ensin poimitaan herkkuListalta kaikki alkiot, jotka sisältävät merkkijonon ”kanaherkku” ja lasketaan poimittujen alkioiden määrä. Ensin luodaan siis uusi lista, jossa on vain alkioita, joissa on merkkijono ”kanaherkku”. Ja kun lista on luotu, sille sovelletaan length -konetta, joka tuottaa alkioiden lukumäärän. Ei hullumpaa vai mitä sanot Pate?”

”Ei ollenkaan”, vastaa Pate. ”Jos oikein ymmärsin, niin koko ohjelma on itse asiassa sieventämistä – mutta voiko se olla niin yksinkertaista?”

”Kyllä se Pate voi. Yksinkertainen on kaunista!”, hihkuu Selma. ”Tässä on Pate vielä herkkuluiden ja koiransuklaanappien lukumäärät selvittävät ohjelmakoodit. Jos haluat harjoitella, voit piirtää niistäkin tuollaiset funktiokoneet, niin opit asiat paremmin.”

herkkuluuLkm = length(filter(=="herkkuluu")herkkuLista)

koiransuklaanappiLkm = length(filter(=="koiransuklaanappi") herkkuLista)

”Kuulehan Selma. Jotta (=="herkkuluu") kohtaa ei jatkuvasti tarvitsisi toistaa eri puolilla ohjelmakoodia, voisimmeko korvata sen antamalle tuolle vertailukoneelle nimen?”

”Kyllä vaan Pate. Luonteva nimi vertailukoneelle voisi olla onkoHerkkuluu. Nimi on hyvä siksi, että == - kone antaa aina tuotokseksi tosi tai epätosi, joten voi olla luontevaa ”kysyä” onkoHerkkuluu ja antaa koneen sitten vastata tosi tai epätosi. Katsohan koodia. Voit laatia koodin joko näin

onkoHerkkuluu = (=="herkkuluu")

tai hahmontunnistusta käyttäen näin

onkoHerkkuluu "herkkuluu" = True

onkoHerkkuluu _ = False

Sitten vain muutat ohjelmakoodiasi näin

herkkuluuLkm = length(filter(onkoHerkkuluu)herkkuLista)

Ja nyt voit käyttää onkoHerkkuluu -konetta aina siellä, missä sinun tarvitsee tutkia, onko jokin merkkijono ”herkkuluu” vai ei. Näin sinun Pate ei tarvitse aina erikseen kirjoittaa tuota ”herkkuluu” – sanaa joka puolelle, vaan voit korvata sen onkoHerkkuluu -koneella. Jos kirjoitat moneen paikkaan ”herkkuluu”, niin joskus voikin tulla virhe ja kirjoitat vahingossa ”hekkuluu” ja tuletkin vahingossa etsineeksi herkkulistaltasi jotain, mitä siellä tuskin on. Kun käytät yhtä ja samaa konetta, voit korjata virheellisen ”hekkuluu” -sanan ja se tulee saman tien voimaan kaikissa niissä muiden koneiden määritelmissä, jossa esiintyy onkoHerkkuluu -nimi.

”Ohjelma näyttää Selma kyllä toimivan, mutta onko tuo koodi nyt niin fiksua kuin se voisi olla”, kysyy Pate hieman epäilevästi.  ”Eikö tuossa ole aika paljon samannäköisiä koodirivejä, joissa vaihtuu vain tuo herkun tai sitä vastaavan koneen (esim. onkoHerkkuluu) nimi. Nuo lukumääriä laskevat koneiden nimet, kuten herkkuluuLkm ja koiransuklaanappiLkm eroavat toistaan vain filter-funktion argumentin eli annosteltavien raaka-aineiden osalta. Loppujen lopuksi näytämme saavan aikaiseksi jakauman eri herkkujen määristä pussissa, mitä edustavat nuo Lkm -päätteiset koneet.

Kun nuo herkkujen nimet on jo kuitenkin esitelty tuossa herkkuListassa, niin emmekö voisi käyttää nimiä suoraan tuosta listasta? Nythän nimiä käytetään vertailukoneessa ”tassupelillä”. Mehän tarvitsisimme nyt vain sellaisen listan, joka sisältää vain ne herkkuListan alkiot, jotka ovat ainutlaatuisia, eli listan, jossa on vain eri herkkuja – listan, jossa ei ole kahta samaa alkiota. Näin saisimme tietää erilaisten herkkujen joukon. Ja mitä sitten tapahtuu, jos erilaisia herkkuja onkin vaikka 50, pitääkö kaikki rivit kirjoittaa käsin. Minä osaan Selma helpostikin kuvitella 50 erilaista koiranherkkua. Lkm-ja Lista -loppuisia koneiden nimiä tulee hurja määrä ja on hirveän helppo tehdä kirjoitusvirhe niitä kirjoitellessa ja meneehän siihen jo aikaakin paljon.”, huokailee Pate.

”Oletpa Pate taas kerran oikeassa!”, jatkaa Selma. ”On todellakin niin, että ohjelmasta voisi tehdä paljon fiksumman. Katsotaanpa yhdessä, miten se onnistuisi. Olet oikeassa myös siinä, että herkkuListalla olevia nimiä kannattaa käyttää uudelleen. Ei ole mielekästä käsin kirjoittaa uudelleen herkkujen nimiä, koska olemme ne jo kerran kirjoittaneet. Mutta osaisitko kertoa millainen olisi se ohjelmakoodi, jolla saisimme toisteisuutta vähemmälle ohjelmakoodissa?”

”Nyt pistit kyllä Selma pahan pähkinän minun koiranaivoilleni, mutta yritetäänpä. Jos siis saisimme aikaiseksi tuollaisen listan, jossa on herkkujen joukko, niin mehän voisimme käyttää sitä listaa ”valitsimena” siinä tilanteessa, että meidän pitäisi laskea kuinka monta kertaa eri herkut esiintyvät tuossa herkkuLista -listalla. Otettaisiin ensin herkkujen joukon ensimmäinen alkio ja annosteltaisiin se raaka-aineena sellaiseen koneeseen, joka osaisi käyttää alkioita raaka-aineena laskiessaan kuinka monta kertaa kyseinen herkku esiintyy herkkuLista -listalla. Eli me tarvitsemme siis kaksi listaa: herkkujen joukkoa kuvaava lista ja alkuperäistä herkkupussin sisältöä kuvaava lista. Ja siten vaan tuotetaan niistä raaka-aineista uusi lista, jonka jokainen alkio kuvaa tietyn herkun esiintymiskertoja alkuperäisellä herkkuLista -listalla.”

”Kuulostaa järkevältä”, vastaa Pate Selmalle. ”Miten ihmeessä saamme aikaiseksi erilaisia alkioita sisältävän joukon tuosta alkuperäisen herkkupussin sisältöä kuvaavasta herkkuLista -listasta? Taas jollain koneella?”

”Koneellapa hyvinkin”, vastaa Selma. ”Ja sellainen kone on jo keksitty. Sen nimi on nub -kone. Annat sille raaka-aineena minkä tahansa listan, niin se tuottaa sinulle listan, jossa on vain erilaisia alkioita. Mitä sellaisesta koneesta sanot?”

”Wau ja hau!”, vastaa Pate. ”Sitähän me sitten käytämme. Mutta kun sellainen lista saadaan aikaiseksi, niin miten me käytämme sitä nub-koneen tuottamaa listaa hyväksemme? Miten me saamme nuo alkuperäiset merkkijonot korvattua niillä nub-koneen tuottamilla arvoilla? Miten se homma saadaan oikein rullaamaan?”

”Kuulehan Pate!”, jatkaa Selma. ”Homma saadaan toimimaan esittelemällä uusi kone, nimittäin map-kone. Se on koneiden aatelia ja saa raaka-aineenaan toisen koneen ja listan asioita. Kun opit käyttämään map-konetta, niin olet jo ohjelmoinnin opinnoissasi pitkällä. Se on yksi tärkeimpiä koneita ja map-kone kannattaa opetella hyvin. Koirakaverisi Topi saattaa kyllä mennä vihreäksi kateudesta, kun kerrot, että osaat käyttää map-konetta. Sitten voit opettaa map-koneen käytön Topillekin, niin hän saa turkkiinsa tutun ruskean sävyn takaisin!

 

9. Map-kone 

”Kuulehan Pate! Nyt on aika tutustua map-koneeseen. Se on hienostunut kone, jota käytetään nykyään monissa ohjelmointikielissä.”

”Vai niin Selma”, vastaa Pate. ”Mitä erinomaista siinä map-koneessa sitten oikein on?”

”Oletko Pate kuullut kennelistä, jossa jokaisen kennelin koiran kanaherkkuannosta nostettiin kolmella kanaherkulla?”

”No enpä ole”, vastaa Pate uteliaana. ”Olisi tietysti mukava olla koirana sellaisessa kennelissä, missä kanaherkkuannosta voidaan nostaa vaivattomasti kaikille koirille kerralla. Se olisi kennel minun makuuni, kirjaimellisesti.”

”No varmasti olisi”, vastaa Selma. ”Ajatellaan, että meillä olisi kuuden koiran kenneli ja koirien nimet olisivat Selma, Topi, Repa, Motti ja Pate. Koirien tämänhetkiset kanaherkkuannokset olisivat:

Selma, 2 annosta
Topi, 3 annosta
Repa, 5 annosta
Motti, 1 annos
Pate, 3 annosta.

Annoksia voitaisiin kuvata myös listalla, joka sisältäisi jokaisen koiran annoksen alkaen Selmasta ja päättyen Pateen [2,3,5,1,3].

Jos jokaisen koiran annoskokoa kasvatettaisiin kahdella kanaherkulla, niin uudet annokset olisivat [4,5,7,3,5]. Jokaiseen annokseen voitaisiin siis yksitellen lisätä luku 2. On kuitenkin melkoinen työ lisätä yksitellen jokaiseen lukuun tuo sama kakkonen. Mietipä Pate, jos kyseessä olisikin 200 koiran kenneli. Siinä loppuisivat äkkiä tassut kesken kasvattaessa jokaisen koiran annoskokoa yksitellen.

Eikö olisi Pate mukavaa, jos voisimme käsitellä listalla olevien annosten kokoja ihan samalla tavalla kuin käsittelemme yhtä annosta. Jos voimme kasvattaa yhden koiran annoksen kokoa kahdella, niin miksemme päätyisi ajattelemaan, että ehkä jollain kumman konstilla voisimme kasvattaa jokaisen koiran annoskokoa kahdella. Sellainen kone tarvitsisi raaka-aineikseen yhtäältä listan eri koirien kanaherkkuannosmääristä ja toisaalta tiedon miten kukin kanaherkkuannos muuttuu.”

”No todellakin se olisi viisasta”, jatkaa Pate uteliaana. ”Palan jo halusta nähdä, miten se map-kone toimii. Näyttäisitkö miltä se näyttää?”

”Totta kai näytän”, vastaa Selma ja nyökkää ystävällisesti. ”Näin se toimii, katsohan kuvaa ja ohjelmakoodia.”

> annosLista = [2,3,5,1,3]

> uudetAnnoksetLista = map (+2) annosLista

> uudetAnnoksetLista

=> [4,5,7,3,5]

”No tuo on todella edistyksellistä”, sanoo Pate. ”+2 näyttää olevan se pariaate, jolla kanaherkkuannosmäärää muutetaan. Jokaisen koiran annokseen lisätään kaksi. Samalla periaatteellahan voisimme nyt myös pienentää herkkuannoksia, jos kennelin koirat päättäisivät yhtenä laumana juosta vaikkapa jänisten perässä ilman lupaa.”

”Hyvin olet läksysi tehnyt, aivan hyvin voisimme pienentää kaikkien kennelin koirien kanaherkkuannoksia kahdella näin.

 

> annosLista = [2,3,5,1,3]

> uudetAnnoksetLista = map (-2) annosLista

> uudetAnnoksetLista

=> [0,1,3,-1,1]

Huomasit myös Pate varmaan, kuinka listan neljännen koiran kanaherkkuannosta ilmaiseva luku on nyt negatiivinen -1. Tällainen tilanne ei tietenkään ole hyvä, sillä eihän kanaherkkuja voi olla nollaa vähempää. Emme kuitenkaan tässä vaiheessa tartu ongelmaan sen enempää. On silti hyvä huomata että ohjelmassamme on parantamisen varaa.”

”Kiitos taas hyvistä opeista Selma”, vastaa Pate. ”Koska nälkä kasvaa koiran syödessä, niin haluaisin laatia sellainen koneen, jonka avulla hyvästä käytöksestä saisi enemmän kanaherkkuja ja huonosta käytöksestä vähemmän kanaherkkuja. Kone voisi toimia vaikka niin, että jos viikossa olisi juossut useammin kuin kaksi kertaa jäniksen perään ilman lupaa, niin kanaherkkuannos pienenisi yhdellä. Olisiko sellaisen koneen rakentaminen näillä tiedoilla mahdollista ja millainen kone siitä Selma oikein tulisi?”

”No sinähän olet oikein innoissasi näistä koneista enkä yhtään ihmettele, sillä näillä koneillahan voi tehdä mitä vaan. Ensin tarvittaisiin tietysti tieto siitä, kuinka monta kertaa kukin koira on jäniksen perään viikon aikana juossut:

Selma, 4 kertaa
Topi, 2 kertaa
Repa, 2 kertaa
Motti, 9 kertaa
Pate, 2 kertaa.

Karkailukertojen lukumääriä voitaisiin myös kuvata listalla [4,2,2,9,2].

Nyt meillä on jäljellä yksinkertainen tehtävä. Kanaherkkuannosta pitää pienentää, jos koira on juossut jäniksen perään useammin kuin kaksi kertaa viikon aikana. Logiikka, joka aiemmin oli yksinkertainen (lisäsimme kanaherkkuannokseen 2 tai vähensimme siitä 2) on nyt hieman mutkikkaampi, mutta ei mahdoton tehtävä innokkaalle vehnäterrierille.

Ongelma ratkeaa helposti niin, että luomme pareja joissa parin ensimmäisenä alkiona(fst eli first) on koiran nykyinen viikkoannoskoko ja toisena alkiona(snd eli second) on koiran karkailujen lukumäärä. Koska jokaisella koiralle tarvitsemme yhden tällaisen parin, tarvitsemme parien listan, sillä koiria on 5.

Parilistan voi luoda myös käsin, mutta zip-konetta käyttäen saamme listan luotua helposti. zip-koneelle annamme ensimmäiseksi raaka-aineeksi kanaherkkuannoskokolistan ja toiseksi raaka-aineeksi karkailumäärälistan. Lopputuloksena saamme listan, jossa on pareja, joiden ensimmäisenä alkiona on kanaherkkuannoskoko ja toisena alkiona karkailumäärä.

> zip [2,3,5,1,3] [4,2,2,9,2]

=> [(2,4),(3,2),(5,2),(1,9),(3,2)]

Mutta millainen on kone, jonka avulla saadaan luotua uusi kanaherkkuannoslista, jossa tiettyjen koirien kanaherkkuannosta on pienennetty karkailumäärän perusteella?  

Kuten muistamme map-kone käy läpi listan ja tuottaa uuden listan. map-koneemme ottaa käsittelyyn listan ensimmäisen alkion (2,4). Jos listan alkiota(tässä tapauksessa pari) kuvataan kirjaimella x, niin parin jälkimmäistä alkiota voidaan kuvata ilmaisulla snd x. Snd on siis kone, jonka avulla saadaan parin toisen alkion arvo. fst on kone, jonka avulla saadaan parin ensimmäisen alkion arvo. Ensin tutkimme, onko parin jälkimmäinen alkio suurempi kuin 2. Kun yhdistämme tietomme siitä mitä vahtien avulla voi tehdä, saamme aikaan seuraavanlaisen koneen:

sääntö x          -- x on yksi pari! 
  |snd x>2 = fst x-1
  |otherwise = fst x

Sääntö -kone saa raaka-aineena parin. Jos parin toinen alkio(karkailumäärä) on suurempi kuin 2, koneen tuotos on parin ensimmäisen alkion(herkkuannoksen) arvo vähennettynä yhdellä eli fst x-1. Jos karkailumäärä on kaksi tai pienempi(otherwise) koneen tuotoksena on fst x, mikä on koiran nykyinen herkkuannos. Katsotaanpa Pate millaisen kuvan ja ohjelmakoodin tästä kaikesta saamme aikaiseksi.”

annokset=[2,3,5,1,3]
karkailut=[4,2,2,9,2]
annoksetkarkailut = zip annokset karkailut 
sääntö x
 |snd x>2 = fst x-1
 |otherwise = fst x

uudetAnnokset = map sääntö annoksetkarkailut

”Nyt kun Pate käynnistämme uudetAnnokset-koneen saamme tietää millaiset uudet kanaherkkuannokset kullakin koiralla on. Sovellamme siis sääntöä annoksetkarkailut-listan alkioihin.

> uudetAnnokset

[1,3,5,0,3]

”Jos nyt palautamme mieleen koirien järjestyksen listalla saamme seuraavan taulukon.

Selma, 1 annosta
Topi, 3 annosta
Repa, 5 annosta
Motti, 0 annosta
Pate, 3 annosta.

Näyttäisi siis siltä, että Selman ja Motin annoskokoja on pienennetty, koska he ovat hölmöilleet. Tarkastelkaamme Pate vielä tekemäämme kokonaisuutta kuvan avulla. Lisäsin kuvaan selvyyden vuoksi koirien nimet, jotta näemme raaka-aineena koneeseen syötetyn listan ja koneen tuottaman kanaherkkuannoslistan selkeämmin. Koska on niin, että zip-koneen tuotos annostellaan map-koneeseen raaka-aineena, niin voimme hyvin esittää koko ohjelman yhdellä kuvalla.

Vasemmalla puolella kuvaa näet zip-koneen jonka tuotos annostellaan map-koneeseen. Oikealla puolella oleva kuva tarkoittaa samaa kuin vasemmalla puolella oleva kuva, mutta raaka-aineet ja tuotokset on kuvattu nimillä arvojen sijaan.

Jokaiselle map-koneen työstämälle parille sovelletaan sääntö -konetta. Sääntö -koneen viisaus on siinä, että se osaa raaka-aineena saamansa parin perusteella päätellä mitä tehdä tietyn koiran kanaherkkuannokselle. Siinä, missä filter-kone luo listan, joka on tyhjä tai siinä on enintään yhtä monta alkiota kuin raaka-aineena olevassa listassa, map-kone luo uuden listan, jossa on aina yhtä monta alkioita kuin raaka-aineena käytetyssä listassa.”

”Onpa melkoinen kone tuo map-kone”, sanoo Pate. ”Mutta emme kuitenkaan ole vielä ratkaisseet edellisessä luvussa esittelemäämme ongelmaa, jossa voisimme map-koneen avulla luoda koneen, jonka avulla voisimme saada jakauman herkkupussin sisällöstä niin, että eri herkkujen lukumääriä laskevia koneita ei tarvitsisi määritellä erikseen. Se olisi minun mielestäni todellista automaattista tietojenkäsittelyä sanan varsinaisessa merkityksessä. Kerrohan Selma, miten sellainen kone tehtäisiin?”

”Kerronhan toki. Se on todella yksinkertaista. Sovellamme ensin herkkuListaa nub -koneeseen, jolloin saamme listan, jossa jokainen herkku esiintyy vain kerran. Sen jälkeen sovellamme herkkuJoukko -listaa map-koneeseen. map-kone saa listan lisäksi raaka-aineeksi koneen, joka käy läpi herkkuJoukko listan alkioita. Jokaista herkkuJoukko -listan alkiota kohden selvitetään kuinka monta kertaa kyseinen alkio esiintyy herkkuLista -listalla.

Meidän on nyt vaan keksittävä, miten tuo map-koneelle annosteltava kone on laadittava, jotta ongelma ratkeaa. Koneen idea on, että se saa raaka-aineena herkun nimen ja selvittää nimen perusteella kuinka monta kertaa nimi esiintyy herkkuListalla. Kone tuottaa nimen esiintymislukumäärän herkkuListalla. Kone voitaisiin nimetä vaikkapa laskuri -koneeksi ja se voitaisiin toteuttaa näin. Herkun nimeä kuvaa x:

laskuri x=length(filter(==x)herkkuLista))

Nyt kun laskuri -kone on laadittu, voimme kirjoittaa lopun ohjelmakoodin, joka käyttää laskuri -konetta:

herkkuLista = ["kanaherkku","kanaherkku","koiransuklaanappi", "herkkuluu"]

herkkuJoukko=nub herkkuLista

herkkujenEsiintymiskerratPussissa=map laskuri herkkuJoukko

> herkkujenEsiintymiskerratPussissa

=> [2,1,1]

Katsotaanpa Pate vielä vaiheittain, miten juttu etenee. herkkuLista on ["kanaherkku","kanaherkku","koiransuklaanappi","herkkuluu"]. Herkkujen joukko luodaan siis nub -koneella annostelemalla siihen kaikki herkut ja tuotoksena on siis ["kanaherkku","koiransuklaanappi","herkkuluu"].

Lopuksi annostelemme herkkuJoukon map-koneeseen ja annamme laskuri -koneen selvittää kuinka monta kertaa tietty herkku herkkuListalla esiintyy. Näin saamme tietää eri herkkujen määrän pussissa niin, että ohjelmakoodimme pysyy lyhyenä ja napakkana. Jos herkkupussiin lisätään uusia herkkuja, ohjelmakoodissa esiintyvät koneet osaavat ottaa uudet herkut huomioon, sillä nub -kone tuottaa listan, joka sisältää kaikki herkkuListan erilaiset alkiot.

Tiesitkö muuten Pate, että map ja filter-koneet ovat kenties yleisimmin käytettyjä funktionaalisen ohjelmoinnin koneita ja niitä käytetään monissa muissakin ohjelmointikielissä kuin Haskellissa.

map ja filter-koneiden toiminta voidaan kuitenkin korvata fold-koneella. Eräs versio Haskell-kielen fold-koneesta  tunnetaan yleisesti myös nimellä reduce. fold-kone on tällä erää viimeisin laaja esimerkkimme. fold-koneen käyttämisessä on kuitenkin sen verran pohdittavaa, että käymme asian läpi seuraavassa luvussa. Nyt on aika unohtaa koneet hetkeksi ja syödä pari kanaherkkua palkkioksi ahkerasta opiskelusta.”

”Sopii minulle”, sanoo Pate ja syö jo ensimmäistä kanaherkkuaan.

 

10. Fold-kone 

”Tiesitkö Pate, että nykyaikainen tietojenkäsittely on muutakin kuin kanaherkkuannosten koon muuttamista koirien käyttäytymisen perusteella. Usein tiedosta halutaan saada aikaan myös erilaisia yhteenvetoja. Yksi esimerkki tällaisista yhteenvedoista on jakauma, joka saadaan eri herkkujen esiintymistiheyksistä herkkuListalla. Herkkulista sisältää kaiken tiedon, mikä jakauman esittämiseen tarvitaan. Meidän työkalupakistamme kuitenkin puuttuu vielä yksi kone, jonka avulla on helppo luoda kaikenlaisia yhteenvetoja. Sen koneen nimi on fold. Tutkimme miten map ja filter-koneen yhteistoiminta voidaan korvata kätevästi yhdellä koneella, foldilla.”   

”Vai että fold-kone. Mahtaa olla melkoinen kone, jos sillä niin helposti yhteenvetojakin saa aikaiseksi ja varsinkin jos sillä voi korvata kaksi konetta”, jatkaa Pate.

”Niin. Siinäpä meillä onkin miettimistä”, toteaa Selma. ”Ajatellaanpa, että eurasier ystäväsi Topi on juuri saanut seuraavanlaisen todistuksen koirakoulun ensimmäiseltä luokalta. Todistuksessa arvioidut taidot jaetaan kolmeen luokkaan: metsästys, pihatyöt ja muut taidot.

 

Metsästys

Pupun jäljestys 9

Hirven jäljestys  8

Linnun noutaminen 8

Pihatyöt

Lumen pöllyytys 10

Kukkapenkkien kaivaminen 10


Muut

Parvekkeen vahtiminen 9

Piilotetun luun löytäminen 7

Oman hännän jahtaaminen 9

Kuun ulvonta 8           

 

Jotta taitojen yhteispistemääriä voitaisiin verrata luokkakohtaisesti, ne pitää tietenkin laskea ensin yhteen. Mutta mitä tietoa meillä pitäisi olla, jotta tämä olisi mahdollista? Mitä Pate tuumaat?”, jatkaa Selma.

”No ainakin meidän pitäisi tietää, mitä taitoja eri ryhmiin kuuluu, eli meillä pitäisi olla jonkinlainen luokitus. Tässä voisimme varmaan käyttää pareja. Parin ensimmäinen alkio voisi tarkoittaa taitoa ja toinen alkio luokkaa, johon alkio kuuluu. Pari-tyyppisestä tietorakenteesta käytetään myös nimitystä monikko(tuple), koska se sisältää aina 2 tai useampia arvoja. Voisiko luokituksen tehdä näin?”, kysyy Pate ja näyttää ohjelmakoodia.

luokitus=[("Pupun jäljestys","Metsästys"),

          ("Hirven jäljestys","Metsästys"),

          ("Linnun noutaminen","Metsästys"),

          ("Lumen pöllyytys","Pihatyöt"),

          ("Kukkapenkkien kaivaminen","Pihatyöt"),

          ("Parvekkeen vahtiminen", "Muut"),

          ("Piilotetun luun löytäminen","Muut"),

          ("Oman hännän jahtaaminen","Muut"),

          ("Kuun ulvonta","Muut")]

”Kyllä vaan voi”, vastaa Selma. ”Siinähän ne ovat! Nyt meidän vielä pitäisi esittää koirakoulun todistus niin, että siinä ovat vain arvosanat eri taidoista. Osaisitko Pate kirjoittaa sellaisen ohjelmakoodi?”.

”No aina voi yrittää, ja näin minä sen todistuksen muotoilin ohjelmaksi”, vastaa Pate.

topinTodistus'=   [("Pupun jäljestys",9),

                    ("Hirven jäljestys",8),

                    ("Linnun noutaminen",8),

                    ("Lumen pöllyytys",10),

                    ("Kukkapenkkien kaivaminen",10),

                    ("Parvekkeen vahtiminen",9),

                    ("Piilotetun luun löytäminen",7),

                    ("Oman hännän jahtaaminen",9),

                    ("Kuun ulvonta",8)]

”Tuohan näyttää tosi hyvältä”, sanoo Selma. ”Nyt meidän pitää vain luoda ohjelma, jonka avulla saamme laskettua yhteen todistuksen arvosanat luokittain. Siihen voimme avuksi luoda pienen oman tietotyyppimme. Me emme tässä yhteydessä käsittele tietotyyppejä sen seikkaperäisemmin, mutta kerrottakoon, että niiden avulla voimme tehdä kaikenlaisia hauskoja temppuja. Myös sellaisia temppuja, joita ei koirien agility radoilla ole kuunaan tullut vastaan. Lisätäänpä Pate soppaan tällainen ohjelmarivi.

data LuokkienSummat = LuokkienSummat {metsästys::Int, pihatyöt::Int, muut::Int}

Kaikki oleellinen on nyt määritelty Pate, nyt meidän tarvitsee vain miettiä, millaisia koneita tarvitsisimme, jotta saisimme arvosanat luokiteltua ja laskettua eri luokkiin kuuluvien arvosanojen summat. Mutta koska olet Pate ensimmäistä kertaa tällaista ongelmaa ratkaisemassa, niin kerron sinulla, miten ongelmaa voisi lähestyä.”, jatkaa Selma.

”No jos vielä tämän tehtävän jaksan kuunnella ja pohtia, niin lupaatko Selma, että syödään sen jälkeen kunnon kanafileet?”, kysyy Pate.

”Kyllä vaan syödään”, vastaa Selma topakasti. ”Näin minä sen ongelman ratkaisisin”, sanoo Selma.

summatAiheittain' = foldr (\x acc->case (luokittele (fst x) of

     "Metsästys" -> acc {metsästys=(metsästys acc)+snd x}

     "Pihatyöt" -> acc {pihatyöt=(pihatyöt acc)+snd x}

     "Muut" -> acc {muut=(muut acc)+snd x})

     LuokkienSummat {metsästys=0, pihatyöt=0, muut=0} topinTodistus'

”Tässä ohjelmakoodissa on nyt joitain uusia asioita, kuten omia tietotyyppejä ja hiukan muutakin, mutta me koirat emme pikkuseikkoihin takerru, vaan selvitämme isot linjat ensin. summatAiheittain'-koneen idea on, että se käyttää foldr-funktiota. foldr-on funktio, jolle annostellaan kolme asiaa.

Ensinnäkin annostelemme koneelle Topin todistuksen ilman luokitusta, se on viimeinen annosteltavista asioista ja sitä kuvaa nimi topinTodistus'. Sitä ennen annostelemme foldr-koneeseen oman tietotyyppimme arvon {metsästys=0, pihatyöt=0, muut=0}. Tuossa rimpsussa annamme jokaiselle luokalle alkuarvoksi nollan. LuokkienSummat nimi kertoo, millaisen tietotyypin arvon luomme – ja meidän tietotyyppimme nimihän on LuokkienSummat.

Mutta mitä ihmettä tuo kone sitten oikein tekee, kun annosteltuja arvoja aletaan käsitellä? Kone alkaa käydä läpi topinTodistus'-listaa ja ottaa käsittelyyn ensimmäisen alkion. Kone tutkii ensimmäisen parin taitoa koskevaa alkiota. Muistathan, että arvosanat määriteltiin listalla olevina (taito, arvosana) pareina. Jos taito on metsästys, lisäämme oman tietotyyppimme metsästys-tietokenttään todistuksessa olevan arvon. topinTodistus'-listan alkion arvosanan saamme ottamalla parin toisen alkion snd-koneella, johon viittaa snd x.  

Tämä onnistuu, sillä acc nimi pitää sisällään arvon, joka viittaa LuokkienSummat-tyyppiin, joka sisältää kolme kokonaislukutyyppistä tietokenttää. Nämä kolme kokonaislukutyyppistä tietokenttää kuvaavat niitä luokkia, joihin Topin todistuksen pisteet luokitellaan.  

acc {metsästys=(metsästys acc)+snd x}

Acc tarkoittaa koneen toiminnan aikana kerrytettävää tietoa (sisältää oman tietotyyppimme, jonka sisältämät arvot alustimme nolliksi), josta topinTodistus' listaa läpikäydessä luodaan aina uusi versio sen perusteella, minkä luokan pistemäärään todistuksesta löytyvä pistemäärä pitää kulloinkin lisätä. Kone käy läpi topinTodistus'-listan ensimmäisestä alkiosta viimeiseen ja lopulta kone tuottaa LuokkienSummat-tyyppisen arvon, joka sisältää arvosanojen summat luokittain.

Mutta jotta kone toimisi oikein, sen tulee tavalla tai toisella löytää topinTodistus'-listan monikkoalkion taitoa vastaavaa luokka, ennen kuin luokittelu on mahdollista. Tätä varten loin pienen apukoneen luokittele, jolle annostellaan raaka-aineena taito, ja kone tuottaa sitä vastaavana luokan. Näin luokittele-konetta käytetään summatAiheittain'-koneessamme.

(\x acc->case (luokittele (fst x)…

Koska x on pari ja kuvaa käsittelyssä olevaa topinTodistus'-listan alkiota, taito saadaan ottamalla x:stä ensimmäinen alkio käyttämällä fst-konetta. Sen jälkeen taito annostellaan luokittele-koneeseen, joka näyttää tältä:

luokittele taito = snd(head(filter(\z-> (fst z)==taito) luokitus))

Se on yksinkertainen kone, jolle annostellaan taito ja kone kertoo, mihin luokkaan kyseinen taito kuuluu. Koneeseen annostellaan luokitus, joka sisältää tiedon siitä, mihin luokkaan mikin taito kuuluu. Lisäksi koneeseen annostellaan kone, jonka avulla poimitaan juuri tiettyä taitoa koskeva luokka luokituksesta (filter(\z-> (fst z)==taito). Koska filter-kone tuottaa aina listan, on meidän otettava tuotetusta listasta ensimmäinen alkio head-koneella. Tämän jälkeen nappaamme vielä parin toisen alkion snd-koneella ja näin meillä on siis käsissämme luokka, mihin koneeseen annosteltu taito kuuluu. Kun kirjoitamme komentoriville:

> summatAiheittain'

Saamme vastaukseksi:

=> LuokkienSummat {metsästys = 25, pihatyöt = 20, muut = 33}

Näimme Pate useassa kohtaa merkintätavan (\z->... , jossa z:n paikalla saattoi olla muutakin. Kysymys on siitä, että määrittelemme koneen ilman nimeä, siinä paikassa, missä konetta tarvitaan. Ohjelmointitermein funktio määriteltiin lambda-lausekkeena. Tämä viimeinen fold-konetta koskeva luku oli ilman muuta luvuista haastavin ja sinähän Pate voit palata aiheeseen ja vaikkapa käyttää hakukoneita ja alkaa etsiä itse tietoa asioista, nyt kun tunnistat peruskäsitteitä.

Lambdojen ja monien muiden funktionaalisen ohjelmointikielen käsitteiden ymmärtämisestä ja soveltamisesta on paljon iloa paitsi Haskell-kielellä ohjelmoidessa myös monilla muilla ohjelmointikielillä työskennellessä. Funktionaalisia piirteitä sisältävät monet nykyaikaiset ohjelmointikielet, kuten JavaScript (ECMA Script), TypeScript, Python, Java, Kotlin, Scala, Ruby, PHP,  ja C++ ja monet muut.

Lienee Pate sanomattakin selvää, että funktionaalisen ohjelmoinnin perusjuttujen opetteleminen tukee hyvin myös muiden funktionaalisten kielten opiskelua. Sellaisia kieliä ovat esimerkiksi Elm, Racket, Clojure, ML ja Idris.

Nyt on Pate kanaherkkujen aika. Eiköhän mennä pyytämään emännältä yhdet sellaiset oikein isot ja yliherkulliset kanaherkut, koska emmehän me koirat pelkästä ohjelmoinnista elä, emmehän Pate?”

”No emme totisesti elä”, vastaa Pate. ”Ei kun kanaherkuille, mars”, säestää Pate.

Niin painui koirakaverusten päivä illaksi ja molemmat nukahtivat kanaherkun makuisiin uniinsa pohtien voisiko jonkin ohjelmointikielen kanaherkkuja sisältävä lista olla äärettömän pitkä.

 

Lähteet

Haskell-kieltä käsittelevä verkkosivusto
https://www.haskell.org/

Lukusuosituksia

Haskell-kieleen tutustuvalle

Lipovača, Miran  2011: Learn You a Haskell for Great Good!, https://www.learnyouahaskell.com

Haskell-kieltä opiskelevalle

Hallgren, Thomas 2019: Introduction to Functional Programming Course Material, http://www.cse.chalmers.se/edu/course/TDA555/

Haskell-kielen teoreettisesta viitekehyksestä kiinnostuneille

Milewski, Bartosz 2014: Category theory for programmers, https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface

Videoita

Koiramaisia videoita, joissa tehdään pieniä Haskell-kielisiä ohjelmia: https://www.youtube.com/watch?v=zUxXkrSEx0Y&list=PLaPZ0rDCxLreHHJAIqkUQbdcidaksxqMO