maanantai 19. tammikuuta 2009

Luento 3 - suoraviivaohjelmat

Kolmannen luennon aiheena oli suoraviivaohjelmat. Luennolla jaetun monisteen mukaan suoraviivaohjelmat voidaan määritellä ohjelmiksi, jotka eivät sisällä silmukoita tai muita hyppyoperaatioita. Nähdäkseni luento meni hieman tämän otsikon ohi (poimin otsikon aina jaetusta materiaalista), joten seuraavaa luentomuistiinpanojen purku ei välttämättä osu aina täsmälleen aihepiiriin.

Luennolla esitettiin sanapareja, jotka liittyvät toisiinsa jollain oleellisella tavalla. Ensimmäinen esitelty pari oli itsellenikin hyvin läheinen staattinen vs. dynaaminen. Staattisina voidaan pitää sellaisia asioita, jotka tiedetään jo ennalta. Dynaamiset asiat määräytyvät jo tapahtuneiden asioiden perusteella.

Ohjelmointikielten kontekstissa voidaan nähdä selvä jako dynaamisten ja staattisten kielten välillä. Olennaiset erot näiden kielten välillä syntyvät esimerkiksi tavasta määritellä muuttujia. Staattisissa kielissä muuttujan tyyppi määritellään eksplisiittisesti. Dynaamisten kielten tapauksessa tämä määräytyy itse sijoituksen perusteella implisiittisesti. Toki halutessaan ohjelmoija voi testata muuttujan tyyppiä jälkikäteen, mikäli hän näkee sen tarpeelliseksi.

Olen samaa mieltä luennoitsijan kanssa siitä, ettei voida sanoa, että toinen tapa olisi toista parempi. Itse näen, että kummallakin tavalla saa aikaan asioita. Varsinaiset erot syntyvät nähdäkseni ajattelutavassa. Staattisissa kielissä asioita (esim. em. tyypit) pyritään rajoittamaan ennalta. Dynaamisten kielten tapauksessa rajoittaminen tapahtuu vasta ohjelmoijan niin halutessa.

Oman kokemukseni perusteella uskon, että hyvät ja kattavat testit ovat oiva tuki kehittämiselle erityisesti dynaamisia kieliä käytettäessä. Niistä on hyötyä toki myös staattisissa kielissä. Niiden suurin etu on se, että ne auttavat kehittäjää nimenomaan rajoittamisprosessissa. Ohjelmointihan on jossain määrin asioiden ymmärtämistä ja mallintamista mahdollisimman luontevalla tavalla. Selkeä, asian ymmärtämistä kuvastava ajattelutapa heijastuu suoraan lähdekoodiin.

Toinen oleellinen käsitelty pari oli denotationaalinen vs. operationaalinen. Operationaalinen tarkoittaa sitä, miten ohjelma käyttäytyy ajonaikana. Ohjelmointikieli saa merkityksen sitä kautta, miten se ohjaa tietokoneen käyttäytymistä. Denotationaalinen sen sijaan tarkoittaa, että itse ohjelmointikieli on olemassa riippumatta tietokoneen olemassaolosta!

Voidaan siis puhua eräänlaisesta tietokoneriippumattomasta tavasta ajatella ohjelmointia. Oleellista on myös, että denotalionaalisuus kuvaa käytettyjen käsitteiden, esimerkiksi neliöjuuri, merkityksiä. Voisi kuvitella, että tämänlaisesta tavasta ajatella ohjelmointikieltä olisikin hyötyä esimerkiksi matemaatikoille tai kielten suunnittelijoille.

Kielten suunnittelijat käyttävätkin seuraavan parin toista osaa, metakieltä. Metakielen parina esiteltiin kohdekieli. Metakieli tarkoittaa suoraan käännettynä "kielen kieltä". Toisin sanoen metakielen avulla kuvataan konkreettinen kieli, kohdekieli. Metakielenä voidaan käyttää esimerkiksi täsmällistä englantia.

Itse olen käyttänyt jossain määrin Luan ja Pythonin metaominaisuuksia. Tietenkin kaikkien metakielten äiti on vanha kunnon Common Lisp. Ei ole siis tavatonta käyttää kieltä itseään uusien rakenteiden kuvaamiseen. Käytännössä voi olla hyödyllistä tuottaa uusi määritelmä esimerkiksi luokalle. Tämä sinällään voi helpottaa huomattavasti vaikeiden ongelmien ratkaisua. Toisaalta metaohjelmointi ei ole aivan tavallista ja yleisesti tunnettua puuhaa, joten se sinällään voi aiheuttaa päänvaivaa ylläpidolle.

