Luku 15. Parametripolymorfismi Kumitus

Samankaltaiset tiedostot
Alityypitys. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

TIES542 kevät 2009 Rekursiiviset tyypit

Geneeriset tyypit. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

14.1 Rekursio tyypitetyssä lambda-kielessä

Oliot ja tyypit. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

lausekkeiden tapauksessa. Jotkin ohjelmointikielet on määritelty sellaisiksi,

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Ydin-Haskell Tiivismoniste

Haskell ohjelmointikielen tyyppijärjestelmä

Staattinen tyyppijärjestelmä

12. Monimuotoisuus 12.1

12. Monimuotoisuus 12.1

semantiikasta TIE448 Kääntäjätekniikka, syksy 2009 Antti-Juhani Kaijanaho 5. lokakuuta 2009 TIETOTEKNIIKAN LAITOS Ohjelmointikielten staattisesta

TIES542 kevät 2009 Tyyppiteorian alkeet

Laajennetaan vielä Ydin-Haskellia ymmärtämään vakiomäärittelyt. Määrittely on muotoa

2.4 Normaalimuoto, pohja ja laskentajärjestys 2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 26. kesäkuuta 2013

Taas laskin. TIES341 Funktio ohjelmointi 2 Kevät 2006

sama tyyppi (joka vastaa kaikkien mahdollisten arvojen summa-aluetta). Esimerkiksi

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Demo 7 ( ) Antti-Juhani Kaijanaho. 9. joulukuuta 2005

ELM GROUP 04. Teemu Laakso Henrik Talarmo

TIEA341 Funktio-ohjelmointi 1, kevät 2008

15. Ohjelmoinnin tekniikkaa 15.1

TIES542 kevät 2009 Oliokielten erityiskysymyksiä

Tyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet. TIES341 Funktio-ohjelmointi 2 Kevät 2006

15. Ohjelmoinnin tekniikkaa 15.1

11/20: Konepelti auki

Lisää laskentoa. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Yksinkertaiset tyypit

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Groovy. Niko Jäntti Jesper Haapalinna Group 31

Ohjelmoinnin peruskurssien laaja oppimäärä

Tämän vuoksi kannattaa ottaa käytännöksi aina kirjoittaa uuden funktion tyyppi näkyviin, ennen kuin alkaa sen määritemää kirjoittamaan.

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 30. marraskuuta 2015

Rekursiiviset tyypit

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

TIES542 kevät 2009 Oliokielten erityispiirteitä

TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 27. lokakuuta 2009

Ohjelmoinnin peruskurssien laaja oppimäärä

Koka. Ryhmä 11. Juuso Tapaninen, Akseli Karvinen. 1. Taustoja 2. Kielen filosofia ja paradigmat 3. Kielen syntaksia ja vertailua JavaScriptiin Lähteet

Uusi näkökulma. TIEA341 Funktio ohjelmointi 1 Syksy 2005

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 16. toukokuuta 2011

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

7/20: Paketti kasassa ensimmäistä kertaa

ja λ 2 = 2x 1r 0 x 2 + 2x 1r 0 x 2

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Tietotekniikan valintakoe

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Luku 2. Ohjelmointi laskentana. 2.1 Laskento

Ohjelmoinnin peruskurssien laaja oppimäärä

T Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

Lisää pysähtymisaiheisia ongelmia

Rekursiiviset tyypit - teoria

Tyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

Ruby. Tampere University of Technology Department of Pervasive Computing TIE Principles of Programming Languages

11.4. Context-free kielet 1 / 17

Luku 3. Listankäsittelyä. 3.1 Listat

815338A Ohjelmointikielten periaatteet

Javan perusteita. Janne Käki

TIES542 kevät 2009 Aliohjelmien formalisointia lambda-kieli

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

9. Periytyminen Javassa 9.1

Matematiikan tukikurssi

Matematiikan johdantokurssi, syksy 2016 Harjoitus 11, ratkaisuista

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Kehittää ohjelmointitehtävien ratkaisemisessa tarvittavia metakognitioita!

4. Olio-ohjelmoinista lyhyesti 4.1

Ohjelmoinnin peruskurssien laaja oppimäärä

