maanantai 5. joulukuuta 2016

Tietokone, osa 7: Funktiot

Perjantain ohjelmointitekstissä esitettiin itseisarvofunktion toteutus useammalla ohjelmointikielellä. Funktion määritelmä puolestaan jäi vielä vajaaksi, muuten kuin että se muistuttaa matemaattista vastinettaan. Matemaattinen määritelmä on seuraava:

Funktio on sääntö, joka liittää jokaiseen määrittelyjoukon $A$ alkioon täsmälleen yhden maalijoukon $B$ alkion.

Toisin sanoen funktio ottaa vastaan joitakin parametreja ja antaa niiden pohjalta tuloksen. Muunnetaan tämä osaksi ohjelmaa.

Ohjelman paloittelua

Muistin virkistämiseksi itseisarvofunktio näytti Pascal-kielellä seuraavalta:

function Abs(x: Integer) : Integer
begin
    if x < 0 then
        x := -x;
    Abs := x;
end;

Tämä funktio ottaa yhden parametrin, kokonaisluvun x, ja sen tulos on niin ikään kokonaisluku. Toinen esimerkki funktiosta, niin ikään Pascalilla, voisi olla seuraava:

function Divide(a, b: Integer) : Real
begin
    Divide := a / b;
end;

Tämä funktio puolestaan ottaa syötteekseen kokonaisluvut a ja b sekä palauttaa näiden jakolaskusta syntyneen reaaliluvun (niistä lisää ensi kerralla). Kumpikin näistä esimerkeistä on tahallisen yksinkertainen. Funktioiden arvo on siinä, että niillä voidaan paloitella ohjelmaa pienemmiksi osiksi ja välttää osien toistamista. Lainatakseni vanhan matematiikankirjani esimerkkiä, ulko-oven avaaminen koostuu seuraavista operaatioista:

  1. Työnnä avain lukkoon.
  2. Käännä avainta puoli kierrosta myötäpäivään.
  3. Vedä ovi auki.
  4. Käännä avainta puoli kierrosta vastapäivään.
  5. Vedä avain lukosta.

Tämä voitaisiin jakaa yhdeksi funktioksi, joka liikuttaa esinettä ja toiseksi, joka kääntää avainta. Nämä kokoava funktio, joka ottaa syötteekseen avaimen ja oven, ja joka ei palauta mitään arvoa, voisi näyttää pseudo-Pascalilla tämäntyyppiseltä:

procedure AvaaOvi(avain: Avain; ovi: Ovi)
begin
    Liikuta(avain, poispäin);
    KäännäAvainta(avain, myötäpäivään);
    Liikuta(ovi, itseenpäin);
    KäännäAvainta(avain, vastapäivään);
    Liikuta(avain, itseenpäin);
end;

Erotuksena matemaattiseen funktioon tämä funktio ei palautakaan arvoa. Sen sijaan oven muuttuminen avatuksi on sivuvaikutus. Sivuvaikutukset ovat väistämättömiä vähänkään monimutkaisemmassa ohjelmassa, koska harvempi käyttää tietokonetta syöttämällä lähtötiedot, painamalla nappia ja odottamalla, että vastaus putkahtaa ulos tulostimesta. Toisaalta hallitsemattomat sivuvaikutukset hankaloittavat ohjelman suunnittelua: miten edellinen ohjelma toimii, jos oveen onkin kytketty hälytin?

Funktionaaliset ohjelmointikielet, joihin viimekertainen Scheme (se jossa on ((ihan liian) paljon) sulkeita) kuuluu, ratkaisevat ongelman sillä, että funktio palauttaakin arvoparin: uuden avaimen ja oven. Oven avaaminen luo rinnakkaistodellisuuden, jossa ovi on avattu, mutta muissa todellisuuksissa ovi pysyy kiinni.

Pino funktioita

Yksi tietorakenne pitää mahduttaa tähänkin sarjaan: pino. Nimestä ei liene kauhean vaikeaa päätellä sen toimintaa: siihen voi lisätä ja poistaa tavaraa, mutta ainoastaan pinon päältä. Pinoja käytetään osana monenlaisissa algoritmeissa, mutta tärkein niistä liittyy funktioihin.

Kun AvaaOvi-funktiomme suorittaa Liikuta-funktion, prosessorin täytyy keskittyä liikuttamiseen. Oven avaamisen ja kotiin menon pitää jäädä siksi aikaa taka-alalle. Aiemmin käytin suttupaperia vertauskuvana prosessorin lyhytmuistista. Nyt tehdäänkin niin, että jokainen funktio saa oman suttupaperinsa (niitä tietenkin kierrätetään ekologisina).

Esimerkiksi tietokone suorittaa funktiota MeneKotiin, jossa tulee ohjeeksi suorittaa funktio AvaaOvi. Prosessori kirjoittaa suttupaperille, mihin vaiheeseen jäi, ja laittaa sen pinon päällimmäiseksi (1). Nyt se aloittaa puhtaalta pöydältä funktion AvaaOvi kanssa, jossa heti ensimmäisenä vaiheena on käsky liikuttaa avainta. Prosessori kirjoittaa taas paperille, että seuraavaksi jatketaan vaiheesta 2, laittaa sen pinoon, ja siirtyy liikuttamaan avainta (2). Sen tehtyään se ottaa pinosta päällimmäisen paperin ja jatkaa sen mukaan toimimista (3).

Rekursioksi kutsutaan tilannetta, jossa funktio kutsuu itse itseään. Tällä menettelyllä voidaan toteuttaa joitakin asioita yllättävän tehokkaasti. Otetaan esimerkiksi lista lukuja, joiden summa pitäisi laskea. Ei-rekursiivinen tapa olisi edetä listassa yksi kerrallaan ja laskea lukuja yhteen sitä mukaa. Rekursiivinen tapa puolestaan on ottaa listan ensimmäinen luku ja lisätä siihen loppujen lukujen summa. Mistä se summa saadaan? Kutsumalla funktiota itseään listan loppuosalla! Ero on hienovarainen, mutta merkittävä edettäessä monimutkaisempiin algoritmeihin.

Kirjastoja ja käyttöjärjestelmiä

Osa funktioista on hyvin yleisesti käytettyjä. Jakolaskun ja itseisarvon kaltaiset asiat ovat prosessorin ymmärtämiä käskyjä, mutta miten vaikkapa keskiarvon ottaminen kahdesta luvusta? Olisi ajan haaskausta kirjoittaa perusfunktioita uudelleen jokaiseen projektiin. Tämän takia jokaisen ohjelmointikielen mukana tulee kokoelma funktioita. Funktioiden kokoelmaa kutsutaan kirjastoksi, ja ohjelmointikielen mukana tulevaa erityisesti standardikirjastoksi.

Erilaisia kirjastoja on valtavasti. Salakirjoitusfunktioita, fysiikan laskentaa, Twitterin käsittelyä, melkein mitä vain. Kirjastofunktiot ovat kuin Lego-palikoita; ohjelmoijan tehtäväksi jää koota niistä valmiita rakennelmia. Kirjastoiden tehtävänä on piilottaa ylimääräistä monimutkaisuutta.

Ehkä ultimaattisin kirjasto on tietokoneen käyttöjärjestelmä: se peittää tietokoneen tekniset yksityiskohdat ja tarjoaa kaikille sovelluksille yhteisiä ominaisuuksia. Piirustusohjelma näkee hiiren paikan ja osaa piirtää viivan — sen ei tarvitse miettiä, onko hiiri yhdistetty Bluetoothilla vai USB-johdolla, tai kannattaako viivan pisteet kopioida näytölle millä tavalla. Ja kun koneeseen kytketäänkin kosketusnäyttö, käyttöjärjestelmä muuntaa sormen liikkeet kuvitteelliseksi hiireksi.

No kuinkas ne kuvat sitten syntyvät? Siitä ensi kerralla.


Kokeiltavaksi ja pohdittavaksi

  • Kirjoita vaihe vaiheelta etenevä lista jostain arkisesta askareesta, vaikkapa hampaiden pesusta. Entä kuinka kukin vaihe toteutetaan? Entä niiden alivaiheet? Kuinka pieniksi osiksi saat pilkottua tehtävän, ennen kuin keksit parempaa tekemistä?
  • Onko olemassa tapausta, jossa esimerkkifunktio Divide ei toimikaan? Mitä tapahtuu tai pitäisi tapahtua?

Ei kommentteja:

Lähetä kommentti

Kommentit ovat moderoituja — yritän hyväksyä kommenttisi mahdollisimman pian. Voit kirjoittaa kommenttiin LaTeX-koodia tai yksinkertaista HTML-merkintää: lue lisää Kommentointi-sivulta.