Varsinaisten metaohjelmointiominaisuuksien lisäksi kieliä voidaan käyttää oman määritelmänsä tulkitsemiseen. Toisin sanoen kielellä itsellään voidaan kirjoittaa oman itsensä tulkki, jota metasirkulaariseksikin sanotaan. Toisaalta esimerkiksi C:n tapauksessa kielen kääntäjä voidaan kirjoittaa C:llä.

Viimeinen käsitelty pari oli klassinen arvo vs. olio. Tässä tapauksessa olio ei tarkoita perinteistä olio-ohjelmoinnista tuttua oliota, joka sisältää sekä attribuutteja että metodeja. Tämä olio voidaan ajatella primitiivisimpänä mahdollisena, joka sisältää vain uniikin identiteetin sekä tilan, jota voidaan muuttaa.

On oleellista erottaa arvon käsite edellä määritellystä oliosta. Arvot ovat oleellisesti muuttumattomia. Esimerkiksi luku viisi tai merkkijono "kissa" ovat arvoja. Voidaan ajatella, että kutakin arvoa löytyy ideaalista universumista tai Platonin sanoin ideamaailmasta vain yksi kappale. Muut näkemämme arvon ilmentymät ovat vain näiden ideaalisten arvojen eräänlaisia kopioita tai varjoja. Käytännössä käyttämiämme arvoja voidaan ajatella riittäviksi korvikkeiksi eikä tällä filosofisella jaolla ole sen suurempaa merkitystä. Se kuitenkin tarjoaa mielenkiintoisen viitekehyksen, jonka kautta hahmottaa asiaa laajemmin.

Samaa ajatusta voisi kenties soveltaa myös olioihin. Vai voidaanko? Arvojen ja olioiden olennainen ero syntyy mielestäni siitä, että arvot ovat ajattomia. Ne ovat olemassa ajasta riippumatta. Jokaisen olion tulee syntyä kerran. On kenties absurdia sanoa, että olio on olemassa jo ennen syntymäänsä. Kuitenkin jotta olio voi syntyä, tulee olla olemassa malli, joka esittää sen abstraktin muodon. Ja jossain määrin järkevää voisikin olla ajatella malleista samoin tavoin kuin arvoista.

Sen lisäksi, että jokaisen olion tulee syntyä, voi olla että joidenkin niistä tulee kuolla. Se, tuleeko kaikkien olioiden kuolla joskus onkin jo filosofinen kysymys... Olioiden kuolemaan liittyy mielenkiintoinen ongelma siitä, mitä tämän jälkeen tulisi tehdä. Olio voidaan yksinkertaisimmillaan unohtaa. Toisaalta voisi olla mielekästä, että vanhat oliot tekisivät tilaa uusille. Tämä siis olettaen, että olioiden elintila olisi rajallinen. Tässä tapauksessa tulisi hyödyntää jotain sopivaa mekanismia joiden avulla oliot voisivat viestiä ympäristölleen, että on tullut heidän aikansa poistua. Tai vaihtoehtoisesti ympäristö voi tarkkailla sisältöään ja poistaa aikansa eläneitä olioita.

Tätä kautta päästään kiinni ohjelmoinnissa oleelliseen muistinkäsittelyyn. Esitetyt tavat ovat analogisia manuaalisen ja automaattisen muistinkäsittelyn käsitteiden kanssa. Esimerkiksi C++:ssa oliolle (siis olio-ohjelmoinnin oliolle!) voidaan määritellä destruktori, joka huolehtii sen jälkien siivoamisesta. Jälkimmäinen lähestymistapa on lähellä automaattista roskienkeruuta.

Ohjelmistojen tapauksessa on oleellista muistaa myös, että on olemassa ns. staattisia olioita, jotka elävät ohjelman elinajan aina sen käynnistyksestä sulkemiseen asti. Vielä mielenkiintoisempi tapaus ovat oliot, jotka elävät ohjelman tilasta huolimatta. Toisin sanoen mikäli ohjelma suljetaan ja käynnistetään uudelleen ovat samat vanhat oliot yhä ohjelmassa. Tällöin puhutaan persistenteistä olioista. Olioiden voidaan ajatella olevan horroksessa sen aikaa kun ohjelma on suljettuna.