Java-kielen perusteet

Java kahdessa tunnissa. Jyry Suvilehto

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Insinöörimatematiikka A

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op. Tietorakenneluokkia 2: HashMap, TreeMap

jäsentäminen TIEA241 Automaatit ja kieliopit, syksy 2015 Antti-Juhani Kaijanaho 26. marraskuuta 2015 TIETOTEKNIIKAN LAITOS

Tietueet. Tietueiden määrittely

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

Sisällys. 6. Metodit. Oliot viestivät metodeja kutsuen. Oliot viestivät metodeja kutsuen

Ohjelmoinnin perusteet Y Python

Logiikan kertausta. TIE303 Formaalit menetelmät, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos.

4 Matemaattinen induktio

T Syksy 2004 Logiikka tietotekniikassa: perusteet Laskuharjoitus 7 (opetusmoniste, kappaleet )

Reaalilukuvälit, leikkaus ja unioni (1/2)

16. Javan omat luokat 16.1

Olio-ohjelmointi Syntaksikokoelma

Algoritmit 1. Luento 3 Ti Timo Männikkö

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2

815338A Ohjelmointikielten periaatteet

Ohjelmoinnin jatkokurssi, kurssikoe

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Java-kielen perusteet

Induktiota käyttäen voidaan todistaa luonnollisia lukuja koskevia väitteitä, jotka ovat muotoa. väite P(n) on totta kaikille n = 0,1,2,...

Dierentiaaliyhtälöistä

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit

Transkriptio:

Luku 15 Parametripolymorfismi Lähes kaikessa ohjelmoinnissa tarvitaan säilöjä (engl. containers) ja niitä käsitteleviä operaatioita. Aiemmissa monisteissa esiteltiin NumList, joka oli numeroita sisältävä lista. Vastaavalla tavalla voitaisiin toteuttaa mitä tahansa muutakin nimettyä tyyppiä sisältävä listatyyppi, mutta ongelmaksi tulee, että tällainen koodin monistaminen on huonoa ohjelmointia. Yksi ei mitenkään harvinainen ratkaisu on käyttää alityypitystä (johon palataan seuraavassa luvussa) hyväksi ja tehdä listatyyppi, joka sisältää yleisimmän tyypin (esimerkiksi Javassa Object) arvoja. Ongelmaksi muodostuu listan alkioiden käsittely aitona itsenään, sillä se vaatii downcastausta, joka puolestaan on staattisesti tyypitetyssä kielessä huonoa tyyliä 1. Edellä kuvattu tilanne vastaa Java-kielen tilannetta ennen versiota 5, jolloin otettiin käyttöön generics-laajennus. Se perustuu Generic Java -kieleen (Bracha et al., 1998), jonka alkuperäiset kehittäjät ovat tyyppiteorian ja ohjelmointikielten teorian arvostettuja tutkijoita. Generic Javan taustalla on tässä luvussa käsiteltävät parametrisen polymorfismin perusteoriat, Girardin Systeemi F (sama kuin Reynoldsin polymorfinen lambda-kieli) sekä sen tyyppioperaattorilaajennus. Luvun päälähde on Pierce (2002, luvut 23 ja 29). 15.1 Kumitus Kumitus (engl. erasure) on kuvaus tyypitetyltä kieleltä vastaavalle tyypittömälle kielelle siten, että ohjelmasta poistetaan kaikki viittaukset tyyppeihin muuttamatta ohjelman merkitystä. Esimerkiksi (puhtaasta) yksinker- 1 Downcastaus on olennaisesti dynaaminen tyyppitarkastus ohjelmoijan käskystä. 155

156 LUKU 15. PARAMETRIPOLYMORFISMI taisesti tyypitetystä lambdakielestä tyypittömään lambdakieleen: erase(x) = x erase(tu) = erase(t)erase(u) erase(λx : T t) = λx erase(t) Kaikilla kielillä ei ole kumitusta (esimerkiksi Haskellissa tyypeillä on myös dynaamista merkitystä). 15.2 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. Cardelli ja Wegner (1985) 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 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

