maanantai 26. tammikuuta 2009

Luento 5 - Aliohjelmat

Luennon aluksi esitettiin väittämä, että eri ohjelmointikielellä tehdyt ohjelmat sisältävät saman määrän bugeja suhteutettuna tuotettujen koodirivien määrään. Väittämän mukaan toisin sanoen esimerkiksi C:llä kirjoitettu, ilmaisultaan sanarikkaampi koodi sisältäisi saman määrän bugeja kuin vaikka Pythonilla kirjoitettu korkean tason koodi, jossa ei oteta kantaa esimerkiksi muistinhallintaan lainkaan. Sinänsä väittämä tuntuu intuitiivisesti todelta.

Huomaa, että väittämä ei sano mitään bugien vakavuudesta tai niiden korjattavuudesta. Se koskee vain bugien määrää. Uskonkin, että korkeamman tason kielellä kirjoitettu koodi onkin helpommin korjattavissa kuin alemman tason kielellä kirjoitettu. Toisaalta tehdyt virheet eivät välttämättä ole yhtä vakavia kuin alemman tason kielen tapauksessa. Tämä johtuu siitä, että riskialttiisiin seikkoihin on jo otettu kehittäjän puolesta kantaa. En sano etteikö matalan tason kieliä kannattaisi käyttää, jos käyttö on perusteltua. Totean vain, että korkean tason kielten tapauksessa virheet ovat erityyppisiä ja uskoakseni helpommin sekä nopeammin korjattavissa.

Luennon alussa oli mainintaa myös kirjastoista ja niiden käytöistä. Mielenkiintoisin huomio oli se, että ei ole mielekästä kirjoittaa kirjastoa vain sen takia, että se voidaan kirjoittaa. Kirjastolla tulee olla selvä käyttötarkoitus. Muulloin työ on saattanut olla jossain määrin turhaa. Toisaalta käytetyn kirjaston eri käyttäjäryhmät saattavat esittää keskenään jopa ristiriitaisia vaatimuksia. Tämä voikin olla eräs syy siihen, miksi kirjastojen käyttämistä sellaisenaan saatetaan karsastaa. Parhaimmillaan kirjaston käyttäminen on synergistinen prosessi, jossa sekä sen käyttäjä että kehittäjä hyötyvät. Kehittäjät saavat palautetta työstään. Toisaalta käyttäjät saavat tarvitsemiaan parannuksia. Ainakin näin karkeasti ajatellen. Käytännössä tilanne on paljon monitahoisempi.

Luennon varsinainen aiheena olivat aliohjelmat. Miksi aliohjelmia ylipäänsä tarvitaan? Luonteva vastaus lienee toiston poisto. Sen sijaan, että koodinpätkää kopioisi joka paikkaan, voi olla mielekästä määritellä se aliohjelmana. Tätä aliohjelmaa voitaisiin sitten kutsua eri paikoista koodia aina tarpeen mukaan. Kutsu tehdään hyödyntäen apuna itse aliohjelman määrittelyä. Määrittelyssä voidaan kertoa mitä parametreja aliohjelmalle annetaan. Toisaalta määrittelystä saatetaan nähdä aliohjelman palautustyyppi.

Ei ole mitenkään itsestään selvää millainen aliohjelman määrittelyn tulisi olla. Joissain kielissä määrittely voi olla erittäin vapaa. Esimerkiksi palautustyyppiä ei välttämättä tarvitse määritellä ollenkaan vaan se määräytyy implisiittisesti aina palautetun tyypin mukaan. Näin siis dynaamisissa kielissä.

On myös mahdollista, että parametreja ei rajata ollenkaan. Aliohjelma voidaan siis toisin sanoen antaa mitä parametreja ikinä halutaan. Missä tapauksessa tällaisesta vapaasta määrittelystä voisi olla sitten hyötyä? Itse olen käyttänyt kyseistä tapaa esimerkiksi dynaamisten hakujen suorittamiseen. Ajatuksena on, että aliohjelma saa nimetyn parametrin (esim. joukko.tee_haku(nimi='Jeppe')), joka kertoo haetun attribuutin nimen sekä siihen liittyvän haetun arvon. Hieman lisää miettimällä löytää varmasti muitakin käteviä esimerkkejä. Mielestäni on kuitenkin oleellista ymmärtää, että aliohjelman konkreettinen määritelmä voi olla hyvinkin erilainen aina kielestä riippuen.

Luennolla esiteltiin, kuinka aliohjelmat voivat toimia muistinhallinnan suhteen. Oleellisena esille nousi stack framen (pinokehys?) käsite. Tavallisesti aliohjelmakutsujen voidaan ajatella luovan uusi pinokehys kutakin kutsua varten. Kehys sisältää viitteet kutsujaan, parametreihin, palautusarvoihin sekä paikallisiin muuttujiin. Käsittääkseni tämä kutsu tunnetaan myös jatkeen nimellä (ts. aliohjelmien kutsupinoon lisätään uusi kehys). On olemassa tapaus, jossa aliohjelmakutsua varten ei tarvitse luoda uutta kehystä.