Persistentit oliot ovat käsitteenä varsin kiehtovia. Niitä voidaan matkia myös kielissä, jotka eivät todellisuudessa toteuta persistenttejä olioita hyödyntämällä sarjallistamista. Tässä tapauksessa oliot kirjoitetaan tietyssä muodossa levylle. Toki tällä lähestymistavalla on ongelmansa. Käsittääkseni suurin ongelma liittyy siihen, että kaikkea ei yksinkertaisesti voida sarjallistaa. Voisi arvella, että sykliset rakenteet voisivat olla vähintään haastavia sarjallistajan kannalta.

Todelliset persistentit oliot voivat olla mielenkiintoisia myös todellisen kehittämisen kannalta. Mitä tapahtuu, jos olion alkuperäisen luokan määritelmä muuttuu? Tätä varten on lienee rakennettava erityinen mekanismi, joka huolehtii olioiden päivittämisestä uutta luokkaa vastaavaksi silti säilyttäen sen kartuttaman tiedon oleellisilta osin.

Vielä palataksemme olioon ja sen tilaan on tärkeää muistaa, että tilan käsitettä voidaan yleistää. Tila voi siis olla tilojen kokoelma tai tilojen kokoelman tilojen kokoelma tai jne. Tätä kautta päästään käsiksi moniulotteisten taulukkojen käsitteeseen. Pienellä nikkaroinnilla ja käsitteen venyttämisellä tilana voidaan ajatella jopa lienee C:stä tuttuja abstrakteja tietotyyppejä, jotka sinällään ovat vain omia pieniä kokoelmiaan.

Luennolla tehtiin mielenkiintoinen havainto liittyen taulukkojen indeksointioperaattoriin. Ensinnäkin indeksin avullahan viitataan tietyssä muistiosoitteessa olevaan tietoon. Se, kuinka notaatiota tulkitaan, riippuu Starcheyn määrittelemistä L-arvon (lausekkeen vasen puoli) ja R-arvon (lausekkeen oikea puoli) käsitteistä. Mikäli viittaus tehdään sijoituslauseen vasemmalla puolella, tarkoittaa se sitä, että kyseisessä indeksissä sijaitsevaan osoitteeseen sijoitetaan. R-arvon tapauksessa kyseisessä indeksissä sijaitseva arvo haetaan ja sijoitetaan lausekkeen vasemmalla puolella olevaan olioon.

Luennon suurin anti liittyi nimenomaan esitettyihin pareihin sekä niiden kautta käytyyn dialogiin. Mielestäni olion määritteleminen uudelleen ja sitä kautta saavutettu abstraktio auttoivat ymmärtämään paremmin sitä, millainen filosofinen perusta moderneilla kielillä on.

1 kommentti:

  1. Näkemykseni dynaamisen tyypityksen suhteen on muuttunut verrattuna postaukseen. Nykyään uskon, että tietyissä tapauksissa dynaamisesti tyypitettyä kieltä kannattaa laajentaa omilla eksplisiittisiä tyyppejä edustavilla luokilla. Lähtökohtaisesti dynaamisesti tyypitetyssä kielessä tyyppitarkasteluja voi tehdä esim. assert-lauseiden avulla tyyliin

    def sum(a, b):
    ____assert type(a) is int and type(b) is int
    ____return a + b

    (Sivuhuomiona haluaisin kirjoittaa kyseisen joukottamalla (set) a:n ja b:n ja suorittamalla vertailun koko joukon sisälle kerrallaan... Tätä se kielten opiskelu teettää...)

    Kauniimpi ratkaisu menisi seuraavasti:
    def sum(a, b):
    ____return int(a) + int(b)


    Ongelmana tässä tapauksessa on tyyppikonversio float -> int, joka seuraa int-olion luonnista. Tämän ongelman voisi kiertää toteuttamalla oman Int-luokan, joka sisältäisi vain intin hyväksymän tarkistuksen.

    Asian voisi hoitaa myös dekoraattorin tai docstring -pohjaisen ratkaisun kautta:
    # dekoraattori
    @types(int, int)
    def sum(a, b):
    ____return a + b

    # docstring
    def sum(a, b):
    ____''' int, int '''
    ____return a + b

    Docstringin syntaksi tuntuu tosin tällä hetkellä hieman turhan toisteiselta. Tyypit voisi heittää suoraan määrittelyynkin. Tosin tässä tapauksessa joutuisi kirjoittamaan oman tulkin, joka muuttaa tämän oikeaksi, validiksi koodiksi hyödyntäen jotain mainittua tapaa.

    (Huom! koodiesimerkkien _ tarkoittaa tyhjää.)

    VastaaPoista