15.3. SYSTEEMI F 157 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ää. 15.3 Systeemi F Jean-Yves Girardin Systeemi F, joka tunnetaan myös John Reynoldin laatimana polymorfisena lambdakielenä, on yksinkertaisesti tyypitetyn lambdakielen laajennus, joka mahdollistaa parametrisesti polymorfiset funktiot. Sen taustalla on seuraava probleema: mikä tyypitetyn lambda-kielen termi vastaa tyypittämättömän lambda-kielen termiä λxx? Yksinkertaisesti tyypitetyssä lambda-kielessä joudutaan valitsemaan jokin konkreettinen tyyppi, esimerkiksi λx : Num x taikka λx : Num Num x nämä kaikki kumittuvat λxx:ksi, mutta mitään niistä ei voi sanoa tuon funktion kanoniseksi tyypitetyksi versioksi. Toinen esimerkki tulee, kun ajatellaan parametrisesti polymorfisia tietorakenteita (joista tarkemmin myöhemmin). Oletetaan, että kielessä on tyyppinimi List T, jossa T:n voi korvata millä tahansa tyypillä ja List T on T-tyyppisiä alkioita sisältävä lista. Nyt kysymys kuuluu: onko mahdollista kirjoittaa map-funktiota, jolle voi antaa parametrina minkä tahansa listan (riippumatta alkiotyypistä)? Yksinkertaisesti tyypitetyssä lambdakielessä tämä ei ole mahdollista (ja sama ongelma on kaikilla monomorfisilla kielillä kuten C ja Pascal). Systeemi F:n taustalla on se havainto (jota sanotaan Curryn Howardin vastaavuudeksi), että yksinkertaisesti tyypitetyn lambda-kielen tyypit vastaavat suoraan (intuitionistisen) propositiologiikan kaavoja: funktiotyyppi vastaa implikaatiota ja perustyypit ovat propositiovakioita 2 ; termit puolestaan on mahdollista lukea näiden kaavojen todistuksiksi. Systeemi F laajentaa yksinkertaisesti tyypitettyä lambda-kieltä samaan tyyliin kuin toisen kertaluvun predikaattilogiikka laajentaa propositiologiikkaa: otetaan käyttöön tyyppikvanttori α T (myös kvanttori voidaan ottaa mukaan, mutta se ei kuulu Systeemi F:ään). Systeemi F:ssä identiteettifunktion I tyyppi on α α α ja map-funktion tyyppi on (olettaen List T-tyy- 2 Yksinkertaisesti tyypitetyssä lambda-kielessä ei ole tyyppejä T, T U eikä T U saati T U. Hauska harjoitus on pohdiskella, minkälaisia arvoja tällaissa tyypeissä voisi olla.

158 LUKU 15. PARAMETRIPOLYMORFISMI pin olemassaolo) α (α α) List α List α. Termien tasolla Systeemi F laajentaa lambda-kieltä ottamalla käyttöön tyyppiabstraktion λα t, joka muuttaa monomorfisen termin polymorfiseksi lisäämällä tyyppiin kvanttorin, sekä tyyppiapplikaation t T, joka muuttaa polymorfisen termin monomorfiseksi poistamalla tyypistä kvanttorin. Esimerkiksi identiteettifunktio I kirjoitettaisiin λα λx : α x, ja sitä käytettäisiin esimerkiksi lukuun tyyliin I Num 42. Muodollisesti Systeemi F määritellään seuraavasti. Määritelmä ei sisällä tyyppilaajennuksia kuten aritmetiikkaa taikka tietueita, mutta nuo laajennukset voidaan pultata Systeemi F:ään varsin helposti samaan tapaan kuin yksinkertaisesti tyypitettyyn lambda-kieleen. α, β, γ TypeVariables x, y, z Variables T, U, V Types T, U, V ::= α tyyppimuuttuja T U funktiotyyppi α T universaalityyppi t, u Terms t, u ::= x muuttuja tu (termi)applikaatio λx : T t (termi)abstraktio t T tyyppiapplikaatio λα t tyyppiabstraktio v Values v ::= w λx : T v λα v w Spines w x wv Kuten ennenkin, pitää määritellä korvausoperaattori ja vapaat muuttujat (tällä kertaa molemmat myös tyyppimuuttujille). Samoin tulisi määritellä termien ja tyyppien yhtäläisyys ( ) siten, että sidottujen termi- ja tyyppimuuttujien nimillä ei ole merkitystä. Sivuutetaan nuo tässä. Seuraavat β-sievennyssäännöt ovat laskennan perusta: (λx : T t) u t[x := u] (15.1)