Kyseessä on häntäkutsun poisto. Toisin sanoen jos aliohjelmaa itseään kutsutaan sopivasti, voidaan sen parametrit syöttää sen nykyiseen pinokehykseen. Tällä tavoin suoritus pysyy paikallaan muistiavaruudessa eikä ylimääräistä pinomuistia tarvita. Luennolla osoittautui lisäksi, että tämänkaltainen rekursio vastaa while-luuppia, mikä sinänsä on varsin luontevaa. On huomattava, että kyseinen optimointi tulee toteuttaa ohjelmointikielen tasolla eikä tule olettaa, että se toimisi kaikissa kielissä. Häntäkutsun poiston ja while-ekvivalenssin ansiosta rekursio voi parhaimmillaan olla yhtä tehokas ratkaisu, mikä saattoi olla kenties hieman yllättävää.

Luennolla mainittiin myös sulkeumista (closure), jotka olivatkin käsitteenä jo tuttu. Perusajatuksena on, että aliohjelmien sisälle voidaan määritellä uusia aliohjelmia. Tavallisesti nämä sisäänmääritellyt aliohjelmat näkevät vanhempansa muuttujat, mistä sinänsä voi olla mielenkiintoista hyötyä joskus. Ohjelmointikielen toteutuksen kannalta tämä tarkoittaa, että aliohjelman sisällä olevaa aliohjelmaa kutsuttaessa sille tulee attribuuttien (huom! attribuutit mappautuvat aliohjelman parametreihin kutsussa) lisäksi viedä viite sen vanhempialiohjelmaan, jotta aliohjelman aliohjelma pääsee käsiksi sen muuttujiin edellä mainitulla tavalla. Tämä tehdään siis ohjelmoijalta piilossa.

Parametreihin liittyen esitettiin erityisiä välitysmuotoja, jotka olivat in (luku), out (kirjoitus) sekä inout (luku/kirjoitus). Näistä in-tyyppiset ovat varmasti tutuimpia. Ne ovat juurikin niitä parametreja, jotka aliohjelmalle annetaan lähtöarvoina, joiden mukaan operoida. out-vaihtoehtoa voidaan pitää jotakuinkin tuloarvon tai aliohjelman palautusarvon eräänlaisena vastineena ja inoutia edellä mainittujen yhdistelmänä. Lisäksi esitettiin parametrinvälitysmekanismeja, joista erityisesti otti silmään call-by-name, joka muistutti jossain määrin template method (mallinemetodi???) suunnittelumallista, joka on oikein pätevä yleiskäyttöistä koodia kirjoittaessa. call-by-namessa ajatuksena on, että parametrin nimi korvataan kaikkialla aliohjelman sisällä annetulla lausekkeella (esim. a:n sijalle voidaan sijoittaa x=x+1). Template methodia käyttäen tekee helposti esim. listan järjestelijän (vertailija on aliohjelma, joka annetaan järjestäjä aliohjelmalle). Sama idea saattaisi toimia jopa call-by-namen tapauksessa.

Luennon lopuksi esiteltiin vielä lyhyesti vuorottaisrutiinit (coroutines), jotka olivatkin jo ennalta tuttuja Luasta. Myös Pythonin samantyyliset generaattorit ovat tuttuja. Pohjimmiltaan ajatus on se, että aliohjelma muistaa tilansa tai tarkemmin viimeisimmän suorituskohtansa ennen siitä poistumista. Vuorottaisrutiineista on erityistä hyötyä toteuttaessa esimerkiksi tuottaja-kuluttaja -malliin pohjautuvia algoritmeja. Hyvä esimerkki tästä löytyy osoitteesta http://www.lua.org/pil/9.2.html.

Itse näen aliohjelmat erityisesti abstraktion apuvälineenä. Hyvin nimetyt ja oikein koostetut aliohjelmat auttavat ymmärtämään, mistä koodissa on kyse. Logo-kielessä (Turtle graphics!) aliohjelman merkityksestä kertoo niiden määrittely "TO" sanan avulla. Ts. aliohjelma voidaan kuvata tyyliin "TO BAKEACAKE", joka jostain kumman syystä tuntuu luontevammalta kuin esim. Pythonin "def bake_a_cake()". Itse aliohjelman kuvaus on sitten vain "ruokaohje", mitä sen hyvinkin pitkälle aina jossain määrin tulisi olla.

Aliohjelmien merkitystä ohjelmointikielten kehityksessä ei tule väheksyä. Luento auttoi ymmärtämään hieman paremmin, mistä niissä on pohjimmiltaan kysymys ja miten niitä ainakin teoriassa voitaisiin varioida.

2 kommenttia:

  1. Kurssin loppuvaiheessa suunnittelemassani Meatloaf-kielessä (kts. http://joppimispaivakirja.blogspot.com/2009/03/demo-8-kommentit.html) päätin testata aliohjelman käsitteen joustavuutta.

    Olennaisin löydös olikin lienee, että aliohjelman ydin, kapselointi, soveltuu myös luokkien toteuttamiseen. Aliohjelma voidaan siis nähdä sekä kutsuttavana että instantioitavana kokonaisuutena. Tämä semanttinen ero voidaan päätellä kutsusta esim. seuraavasti (huom! karsin hieman syntaksia yllä mainitusta Meatloafista):
    sum
    a, b = 0, 0
    begin
    return a + b
    end

    sum(a, b) # kutsuu summaa
    sum_instance = sum # luo olion
    sum_instance() # kutsuu summaa

    Näinhän se toimii esim. Pythonissa. Tosin Python erottelee luokkien käsitteen selvästi omakseen.

    VastaaPoista