3.5. TYYPIT 51 3.5.3 Kielitason tyypit Staattinen tyypitys Kielitasolla tyyppejä käytetään sulkemaan pois joitakin merkittäviä ja automaattisesti tunnistettavissa olevia merkitysopillisia virheitä (oliotason tyyppivirheet osa arvotason -arvoista). Tämä toteutetaan staattisella tyyppitarkastuksella (static typechecking), jossa toteutus diagnosoi tyyppivirheet ennen suorituksen alkua. Mikäli kielen määrittely vaatii staattisen tyyppitarkastuksen, kieli on staattisesti tyypitetty (static typing). Dynaamisesti tyypitetyt kielet ovat kielitasolla tarkasteltuna tyypittömiä (untyped) muuttujilla ei ole tyyppejä, tai sanoen toisin, kaikilla muuttujilla on sama tyyppi (joka vastaa kaikkien mahdollisten arvojen summa-aluetta). Esimerkiksi Luca Cardellin ansiokkaassa tyyppijärjestelmäartikkelissa 8 koko terminologia määritellään kielitason näkökulmasta oliotason tyypille ei edes anneta nimeä, vaikka oliotason tyyppivirheelle tämä tehdään. Kielitason tyyppejä Yksinkertaiset tyypit Arvotason rakenteen pohjalla ovat yksinkertaiset alueet, oliotyyppien perusosaset ovat perustyyppejä. Samoin myös kielitason tyyppijärjestelmän perustana ovat yksinkertaiset tyypit (simple types) eli perustyypit (basic types) eli skalaarityypit (scalar types). Yksinkertaiset tyypit vastaavat suoraan yksinkertaisia alueita ja oliotason perustyyppejä. Niinpä näitä ovat erilaiset kokonaislukutyypit ja liukulukutyypit, jotka edustavat sopivia oliotason tyyppejä. Näiden lisäksi yksinkertaisiksi tyypeiksi lasketaan luettelotyypit (enumeration types). Luettelotyypillä tarkoitetaan tyyppiä, joka sisältää äärellisen määrän arvoja joita ohjelmakoodissa edustaa joukko nimiä. Esimeriksi Haskellin määrittely data Viikonpäivät = Maanantai Tiistai Keskiviikko Torstai Perjantai Lauantai Sunnuntai 8. Luca Cardelli: Type Systems. Julkaistu kirjan Handbook of Computer Science and Engineering lukuna 103, CRC Press 1997 sekä Internetissä osoitteessa http://research.microsoft. com/users/luca/papers/typesystems.a4.ps
52 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT määrittelee luettelotyypin Viikonpäivät, jolla on seitsemän ( :sta eroavaa) arvoa. Kukin määrittelyn yhteydessä mainituista nimistä (Maanantai, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai, Sunnuntai) sidotaan määrittelyssä staattisesti yhteen näistä arvoista (niin, että jokainen arvo tulee sidotuksi johonkin nimeen). Vastaavasti Pascalissa voidaan kirjoittaa type viikonpaiva = (maanantai, tiistai, keskiviikko, torstai, perjantai, lauantai, sunnuntai); Joissakin kielissä luettelotyypit ovat järjestettyjä, joissakin ei (esimerikiksi Haskellissa pitää määrittelyn lopussa erikseen mainita, jos haluaa tyypin olevan järjestetty, Pascalissa luettelotyyppi on aina järjestetty sen mukaan, missä järjestyksessä arvoje nimet luetellaan määrittelyssä). Useimmissa kielissä on jo kielen määrittelyssä (tai varuskirjastossa) määritelty totuusarvojen tyyppi (yleensä nimeltään bool, Bool, boolean tai Boolean). Se voidaan kaikissa tapauksissa ymmärtää ainakin käsitteellisesti luettelotyypiksi. Esimerkiksi Haskellin varuskirjasto (Haskell Standard Prelude) määrittelee seuraavaa: data Bool = False True Vastaavasti C99:n 9 tyyppi bool voitaisiin ajatella määritellyksi seuraavasti: enum bool { true, false }; (Oikeasti bool on makro, joka lavenee varustyypiksi (predefined type) _Bool, ja true ja false ovat makroja, jotka lavenevat vakioiksi 1 ja 0.) Toinen luettelotyypiksi käsitettävissä oleva tyyppi on merkkityyppi. Tällöin tosin pitää mielikuvitusta venyttää hieman enemmän kuin edellä, sillä merkkiliteraalit eivät ole kieliopillisesti kelvollisia nimiä. Mutta jos ne sallittaisiin nimiksi, voitaisiin esimerkiksi Haskellissa kirjoittaa jotain seuraavan kaltaista: data Char =... a b... Joissain kielissä (esimerkiksi C:ssä ja C++:ssa) luettelotyypit ovat lähinnä kokonaislukutyyppejä. Lähes kaikissa suhteissa C++:n määrittely enum season { winter, spring, summer, autumn }; vastaa määrittelyä class season { int val; public: explicit season(int val) : val(val) {} operator int () { return val; } }; 9. Programming languages C. International standard ISO/IEC 9899:1999(E), 1999.
3.5. TYYPIT 53 static const season winter(0); static const season spring(1); static const season summer(2); static const season autumn(3); Muissakin kielissä luettelotyyppejä vastaa oliotasolla kokonaislukutyyppi. Osavälityyppejä (subrange types) voidaan käyttää lähes kaikissa Algol-sukuisissa kielissä Pascalista alkaen. Kyse on tyypistä, joka sisältää osavälin jostakin järjestetystä tyypistä (sen päätyyppi, base type, parent type). Tavallisesti päätyypiksi kelpaa vain kokonaislukutyyppi tai luettelotyyppi. Esimerkiksi Pascalissa voidaan määritellä type koepisteet = 0..24; arkipaiva = maanantai..lauantai; Tietueet Tietuetyypin (record type, structure type) arvot ovat tuloalueen alkioita, tietuetyypin oliot ovat monikkoja. Tietueolion kullakin alkiolla eli tietuearvon kullakin projektiolla (eli tietueen kentällä, field) on nimi, joka on kiinnitetty tyypin määrittelyssä. Tietuetyyppiä olevan muuttujan perään kirjoitetaan tyypillisesti piste ja halutun kentän nimi. Tällainen konstruktio on kyseisen olion kyseistä kenttää edustava l-arvo 10. Esimerkiksi C:ssä voidaan määritellä seuraavanlainen tietuetyyppi ja tietue struct paivays { int p; int kk; int v; }; struct paivays tanaan = { 14, 10, 2002 }; Tällöin lauseke tanaan.p viittaa sen olion ensimmäiseen alkioon, joka on sidottu muuttujaan tanaan, ja siis myös kyseisen olion arvon ensimmäiseen projektioon. Yön yli nukuttua on hyvä sanoa tanaan.p = tanaan.p + 1; if (((tanaan.kk == 3 tanaan.kk == 5 tanaan.kk == 7 tanaan.kk == 8 tanaan.kk == 10 tanaan.kk == 12) && tanaan.pp == 31) ((tanaan.kk == 4 tanaan.kk == 6 tanaan.kk == 9 tanaan.kk == 11) 10. L-arvolla (l-value) tarkoitetaan kielen abstraktin syntaksin lauseketta, joka voi toimia jonkin olion nimenä, siis sijoituslauseen vasemmalla puolella.
54 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT && tanaan.pp == 30) (tanaan.kk == 2 && ((!((tanaan.v % 4 == 0 && tanaan.v % 100!= 0) tanaan.v % 400 == 0) && tanaan.pp == 28) (((tanaan.v % 4 == 0 && tanaan.v % 100!= 0) tanaan.v % 400 == 0) && tanaan.pp == 29)))) { tanaan.kk = tanaan.kk + 1; tanaan.p = 1; } if (tanaan.kk > 12) { tanaan.kk = 1; tanaan.v = tanaan.v + 1; } (Tai oikeastaan ei: oikeasti kuukauden vaihdos kannattaa hoitaa taulukoimalla.) Jotkin kielet sallivat muuttuvia tietueita (variant records). Tällöin osa tietueen kentistä vaihtelee sen mukaan, mikä arvo jollakin toisella tietueen kentällä on. Esimerkiksi Pascalissa voidaan kirjoittaa seuraavasti: type string = packed array [0..maxlen] of char; type symkind = (variable, function); type symbol = record nimi : string; case kind : symkind of variable : ( depth : integer; displacement : integer; ); function : ( parameter_count : integer; call_address : integer; ) end; Tällöin tyypin symbol muuttujien sisältönä on merkkijono nimi sekä tieto (kentässä kind) siitä, onko kyseessä muuttuja vai funktio; jos kyseessä on muuttuja, tässä symbol-muuttujassa on kentät depth ja displacement, joita käytetään muuttujanhakukoodin generoinnissa; jos kyseessä on funktio, symbol-muuttujassa on kentät parameter_count, jota käytetään kutsun tyyppitarkastuksessa, ja call_addresss, jota käytetään kutsukoodin generoin-
3.5. TYYPIT 55 nissa. Muuttuvat tietueet toteutetaan luonnollisesti monikko-olioina, joissa on sisällä laputettu yhdisteolio. Algebralliset tyypit Haskellissa voidaan kirjoittaa seuraavasti: data Lauseke = Plus Lauseke Lauseke Miinus Lauseke Lauseke Kerto Lauseke Lauseke Jako Lauseke Lauseke Jaannos Lauseke Lauseke Kokonaisluku Integer Liukuluku Double Tämä määrittelee tyypin nimeltä Lauseke, jonka arvoalue Lauseke on määriteltävissä seuraavasti: I = {Plus, Miinus, Kerto, Jako, Jaannos, Kokonaisluku, Liukuluku} S Plus = Lauseke Lauseke S Miinus = Lauseke Lauseke S Kerto = Lauseke Lauseke S Jako = Lauseke Lauseke S Jaannos = Lauseke Lauseke S Kokonaisluku = Integer S Liukuluku = Double Lauseke = i I S i Huomaa, että luettelotyypit voidaan nähdä algebrallisten tyyppien erikoistapauksena. Tyypin Lauseke määrittelyn vaikutusalueella voidaan nimiä Plus, Miinus, Kerto, Jako, Jaannos, Kokonaisluku ja Liukuluku käyttää koostinfunktioina (constructor), joista kaksi viimeistä ottavat yhden parametrin (Kokonaisluku ottaa parametrin tyyppiä Integer, Liukuluku ottaa Doublen) ja loput ottavat kaksi Lauseke-tyyppistä parametria. Näiden koostinfunktioiden merkitysoppi on seuraava (tarkastellaan esimerkin vuoksi vain Pluskoostinta): Plus : Lauseke Lauseke Lauseke Plus x y = (Plus, (x, y))
56 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT Lauseke-parametrin ottavia funktioita voidaan Haskellissa määritellä paloittain hahmontunnistuksella siten, että parametrin paikalle kirjoitetaan jokin koostimista ja sen parametreiksi nimiä (tai sitten muita samanlaisia hahmoja): sievenna :: Lauseke -> Lauseke sievenna (Plus (Kokonaisluku i) (Kokonaisluku j)) = Kokonaisluku (i + j) sievenna (Miinus (Kokonaisluku i) (Kokonaisluku j)) = Kokonaisluku (i - j) sievenna (Kerto (Kokonaisluku i) (Kokonaisluku j)) = Kokonaisluku (i * j)... Tyyppejä, 1. jotka ovat merkitykseltään tuloalueiden summa-alueita, joissa summaalueen laput voidaan valita vapaasti, 2. joiden laput toimivat koostinfunktioina lausekkeissa, ja 3. joiden arvoja voidaan sovittaa lapuista ja muista hahmoista koostettuun hahmoon, kun funktioita määritellään paloittain, sanotaan algebrallisiksi (algebraic types). Algebrallisia tietotyyppejä on nykyisin lähinnä sellaisissa kielissä kuin ML, Haskell ja Pizza 11. Algebralliset tyypit ovat luonnostaan rekursiivisia: on mahdollista kirjoittaa tyypinmäärittely, jossa määriteltävä tyyppi itse esiintyy koostimen parametrityyppinä. Tätä ominaisuutta tarvitaan useimpien kehittyneiden tietorakenteiden (kuten puut ja verkot) toteuttamiseen. Abstraktit tyypit Abstrakti tyyppi (abstract type) on tyyppi, jonka rakenne on piilotettu. Tyypin käyttäjälle annetaan joukko nimiä, joiden tyyppisidonta kerrotaan. Tyypin käyttäjä voi käsitellä tyypin olioita ja arvoja vain näiden nimien (joista suurin osa on epäilemättä funktioita) avulla. Esimerkiksi C:ssä seuraava otsikkotiedosto määrittelee abstraktit tyypit symbol_table_t ja scope_ ref_t: #ifndef SYMBOL_TABLE_H #define SYMBOL_TABLE_H typedef struct symbol_table * symbol_table_t; typedef struct scope_ref * scope_ref_t; 11. Ks. http://pizzacompiler.sourceforge.net/
3.5. TYYPIT 57 symbol_table_t symbol_table_new(void); void symbol_table_delete(symbol_table_t); void enter_new_scope(symbol_table_t, int closed_p); void leave_scope(symbol_table_t); scope_ref_t get_current_scope(symbol_table_t); void enter_old_scope(symbol_table_t, scope_ref_t); void insert_symbol(symbol_table_t, char const *, void * data); void * lookup_symbol(symbol_table_t, char const *); #endif /* SYMBOL_TABLE_H */ Erillisessä.c-tiedostossa on sitten näiden tyyppien ja funktioiden toteutukset, jotka eivät näy tyypin käyttäjälle. Joissakin kielissä abstrakteihin tyyppeihin voi liittää spesifikaatiota: funktioille saatetaan voida määritellä esiehtoja ja jälkiehtoja, funktioiden välisiä suhteita voidaan rajoittaa kiinnittämällä niille päteviä yhtälöitä ynnä muuta sellaista. Tällaiset spesifikaatiopredikaatit ovat osa abstraktin tyypin rajapintaa: toteutuksen on täytettävä niissä esitetyt vaatimukset, mutta se voi olettaa esiehtojen pätemisen, tyypin käyttäjän on puolestaan huolehdittava, että esiehdot pätevät, mutta se voi olettaa jälkiehtojen ja yhtälöiden pätevän. Tällaiset kielet ovat harvinaisuuksia tiedeyhteisön ulkopuolella; Eiffel lienee tunnetuin. Muita tällaisia kieliä ovat Alphard ja Euclid. Lisäksi monet formaalit menetelmät sisältävät tukea ohjelmoinnille, jolloin mukaan tulevat myös tällaiset keinot. Luokat ja rajapinnat Luokka on olio-ohjelmoinnin tärkein tyyppikäsite. Luokalla on seuraavat merkittävät ominaisuudet: Kapselointi Luokka määrittelee paitsi arvojen ja olioiden rakenteen, myös sen, mitä niillä voi tehdä. Toisin sanoen luokkaan sisältyy eräänlaisia etuoikeutettuja aliohjelmia eli metodeita (methods). Mikäli luokka jättää määrittelemättä yhdenkin metodin toiminnallisuuden (jolloin kyseessä on abstrakti metodi, abstract method), luokka on abstrakti (abstract class). Arvojen ja olioiden rakenne on luokkatyypeissä lähes aina tuloalue ja monikko; tämä määritellään samaan tapaan kuin tietueiden tapauksessa määrittelemällä attribuutteja (attributes) (tietueterminologiassa kenttiä). Perintä Luokka voidaan määritellä perimään kaikki jonkin toisen luokan ominaisuudet (attribuutit ja metodit). Tällöin tästä luokasta tulee perityn luokan alityyppi (subtype) eli aliluokka (subclass) eli johdettu luok-
58 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT ka (derived class); perittyä luokkaa sanotaan tämän luokan ylityypiksi (supertype) eli yliluokaksi (superclass) eli kantaluokaksi (base class). Luokan olio voi toimia kantaluokkansa oliona kaikissa tilanteissa. Monimuotoisuus Luokan metodi voi syrjäyttää (refine, override) kantaluokassa määritellyn metodin. Mikäli tällöin oliota käytetään kantaluokan oliona ja kutsutaan kantaluokan syrjäytettyä metodia, kuitenkin suoritetaan se metodi, joka tuon metodin oli syrjäyttänyt. Tätä monimuotoisuustyyppiä sanotaan sisällytysmonimuotoisuudeksi (inclusion polymorphism). Luokkainvariantti Hyvin monilla luokilla on attribuuttien arvojen kombinaatioita, jotka ovat mielettömiä luokan tehtävän kannalta. Jokaiseen luokkaan liittyy predikaatti, jossa luokan attribuutit esiintyvät vapaina, ja joka on tosi täsmälleen silloin, kun attribuuttien arvot muodostavat mielekkään kombinaation. Tätä predikaattia sanotaan luokkainvariantiksi (class invariant) useimmat kielet eivät tue sitä suoraan, mutta poikkeuksiakin on, tunnetuin niistä on Eiffel. Luokan metodeilla on vastuu siitä, että ne säilyttävät luokkainvariantin toisin sanoen, mikäli luokkainvariantti pätee metodin suorituksen alkaessa, sen tulee päteä myös metodin suorituksen päättyessä. Luokalla on rakennin (constructor 12 ), jonka tehtävänä on alustaa olion tila (attribuutit) sellaiseksi, että luokkainvariantti pätee. Rakennin ajetaan aina heti olion syntymän jälkeen. Joissakin kielissä rakentimia voi luokassa olla useita ja niille voi antaa argumentteja, joiden perusteella rakennin alustuksensa tekee. Luokkien oliot ovat kaikki OO-olioita. Javassa osaa luokista kutsutaan rajapinnoiksi (interface). Ne eroavat tavallisista luokista paitsi kieliopillisesti myös siten, että ne eivät sisällä lainkaan attribuutteja ja kaikki sen metodit ovat abstrakteja. Rajapinnat ovat siis eräänlaisia abstrakteja tietotyyppejä. Taulukot Merkittävin ero oliotason ja kielitason välillä taulukoiden suhteen on se, että kielitasolla indeksointi ei suinkaan aina ala nollasta. Joissakin kielissä taulukon indeksointi alkaa aina ykkösestä, ja monessa kielessä taulukon indeksityypiksi kelpaa myös mikä tahansa osavälityyppi (jolloin indeksien rajat riippuvat tyypistä). Kuitenkin niin kauan, kun indeksityyppi on toteutettu kokonaislukuvälinä ja sen alin arvo tiedetään ennen suorituksen alkamista, 12. Rakentimia ei pidä sekoittaa algebrallisten tyyppien ja Scottin alueiden koostimiin, vaikka englanninkielinen termi onkin sama.