15.4. TYYPPIOPERAATTORIT 159 (λα t) T t[α := T] (15.2) Lisäksi tarvitaan joukko sääntöjä, jotka sallivat osalausekkeiden sivennyksen: t t tu t u u u tu t u (15.3) (15.4) t t λx : T t λx : T t (15.5) t t t T t T (15.6) t t λα t λα t (15.7) Tyypityssäännöissä on Systeemi F:n olennainen sisältö. Ensiksi lambdakielen tavanomaiset säännöt: Γ, x : T x : T Γ t : U V Γ u : U Γ t u : V Γ, x : T u : U Γ (λx : T u) : T U Ja lopuksi tyyppiapplikaation ja -abstraktion säännöt: Γ t : α T Γ t U : T[α := U] Γ t : T Γ λα t : α T 15.4 Tyyppioperaattorit (15.8) U U (15.9) (15.10) (15.11) (x,u) Γ : α FV(U) (15.12) Systeemi F mahdollistaa parametrisesti polymorfiset funktiot mutta ei mahdollista parametrisoituja tyyppejä (joita tarvitaan yleisten, tyyppiturvallisten säiliöiden toteuttamiseen). Tyyppioperaattorien teoria mahdollistaa edellä annetun esimerkkityypin List toteuttamisen.

160 LUKU 15. PARAMETRIPOLYMORFISMI Tyyppioperaattoria kuten List on luonnollista ajatella tyyppitason funktioksi. Siksi luonnolliselta tuntuu lisätä tyyppien tasolle lambda-kielen perusoperaatiot (applikaatio ja abstraktio). Koska tyyppijärjestelmän tulee olla ratkeava, on ilmaisuvoimaa rajoitettava sen verran, että tyyppien sievennys päättyy aina. Yksinkertainen tapa tehdä tämä rajoitus on rakentaa tyypeille oma (yksinkertaisesti tyypitetty) tyyppijärjestelmänsä, sillä yksinkertaisesti tyypitetty lambda-kieli on ratkeava. Ideana on siis rakentaa kolmitasoinen kieli, jossa itse laskenta tapahtuu alimmalla, termien tasolla; keskimmäinen taso on puolestaan tyyppien taso, ja ylimpänä tasona on tyyppien tyyppijärjestelmä. Jotta sanat eivät menisi sekaisin, kutsutaan tyyppien tyyppejä luonteiksi (engl. kinds). Luonteita ovat muiden muassa, joka tarkoittaa tavallista tyyppiä, ja, joka tarkoittaa tyyppioperaattoria, joka ottaa yhden tyyppiparametrin. Tyyppioperaattoreita (sekä monikko-, variantti- ja µ-laajennusta) käyttäen List on lyhennysmerkintä tyyppilausekkeelle λα : µβ null = (), notnull = (α, β) Muodollinen määritelmä kielelle Systeemi F ω (joka on Systeemi F laajennettuna tyyppioperaattoreilla) on seuraavanlainen: α, β, γ TypeVariables x, y, z Variables K Kinds K ::= tavallisten tyyppien luonne K 1 K 2 tyyppioperaattorien luonne T, U Types T, U ::= α tyyppimuuttuja T U funktiotyyppi α : K T universaalityyppi T U operaattoriapplikaatio λα : K T operaattoriabstraktio t, u Terms t, u ::= x muuttuja tu (termi)applikaatio λx : T t (termi)abstraktio t T tyyppiapplikaatio λα : K t tyyppiabstraktio

15.4. TYYPPIOPERAATTORIT 161 Laskentasääntöjä varten tarvitaan myös arvojen ja selkärankojen määrittely: v Values v ::= w λx : T v λα : K v w Spines w x wv (15.13) Tyyppiyhtäläisyyden säännöt sisältävät tyyppien α- ja β-sievennyssäännöt: T T U T T U (15.14) (15.15) T 1 T 2 T 2 T 3 T 1 T 3 (15.16) T 1 U 1 T 2 U 2 T 1 T 2 U 1 U 2 (15.17) T[α := γ] U[β := γ] α : K T β : K U T[α := γ] U[β := γ] λα : K T λβ : K U γ uusi (15.18) γ uusi (15.19) T 1 U 1 T 2 U 2 T 1 T 2 U 1 U 2 (15.20) (λα : K T) U T[α := U] (15.21) Luonnejärjestelmä on yksinkertaisesti tyypitetty: Γ, α : K α : K (15.22) Γ T : K 1 K 2 Γ U : K 1 Γ T U : K 2 (15.23) Γ, α : K 1 T : K 2 Γ (λα : K 1 T) : K 1 K 2 (15.24) Γ T : Γ U : Γ T U : Γ, α : K T : Γ ( α : K T) : (15.25) (15.26)

162 LUKU 15. PARAMETRIPOLYMORFISMI Termien laskusäännöt eivät kovin paljoa eroa Systeemi F:n vastaavista: (λx : T t) u t[x := u] (15.27) (λα : K t) T t[α := T] t t tu t u u u tu t u (15.28) (15.29) (15.30) t t λx : T t λx : T t (15.31) t t t T t T (15.32) t t λα : K t λα : K t (15.33) Tyypityssäännöissä on pieniä muutoksia. Mainittakoon, että tyyppiympäristö sisältää termimuuttuja tyyppi-parien lisäksi myös tyyppimuuttuja luonne-pareja. Nyt voidaan lausua lambda-kielen tavanomaiset säännöt, joissa ei ole muutoksia: Γ, x : T x : T (15.34) Γ t : T 1 T 2 Γ u : U T 1 U Γ t u : T 2 (15.35) Γ, x : T u : U Γ (λx : T u) : U Ja lopuksi tyyppiapplikaation ja -abstraktion säännöt: Γ t : α : K T Γ U : K Γ t U : T[α := U] Γ, α : K t : T Γ λα : K t : α : K T (15.36) (15.37) (x,u) Γ : α FV(U) (15.38) Systeemi F ω :ssa (tarpeen mukaan laajennettuna ja ekvirekursiota nou-

15.4. TYYPPIOPERAATTORIT 163 dattaen) on mahdollista viimeinkin käyttää tyyppiturvallisia säiliöitä: List = λα : µβ null = (), notnull = (α, β) map = λα : λg : α α µ f : List α List α λl : List α case l of null = x null = () notnull = x let (a, r) = x in notnull = (g a, f r) Näillä lyhennysmerkinnöillä onkin mahdollista kirjoittaa map Num (λx : Num x + 1), joka sievenee funktioksi, jonka tyyppi on List Num List Num ja joka lisää jokaiseen lukulistan alkioon ykkösen.

164 LUKU 15. PARAMETRIPOLYMORFISMI

Luku 16 Alityypitys Staattisesti tyypitetyissä oliokielissä on tyypillistä, että tyyppien välillä on osittaisjärjestys (joskus jopa hila), jota kutsutaan alityypitykseksi (engl. subtyping) 1. Oliokielissä tyypillistä on, että tyyppi T on tyypin U alityyppi, merkitään T <: U, jos T on johdettu U:sta perimällä suoraan tai välillisesti. Joskus myös asetetaan alityypityssuhde lukutyyppien välille; kokonaislukutyyppi olisi rationaalilukujen tyypin alityyppi. Alityypitystä voi esiintyä myös muissa kuin oliokielissä. Alityypityksen periaatteellinen määritelmä on subsumptioperiaate: Jos (ja vain jos) T on U:n alityyppi, niin jokainen T:n lauseke kelpaa U:n lausekkeeksi. Lukemalla tätä periaatetta vasemmalta oikealle saadaan tyypityksen subsumptiosääntö: Γ t : T T <: U (16.1) Γ t : U Käytännössä subsumptioperiaatteen toteutuminen vaatii joko, että (a) tyypillä ja sen kaikilla alityypeillä on yhteensopiva binäärirajapinta muun muassa yhteensopiva koko tai että (b) subsumptiosäännön soveltaminen johtaa pakotukseen (engl. coercion) eli implisiittiseen tyyppimuunnokseen. Tyypillisesti (a) on toteutettavissa vain siten, että kaikki alityypittyvät tyypit ovat laatikoituja (engl. boxed) eli tyypin arvo on osoitin varsinaiseen dataolioon ilman, että osoitin näkyy käyttäjälle. Oliokielissä yleensä, kuten esimerkiksi Javassa, alityypittyvät tyypit (luokkatyypit Javassa) ovat kaikki laatikoituja (eli viitetyyppejä). Tapa (b) on harvinaisempi sitä käytetään lähinnä lukutyyppien kanssa, sillä lukutyyppien laatikoiminen hi- 1 Sitä kutsutaan usein myös perinnäksi (engl. inheritance), vaikka perintä varsinaisesti tarkoittaa luokan laajentamista. 165

166 LUKU 16. ALITYYPITYS dastaa ohjelman suoritusta kohtuuttomasti. C++ on tässä suhteessa poikkeuksellinen kieli, se nimittäin käyttää molempia tapoja: tapa (a) on käytössä osoitintyyppien (T ) ja viitetyyppien (T&) kanssa, ja tapa (b) on käytössä suorien oliotyyppien kanssa. Tässä luvussa tarkastellaan subsumptioperiaatteen seurauksia eri tilanteissa. Luvun päälähde on Pierce (2002, luku 15). 16.1 Alityypitys on osittaisjärjestys Subsumptioperiaatteesta seuraa suoraan, että alityypitysrelaatio on refleksiivinen: T <: T (16.2) Jos T 1 <: T 2 ja t : T 1 pätevät, niin subsumptiosäännön mukaan t : T 2 pätee. Jos lisäksi T 2 <: T 3 pätee, niin subsumptiosäännöstä seuraa suoraan, että t : T 3 pätee. Näin ollen T 1 <: T 3 pätee. Alityypitysrelaatio on siis transitiivinen: T 1 <: T 2 T 2 <: T 3 T 1 <: T 3 (16.3) Mikäli T <: U ja U <: T ovat molemmat voimassa, ovat väitteet t : T ja t : U ekvivalentit. On siis luontevaa määritellä, että kaksi tyyppiä ovat samat, jos niihin kuuluu samat termit; tämän ns. ekstensionaalisen tyyppiyhtäläisyyden voimassa ollessa alityypitys on myös antisymmetrinen: T <: U U <: T T U Näin ollen alityypitys on luonteeltaan osittaisjärjestys. (16.4) 16.2 Tietueiden alityypitys Ehkäpä selkein esimerkki alityypityksen toiminnasta saadaan seuraavasti. Tarkastellaan lauseketta (λx : { a : Num } x.a) { a = 1, b = 2 } Pohtimatta vielä tyypitystä lasketaanpa sen arvo: (λx : { a : Num } x.a) { a = 1, b = 2 } { a = 1, b = 2 }.a 1

