3.5. TYYPIT 59 indeksit voidaan siirtää kielitasolta oliotasolle siirryttäessä alkamaan nollasta. Vain mikäli indeksin alin arvo oliotasolla ei ole tiedossa ennen suorituksen alkua, on tarpeen lisätä taulukko-olioon tieto indeksin alarajasta. Tyyppitarkastus (Staattinen) tyyppitarkastus on prosessi, jolla ohjelmointikielen toteutus varmistaa ennen suorituksen alkamista, ettei ohjelma aiheuta yhtään tyyppivirhettä. Useimmat tyyppitarkastimet vaativat tyyppien merkitsemistä ohjelmatekstiin ainakin jokaisen muuttujanesittelyn yhteydessä. Joissakin tapauksissa tyypit voidaan päätellä käyttöyhteydestä näin on esimerkiksi ohjelmointikielten lausekkeiden tapauksessa. Jotkin ohjelmointikielet on määritelty sellaisiksi, että niissä kaikki tyypit on pääteltävissä, jolloin mitään tyyppimerkintöjä ei yleensä tarvita. Tyyppien yhtäläisyys Ohjelmointikielessä, jossa voidaan määritellä uusia tyyppejä, nousee esiin kysymys siitä, milloin kaksi tyyppiä ovat samat. Kaksi pääasiallisinta ratkaisua ovat rakenneyhtäläisyys (structural equivalence) ja nimiyhtäläisyys (name equivalence). Rakenneyhtäläisyys Rakenneyhtäläisyyden perusidea on se, että jos kahdella tyypillä on sama rakenne, ne ovat samat. Se, mitä samalla rakenteella tarkoitetaan, vaihtelee eri kielissä. Tarkastellaanpa esimerkkiä (kirjoitettu kuvitteellisella, Pascalin kaltaisella kielellä). type päiväys1 = record pv, kk, v : integer end; type päiväys2 = record pv, kk, v : integer end; type päiväys3 = record pv : integer; kk : integer; v : integer end; type päiväys4 = record v : integer; kk : integer; pv : integer; end;
60 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT type päiväys5 = record dse : integer (* days since epoch, päiviä ajanlaskun alusta *) end type taulu1 = array [1..10] of integer; type taulu2 = array [1+0..10] of integer; type taulu3 = array [0..9] of integer; Ei syntyne erimielisyyttä siitä, että päiväys1 ja päiväys2 ovat rakenneyhtäläisyyden vallitessa sama tyyppi. Myöskin päiväys3 julistettaneen yksimielisesti mutta ehkä pienen keskustelun jälkeen samaksi tyypiksi kuin kaksi edellistä. Lienee selvää, että päiväys5 on eri tyyppi kuin muut neljä päiväystyyppiä. Erimielisyyttä esiintyy vain päiväys4:n kohdalla: ML hyväksyy sen samaksi tyypiksi kuin kolme edellustä, monet muut kielet eivät. Samaan tapaan lienee selvää, että taulu1 ja taulu2 ovat sama tyyppi, ja taulu3 on eri kuin mikään muu esimerkissä mainittu tyyppi. Rakenneyhtäläisyyden vallitessa tyyppien samuuden tarkastelu tapahtuu helpoiten abstraktin syntaksin tasolla, jossa tyyppinimet korvataan määritelmillään, ja sitten verrataan abstrakteja jäsennyspuita keskenään. Rekursiiviset tyypit tekevät tämän hankalaksi, mutta tämä ongelma on ratkaistavissa palauttamalla se äärellisten automaattien yhtäläisyyden tarkasteluun. Rakenneyhtäläisyyttä käyttävät Algol 68, Modula 3, ML ja (jossakin määrin) C. Nimiyhtäläisyys Nimiyhtäläisyyden vallitessa kaksi tyyppiä ovat samat, jos niillä on sama nimi. Toisin sanoen, jos ohjelmoija kirjoittaa kaksi eri tyypinmäärittelyä, ne määrittelevät aina ja kaikissa tilanteissa eri tyypin. Edellä määritellyt esimerkkityypit päiväys1, päiväys2, päiväys3, päiväys4, päiväys5, taulu1, taulu2 ja taulu3 ovat nimiyhtäläisyyden vallitessa kaikki eri tyyppejä. Nimiyhtäläisyyden suurin ongelma tulee aliastyypeistä eli tyypeistä, jotka määritellään toisen tyypin synonyymiksi. Esimerkiksi C++:n varussäilöt (standard container) määritellään jollakin seuraavan kaltaisella tavalla: template <typename KeyT, typename DataT> class map {... public;... typedef KeyT key_type; typedef DataT mapped_type;
3.5. TYYPIT 61 }; Onko tällöin map<string,int>::mapped_type sama tyyppi kuin int? C++:ssa näin on, ja on aika vaikea kuvitella, miten tuollaisen luokan saisi olemaan yleiskäyttöinen ilman, että näin olisi. Nimiyhtäläisyyttä käyttävää kieltä, jossa aliastyypit eivät ole erillisiä tyyppejä, sanotaan käyttävän heikkoa nimiyhtäläisyyttä (weak name equivalence). Jos aliastyypit ovat erillisiä tyyppejä, kyseessä on vahva nimiyhtäläisyys (strong name equivalence). Nimiyhtäläisyyttä käytetään useimmissa nykykielissä alkaen Javasta. Tyyppimuunnokset Staattisesti tyypitetyissä kielissä tarvitaan nimenomaan tietyntyyppisiä lausekkeita tietyissä paikoissa. Esimerkiksi Haskell-lausekkeessa let f :: Integer -> Integer f x = x + 1 in f r vaaditaan, että r on tyyppiä Integer. Jos ohjelmoija haluaa käyttää näissä paikoissa jonkin toisen tyypin lauseketta, hänen tulee muuntaa (convert, cast) lausekkeen tyyppiä. Tyypinmuunnoksia on neljänlaisia: 1. Jos lausekkeen todellinen tyyppi (lähtötyyppi) ja se tyyppi, joksi se halutaan muuttaa, (kohdetyyppi) olisivat rakenneyhtäläisyyden voimassaollessa sama tyyppi, mutta kielessä käytetään nimiyhtäläisyyttä, voidaan tyyppi muuntaa toiseksi suoraan ilman mitään ajonaikaisia toimenpiteitä. 2. Jos lähtötyypillä ja kohdetyypillä on sama ajonaikainen esitys ja niillä on yhteisiä arvoja, muunnos voidaan toteuttaaa näillä yhteisillä arvoilla. Mikäli lähtötyypin arvoalue on kohdetyypin arvoalueen osajoukko, palautuu ongelma edelliseen tapaukseen. Mikäli kohdetyypin arvoalue on lähdetyypin arvoalueen aito osajoukko, voidaan tässäkin tehdä suora tyypinmuunnos, mutta turvallisuuden vuoksi on syytä tarkistaa suoritusaikana, että lausekkeen arvo on kohdetyypin arvoalueella. Tähän tapaukseen kuuluvat oliokielissä varsin usein tarvittavat muunnokset ylöspäin (upcast), jossa aliluokan muuttuja muunnetaan yliluokan muuttujaksi, sekä alaspäin (downcast), jossa tietyn luokan muuttujan tiedetään (tai toivotaan) sisältävän siitä perityn luokan olion, ja muuttuja muunnetaan tuohon aliluokkaan. 3. Varsin usein myös eri ajonaikaiset esitykset ovat muunnettavissa jollakin hyvin määritellyllä operaatiolla toisikseen. Esimerkiksi 32-bittinen
62 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT kokonaisluku on muunnettavissa kaksoistarkkuuden IEEE-liukuluvuksi ilman tarkkuuden menetystä. Tällöin muunnos vaatii tuon operaation tekemisen suoritusaikana. 4. Joissakin tapauksissa voi olla välttämätöntä tulkita raa asti yhden tyypin olio toisen tyypin olioksi. Tämän tulkinnan toimivuus on hyvin toteutuskohtaista, ja harva kieli edes sallii tällaisen operaation. Ne kielet, jotka sen sallivat, yleensä jättävät määrittelemättä, mitä tällaisessa tilanteessa tapahtuu. C-kielessä kaikkia neljää tapausta merkitään samalla tavalla: kohdetyyppi kirjoitetaan kaarisulkeisiin ja sen perään kirjoitetaan lauseke, jonka tyyppiä halutaan muuntaa. Tämän muunnoksen tulkinta on monimutkainen asia ja riippuu monesta yksityiskohdasta. C++:ssa tätä on parannettu ottamalla käyttöön eri notaatiot näille eri tapauksille: static_cast tekee puhtaan tyyppimuunnoksen, dynamic_cast tekee muunnoksen alaspäin, ja reinterpret_cast tekee raa an uudelleentulkinnan. Lisäksi C++ määrittelee const_cast-operaation, joka liittyy kielen vakiotyyppien käsittelyyn, sekä rakenninkutsunotaation, jossa tyypin nimeä käytetään tyypinmuunnosfunktion nimenä. Monimuotoisuus Staattinen tyyppijärjestelmä voi pahimmillaan olla mahdottoman rajoittava pakkopaita. Tämän lieventämiseksi lähes kaikki staattisesti tyypitetyt kielet tukevat monimuotoisuutta muodossa tai toisessa. Monimuotoisuudella (polymorphism) tarkoitetaan sitä, että ohjelmointikielen aliohjelmalla voi olla useampi kuin yksi tyyppi. Luca Cardelli ja Peter Wegner 13 jaottelevat monimuotoisuuden kahteen lajiin ja neljään alalajiin seuraavasti: yleinen monimuotoisuus (universal polymorphism) Yleisesti monimuotoinen aliohjelma toimii periaatteessa äärettömän monella, jollakin tapaa samanoloisella tyypillä. Tämä jaotellaan kahteen alalajiin: parametrinen monimuotoisuus (parametric polymorphism) Parametrisesti monimuotoinen aliohjelma ottaa parametrinaan tyypin, ja sen muiden parametrien tyypit saattavat riippua tuosta tyypistä. Kullekin tyypille funktion ohjelmakoodi on sama. sisällytysmonimuotoisuus (inclusion polymorphism) Sisällytysmonimuotoisuudessa oliolla voi olla useita tyyppejä, jotka ovat joukkoopin mielessä sisäkkäisiä. Kullakin näistä tyypeistä on oma versionsa sisällytysmonimuotoisesta aliohjelmasta, ja se, mikä tulee 13. Luca Cardelli ja Peter Wegner: On Understanding Types, Data Abstraction, and Polymorphism. ACM Computing Surveys, vol. 17 no. 4, December 1985.
3.5. TYYPIT 63 suoritettua, kun aliohjelmaa kutsutaan, riippuu siitä, mikä on pienin (eli spesifisin) tyyppi, joka oliolla on. satunnainen monimuotoisuus (ad hoc polymorphism) Satunnaisesti monimuotoinen aliohjelma toteuttaa eri tyypeille jossakin mielessä saman operaation, mutta tämän toteuttava koodi on tyypillisesti täysin erilainen kullekin tyypille ja tyypeillä ei ole mitään erityistä yhteyttä toisiinsa monimäärittely (overloading) Monimäärittelyssä useammalla aliohjelmalla voi olla sama nimi. Aliohjelma tulee valittua parametrien määrän ja tyyppien perusteella. automaattinen tyypinmuunnos (coercion) Automaattinen tyypinmuunnos on tyypinmuunnos, joka tehdään ilman, että ohjelmoija sitä erikseen pyytää.
64 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT