maanantai 16. helmikuuta 2009

Luento 11 - Tyyppijärjestelmän laajennoksia

Luento rakensi aiemmin esitetyn teorian luomalle pohjalle. Aluksi esitettiin mielenkiintoinen huomio siitä, kuinka C:stä tuttu void tyyppi on määritelty kielen spesifikaatiossa. Sen mukaan void on tyyppi, jolla ei ole yhtään arvoa. Aliohjelmakutsun tapauksessa tämä tarkoittaa intuitiivisesti hieman hämärää käyttäytymistä.

Olkoon void tee_jotain(int a, int b) { ... }. C käyttäytyy siten, että aliohjelmakutsu (tee_jotain(4, 6)) palaa kutsuvaan funktioon. Kuitenkin määritelmää tarkasti lukien voisi olettaa, että paluuta ei tapahdu. Luennoitsija esittikin, että tarkempi määritelmä olisikin, että void palauttaisi pohjan (pohja on käsittääkseni jotakuinkin sama asia kuin, että aliohjelman saavuttaessa loppunsa, se palaa kutsuvaan käyttäen paluuosoitetta). Edellä mainitun epäselvyyden vuoksi esitettiin erityistä "noreturn" tyyppiä, joka ei palaa kutsujaan.

En ole varma olisiko noreturn tyypistä käytännön hyötyä. Sinällään sen perusajatusta voi helposti emuloida esimerkiksi kutsumalla järjestelmätason poistukomentoa. Toisaalta se olisi hyvinkin eksplisiittinen tapa ilmaista, että aliohjelmakutsu ei palaa takaisin.

Luennolla mainittiin myös, että joissain kielissä aliohjelmista voidaan erottaa erityiset proseduurit. Näistä olen kuullut aiemminkin (erityisesti Delphin yhteydessä), mutta merkitys oli päässyt hieman unohtumaan. Ajatuksena on, että proseduurilla ei ole lainkaan palautusarvoja missään tapauksessa. Proseduureja käytetään sivuvaikutuksien avulla ohjelmointiin. Proseduuri voi manipuloida esimerkiksi jotain globaalia tilaa.

Periaatteessa olisi mahdollista luopua aliohjelman käsitteestä ja käyttää ainoastaan proseduureja. Kuitenkin pelkästään sivuvaikutusten avulla ohjelmointi tuntuu jotenkin vieraalta ajatukselta. Miten tässä tapauksessa kannattaisi hoitaa esimerkiksi hypot(a, b) (hypotenuusa) tulos? Tuloksen voisi tallentaa johonkin globaalisti saatavilla olevaan paikkaan, vaikkapa globaaliin result muuttujaan. Tämän ratkaisumallin ongelmat tulevat esiin, kun pitää suorittaa useita samanaikaisia proseduureja, jotka manipuloivat samaa globaalia dataa. Ei ole taetta siitä, että tulos on oikea. Toisessa ratkaisumallissa voitaisiin palautusosoite antaa parametrina.

Mielestäni aliohjelmien käyttäminen pelkkien proseduurien sijasta on varsin perusteltua. Toisaalta proseduuri sinällään sisältyy aliohjelman määritelmään, mikäli aiemmin mainittu void tai vastaava konstruktio on käytössä. Tässä mielessä on varsin luontevaa, ettei kieli satu sisältämään erillistä proseduurin käsitettä.

Mikäli kieleen haluaisi tarkan jaon aliohjelmien ja proseduurien välille, tämän voisi tehdä siten, että ainoastaan proseduureilla voi olla sivuvaikutuksia. Toisin sanoen voitaisiin eksplisiittisesti kieltää aliohjelmien sivuvaikutukset. Ohjelmoijan kannalta tästä saattaisi olla enemmän haittaa kuin hyötyä joustavuuden suhteen. Kuitenkin semantiikka säilyisi erittäin selvänä.

Aliohjelmista puhuttaessa eräs mielenkiintoinen ja luennolla sivuttu seikka on se, kuinka ne voivat palauttaa monia arvoja. Aina ei ole mielekästä palauttaa vain yhtä arvoa. Toisaalta, jos aliohjelma voi palauttaa monia arvoja kerralla, kuinka se tulisi määritellä staattisesti tyypitetyssä kielessä? Eräs luonteva tapa tehdä tämä on palauttaa monikko (tuple) tai tietue (structure).

Sekä monikot ja tietueet olivat jo ennalta tuttuja. Monikoita olen käyttänyt varsinkin Pythonissa. Sen toteutuksessa monikon sisältämiä viitteitä olioihin ei voi enää muokata luonnin jälkeen. Huomaa, ettei tämä tarkoita etteikö olioiden sisältöä voisi muokata! Ainoastaan viitteet on "lukittu".

Tietue on varsin tuttu käsite C:stä. Tietuehan on pohjimmiltaan vain monikko, jossa sisältöön voidaan viitata konkreettisten nimien avulla pelkkien indeksien sijasta. Tietueisiin liittyy myös varianttien käsite, jossa tietueen tiettyä osaa voidaan varioida. Voidaan siis matkia yksinkertaista perintää noin tietueen tasolla.

Tietue muistuttaa hyvin paljon assosiatiivista listaa (dictionary) rakenteeltaan. Dynaamisesti tyypitetyissä kielissä assosiatiivisen listan avulla voidaankin emuloida tietueita. Esimerkiksi Luassa tässä mennään varsin pitkälle erityisesti syntaksin suhteen (syntaksi muistuttaa C:n tietuesyntaksia mutta operoi assosiatiivista listaa).

Luennon lopussa käytiin läpi vielä viitetyyppien käsitettä. Joskus voi olla hyvinkin hyödyllistä, että voidaan operoida muistiosoitteiden tarkkuudella, kuten C:ssä ikään. Korkeamman tason kielet abstrahoivat tämän käyttäytymisen syntaksinsa alle. Tässä tapauksessa joudutaan tekemään automaattia käännöksiä osoitteiden ja arvojen välillä.

2 kommenttia:

  1. Postauksessa olisi voinut käsitellä lisänä näkyvyysalueita ja paikallisia muuttujia. Näkyvyysalueen käsite voidaan johtaa helposti blokin käsitteestä. Esim. begin end -pari voidaan ajatella blokkina. Joissain kielissä onkin mahdollista kirjoittaa seuraavantyylisesti:
    func tee_jotain(a, b)
    ____begin
    ________print a # tulostaa a:n arvon
    ________begin
    ____________c = 5 # paikallinen muuttuja
    ____________print c # tulostaa 5
    ________end
    ________print c # heittää virheen, koska c ei näy enää
    ____end

    Mielestäni blokin (tälle varmaan löytyy parempi käännös!) käsite on mukava yleistys, jonka kautta näkyvyysalueisiin pääsee käsiksi.

    VastaaPoista
  2. Jos aliohjelma "palauttaa" pohjan, niin silloin se ei koskaan palaa (joko jää jumiin taikka kaataa ohjelman). C:n void-tyypissä pitää olla siis yksi pohjasta eroava arvo. Tämä oli se pointti, jota yritin luennolla voidista ja noreturnista tehdä.

    VastaaPoista