16.3. FUNKTIOIDEN ALITYYPITYS 167 Lauseke ei siis jää jumiin, joten mitään tyyppivirhettä ei pitäisi olla, ja lausekkeen tyypin pitäisi olla Num. Kokeillaanpa tyypittää lauseke: x : { a : Num } x : { a : Num } x : { a : Num } x.a : Num { a = 1, b = 2 } : { a : Num } (λx : { a : Num } x.a) { a = 1, b = 2 } : Num Tyypitys ei onnistu, koska { a = 1, b = 2 } : { a : Num } ei päde yksinkertaisesti tyypitetyssä lambda-kielissä, johon on lisätty tietuetyyppi. Ongelma on ratkaistavissa ottamalla käyttöön subsumptiosääntö ja määrittelemällä, että tietuetyypin lopusta saadaan poistaa kenttiä vapaasti: { (l i : T i ) i=1 m } <: { (l i : T i ) i=1 n m n (16.5) } Huomaa, kuinka alityypissä on enemmän kenttiä kuin ylityypissä! Nyt edellä mainittu tyypitys onnistuu: x : { a : Num } x : { a : Num } x : { a : Num } x.a : Num { a = 1, b = 2 } : { a : Num, b : Num } { a : Num, b : Num } <: { a : Num } { a = 1, b = 2 } : { a : Num } (λx : { a : Num } x.a) { a = 1, b = 2 } : Num On myös mahdollista sallia kenttien alityypitys: ( T i <: U i ) n i=1 { (l i : T i ) n i=1 } <: { (l i : U i ) n i=1 } (16.6) Tietueiden kenttien unohtamissääntö (16.5) vaatii dynaamiselta toteutukselta joko, että säännön soveltaminen johtaa tietueen katkaisemiseen (käytännössä luomalla uusi tietueolio, jossa on vähemmän kenttiä, ja jonka kenttiin vanhan tietueolion kenttien arvot kopioidaan) taikka että tietueet ovat laatikoituja. Kenttien alityypitys (16.6) ei vaadi erityistä dynaamista tukea. 16.3 Funktioiden alityypitys Tarkastellaan funktiokutsua f a, missä funktion tyyppi on f : T U, argumentin tyyppi on a : T ja lauseketta käytetään tilanteessa, jossa vaaditaan tyyppiä U oleva lauseke. Toisin sanoen, f :n tulee kelvata tyyppiä T U olevaksi funktioksi. Ilmeistä on, että T :n tulee kelvata parametrityypiksi, eli pitää olla T <: T Toisaalta U:n pitää kelvata paluukontekstin tyypiksi, eli pitää olla U <: U. Yhteenvetona saadaan seuraavanlainen sääntö: T <: T U <: U T U <: T U (16.7)

168 LUKU 16. ALITYYPITYS Huomaa, kuinka alityypitysrelaatio on nurin päin argumenttityyppien kohdalla, kun taas paluutyyppien kohdalla ei. Sanotaankin, että funktiotyyppi on kontravariantti argumentin suhteen ja kovariantti paluutyypin suhteen. 16.4 Top ja Bot Monissa kielissä on olemassa ylin tyyppi, yleensä nimeltään Object, joka on kaikkien tyyppien ylityyppi. Tyyppiteoriassa sitä kutsutaan nimellä Top (koska se on alityypityshilan supremum): T <: Top (16.8) Vastaavasti voidaan myös määritellä alin tyyppi, joka on kaikkien tyyppien alityyppi, Bot: Bot <: T (16.9) Tämän tyypin erikoisuutena on, että siihen ei sisälly yhtään arvoa. Näin ollen se vastaa aiemmin esiteltyä noreturn-tyyppiä. 16.5 Eksplisiittiset tyyppimuunnokset Joskus, lähinnä dokumentaatio- ja debuggaustarkoituksissa, voi olla tarpeen kirjoittaa ohjelman sisään sellaisia tyyppiannotaatiota, jotka eivät ole tyyppijärjestelmän kannalta tarpeen. Laajennetaan lambda-kieltä tällaisella operaattorilla: t, u ::= t :: T Laskenta yksinkertaisesti jättää tällaisen annotaation huomiotta: (t :: T) t (16.10) Tyyppijärjestelmä huolehtii siitä, että tällainen huomiotta jättäminen on mahdollista: Γ t : T T <: T Γ (t :: T) : T (16.11)

16.6. MUUTTUVAISET 169 Näin määriteltynä kyseessä on niin sanottu upcast, joka lähinnä dokumentoi lausekkeen tyypin (tarvittaessa unohtaen lausekkeen aiemman tyypin, jos se on dokumentoidun tyypin alityyppi). Usein on toisaalta tarvetta downcastaukseen eli ylityypin muuttamiseen alityypiksi. Sellainen operaatio voi epäonnistua, sillä kyse on ohjelmoijan eksplisiittisestä käskystä ohittaa tyyppijärjestelmä kontrolloidusti. Esimerkiksi Javassa operaatio (T)t, jos t:n staattinen tyyppi on T:n ylityyppi, heittää poikkeuksen, jos t:n dynaaminen tyyppi ei ole T:n alityyppi ohjelmoija voi tämän tarkistaa etukäteen käyttämällä instanceof-operaattoria. Lambda-kieleen vastaava on luonnollisinta tuoda käyttämällä case-lauseketta: t, u ::= case t of ( x i :: T i t i ) + i v : T T <: T 1 case v of ( x i :: T i t i ) n i=1 t 1[x 1 := v] (16.12) (16.13) v : T T <: T 1 case v of ( x i :: T i t i ) i=1 n case v of ( x i :: T i t i ) i=2 n n 2 (16.14) t t case t of ( x i :: T i t i ) i=1 n case t of ( x i :: T i t i ) i=1 n (16.15) Γ t : T ( T i <: T ) n i=1 T <: T n Γ, ( x i t i : U) n i=1 Γ (case t of x 1 :: T 1 t 1 x n :: T n t n ) : U (16.16) Huomaa, kuinka tyyppisääntö (16.16) vaatii, että viimeinen tyyppi on t:n oma tyyppi näin viimeinen vaihtoehto toimii jokerina. 16.6 Muuttuvaiset Kaikenlaiset tyypit, jotka kelpaavat sijoituslauseen vasemmalle puolelle esimerkiksi osoitintyypit ovat alityypityksen kannalta hankalia. Tarkastellaan osoitintyyppiä T. Jos sen tyyppistä lauseketta t : T käytetään tähtäysoperaattorissa, joka vaatii tyyppiä U olevan arvon, pitää päteä T <: U. Toisaalta tällöin tähtäysoperaattorin tyyppi on T, joten pitää päteä T <: U. Jos t:tä käytetään sijoituslauseen vasemmalla puolella, ja oikealla puolella on U-tyyppiä oleva lauseke. täytyy toki päteä T <: U ; toisaalta nyt minkä tahansa U-tyyppisen arvon tulee kelvata T-tyyppiseksi arvoksi, joten T <: U pätee. Näin ollen osoitintyyppi on alityypityksen suhteen invariantti: T <: U U <: T T <: U (16.17)

170 LUKU 16. ALITYYPITYS Huomaa argumentaation laatu: jos tyypin lauseke on vastaanottaja (esimerkiksi sijoituslauseen vasen puoli taikka funktion argumentti), on alityypitys kontravarianttia, ja jos tyypin lauseke on lähettäjä (useimmat muut), on alityypitys kovarianttia. Koska osoitintyyppiä oleva arvo voi toimia sekä lähettäjänä että vastaanottajana, on osoitintyyppi sekä kovariantti että kontravariantti eli siis invariantti. 16.7 This-tyyppi Aiemmin tässä monisteessa käsiteltiin binäärimetodien ongelmaa (luku 9.6). Yksi ratkaisu ongelmaan on erillisen this-tyypin ottaminen mukaan kieleen. This-tyyppiä käytettäisiin metodin parametrin tyyppinä tarkoittamaan metodin itseviittauksen dynaamista tyyppiä. Näin laajennetussa Javakielessä voitaisiin equals-metodi kirjoittaa seuraavasti: public class Object {... public boolean equals(this other) { return this == other }... } Tämä kuitenkin rikkoo alityypityksen! Tämä Object.equals-metodi on tyypiltään Object boolean. Objectluokasta peritään luokka String; Javan perintärelaatio on samalla alityypitysrelaatio, joten pätee String <: Object. Nyt String.equals-metodi on tyypiltään String boolean. Koska funktiot ovat parametrinsa suhteen kontravariantteja, voi String <: Object päteä vain, jos String ja Object ovat sama tyyppi, mitä ne eivät ole! Tässä on ristiriita. Bruce et al. (1995) ehdottavat ratkaisuksi mallia, jossa alityypityksestä oliokielessä luovutaan ja se korvataan rajoitetulla parametripolymorfismilla. Tällöin Object.equals olisikin tyypiltään α <# Object α boolean, missä <# on alityypitysrelaatiota rajoitetumpi tyyppien yhteensopivuutta ilmaiseva relaatio, matching. Tällöin yllä ehdotettu This-tyyppi on lyhennysmerkintä tällaiselle parametrisoinnille.