14.1 Rekursio tyypitetyssä lambda-kielessä



Samankaltaiset tiedostot
TIES542 kevät 2009 Rekursiiviset tyypit

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

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

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

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

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

Rekursiiviset tyypit - teoria

Ydin-Haskell Tiivismoniste

Luku 15. Parametripolymorfismi Kumitus

Haskell ohjelmointikielen tyyppijärjestelmä

Taas laskin. TIES341 Funktio ohjelmointi 2 Kevät 2006

Rekursiiviset tyypit

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

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Kesälukio 2000 PK2 Tauluharjoituksia I Mallivastaukset

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

Yksinkertaiset tyypit

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

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

3.3 Paraabeli toisen asteen polynomifunktion kuvaajana. Toisen asteen epäyhtälö

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

TIEA341 Funktio-ohjelmointi 1, kevät 2008

3. Muuttujat ja operaatiot 3.1

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

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

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

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

jäsennyksestä TIEA241 Automaatit ja kieliopit, syksy 2016 Antti-Juhani Kaijanaho 29. syyskuuta 2016 TIETOTEKNIIKAN LAITOS Kontekstittomien kielioppien

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

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Kun järjestelmää kuvataan operaattorilla T, sisäänmenoa muuttujalla u ja ulostuloa muuttujalla y, voidaan kirjoittaa. y T u.

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

Toinen muotoilu. {A 1,A 2,...,A n,b } 0, Edellinen sääntö toisin: Lause 2.5.{A 1,A 2,...,A n } B täsmälleen silloin kun 1 / 13

Harjoitus 5 -- Ratkaisut

Vasen johto S AB ab ab esittää jäsennyspuun kasvattamista vasemmalta alkaen:

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

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

Staattinen tyyppijärjestelmä

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 3. lokakuuta 2016

5/20: Algoritmirakenteita III

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

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

Luku 3. Listankäsittelyä. 3.1 Listat

Luku 2. Ohjelmointi laskentana. 2.1 Laskento

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

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Kuvaus eli funktio f joukolta X joukkoon Y tarkoittaa havainnollisesti vastaavuutta, joka liittää joukon X jokaiseen alkioon joukon Y tietyn alkion.

MS-A0204 Differentiaali- ja integraalilaskenta 2 (ELEC2) Luento 7: Pienimmän neliösumman menetelmä ja Newtonin menetelmä.

S BAB ABA A aas bba B bbs c

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 29. toukokuuta 2013

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

Nopea kertolasku, Karatsuban algoritmi

ELM GROUP 04. Teemu Laakso Henrik Talarmo

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

Yhtälönratkaisusta. Johanna Rämö, Helsingin yliopisto. 22. syyskuuta 2014

Dart. Ryhmä 38. Ville Tahvanainen. Juha Häkli

Lisää laskentoa. TIEA341 Funktio ohjelmointi 1 Syksy 2005

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

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

Tietorakenteet ja algoritmit - syksy

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 12. kesäkuuta 2013

Java-kielen perusteet

Matematiikan tukikurssi, kurssikerta 5

TIES542 kevät 2009 Tyyppiteorian alkeet

Ohjelmoinnin peruskurssien laaja oppimäärä

Mathematica Sekalaista asiaa

Johdatus diskreettiin matematiikkaan Harjoitus 5, Ratkaise rekursioyhtälö

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 16. helmikuuta 2012

Java-kielen perusteet

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

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

2.2 Muunnosten käyttöön tutustumista

Tietorakenteet ja algoritmit

Sekalaiset tehtävät, 11. syyskuuta 2005, sivu 1 / 13. Tehtäviä

2. Seuraavassa kuvassa on verkon solmujen topologinen järjestys: x t v q z u s y w r. Kuva 1: Tehtävän 2 solmut järjestettynä topologisesti.

811120P Diskreetit rakenteet

Vaihtoehtoinen tapa määritellä funktioita f : N R on

Attribuuttikieliopit

Ohjelmoinnin peruskurssien laaja oppimäärä

Rekursio. Funktio f : N R määritellään yleensä antamalla lauseke funktion arvolle f (n). Vaihtoehtoinen tapa määritellä funktioita f : N R on

Kerta 2. Kerta 2 Kerta 3 Kerta 4 Kerta Toteuta Pythonilla seuraava ohjelma:

Täydentäviä muistiinpanoja kontekstittomien kielioppien jäsentämisestä

Tutoriaaliläsnäoloista

Gaussin ja Jordanin eliminointimenetelmä

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

TIES542 kevät 2009 Aliohjelmien formalisointia lambda-kieli

Mitään muita operaatioita symbolille ei ole määritelty! < a kaikilla kokonaisluvuilla a, + a = kaikilla kokonaisluvuilla a.

Insinöörimatematiikka A

Kehittää ohjelmointitehtävien ratkaisemisessa tarvittavia metakognitioita!

Täydentäviä muistiinpanoja Turingin koneiden vaihtoehdoista

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

5.5 Jäsenninkombinaattoreista

Matikkaa KA1-kurssilaisille, osa 3: suoran piirtäminen koordinaatistoon

Matematiikan tukikurssi, kurssikerta 3

Algoritmit. Ohjelman tekemisen hahmottamisessa käytetään

2. Minkä joukon määrittelee kaava P 0 (x 0 ) P 1 (x 0 ) mallissa M = ({0, 1, 2, 3}, P M 0, P M 1 ), kun P M 0 = {0, 1} ja P M 1 = {1, 2}?

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

Transkriptio:

Luku 14 Rekursiiviset tyypit Edellisessä luvussa esitetyt tietue- ja varianttityypit eivät yksinään riitä kovin mielenkiintoisten tietorakenteiden toteuttamiseen. Useimmissa ohjelmissa tarvitaan erilaisia puurakenteita, jotka on luonnollisinta esittää suoraan käyttämällä itseviittaavia tyyppejä. Esimerkiksi yksöislinkitetty kokonaislukulista voidaan määritellä C-kielessä seuraavasti: struct intlist { int datum; struct intlist next; }; Tyypitetyn (ja rakenteisilla tyypeillä laajennetun) lambda-kielen syntaksilla se voitaisiin kirjoittaa esimerkiksi näin: NumList = null : (), notnull : { datum : Num, next : NumList }... paitsi että tuohon kieleen ei kuulu minkäänlaista tapaa antaa nimiä (saati rekursiivisia määritelmiä!) tyypeille. Tässä luvussa käsitellään rekursiivisten tyyppimääritelmien problematiikkaa ja sen ratkaisumalleja. Luvun päälähde on Pierce (2002, luvut 21 ja 22). 14.1 Rekursio tyypitetyssä lambda-kielessä Rekursiivisten tyyppien käsittelyssä tarvitaan rekursiivisia termejä. Koska yksinkertaisesti tyypitetyssä lambda-kielessä ei ole valmista kiintopisteoperaattoria, on sellainen lisättävä: t, u ::= µx : T t 147

148 LUKU 14. REKURSIIVISET TYYPIT Γ, x : T t : T Γ µx : T t : T µx : T t t[x := µx : T t] (14.1) (14.2) Hieman käyttäjäystävällisempi mutta teoriatasolla sotkuisempi on letreclauseke: t, u ::= letrec (x i : T i = t i ) + i in t Letrec-lauseke sitoo kaikki muuttujat (x i ) + i. FV-funktion ja korvausoperaattorin määritelmät sivuutetaan tässä. ( ) n Γ, (x j : T j ) n j=1 t i : T i Γ, (x j : T j ) n j=1 t : T i=1 Γ letrec (x i : T i = t i ) i=1 n in t : T (14.3) Laskentasäännöt on helpointa määritellä kontekstin käsitteen avulla. Konteksti on termi, jossa on yksi (ja vain yksi) reikä (engl. hole). Jos t on konteksti, niin t[u] tarkoittaa termiä, joka saadaan kontekstista korvaamalla siinä esiintyvä reikä u:lla. Konteksti yksinään ei ole mielenkiintoinen, vaan sillä on helppo ilmaista lausekkeen osan korvaaminen toisella: t[u]:sta t[u ]. Huomaa, että t[x] t[u] ei ole sama asia kuin t[x] t[x][x := u], koska edellinen korvaa vain x:n yhden (mahdollisesti sidotun) esiintymän, jälkimmäinen kaikki sen vapaat esiintymät. Lisäksi tarvitaan funktio BV(t), joka palauttaa t:ssä sidottuina esiintyvien muuttujien joukon. Näin voidaan yhdellä säännöllä ilmaista, miten letrec-määritelty muuttuja korvataan määritelmällään: letrec (x i : T i = v i ) n i=1 in t[x k] letrec (x i : T i = v i ) n i=1 in t[v k] 1 k n x k BV(t[x k ]) (14.4) Muutoin letrec-laskentaan tarvitaan tavanomaiset rakennesäännöt: t t letrec (x i : T i = v i ) n i=1 in t letrec (x i : T i = v i ) n i=1 in t (14.5) t k t k letrec (x i :T i =t i ) n i=1 in t letrec (x i:t i =t i ) k 1 i=1,x k:t k =t k,(x i:t i =t i ) n i=k+1 in t 1 k n (14.6)

14.1. REKURSIO TYYPITETYSSÄ LAMBDA-KIELESSÄ 149 Kun in-lauseke ei enää riipu letrecin muuttujista, voidaan letrec poistaa: letrec (x i : T i = v i ) i=1 n in v v {(x i ) i=1 n } FV(v)= (14.7) Rekursiiviset tyypit lisätään lambda-kieleen ottamalla käyttöön tyyppioperaattori µ: α TyVars T, U ::= α µα T Letrecin laajentaminen rekursiivisten tyyppien määrittelemiseksi on toki mahdollista, mutta sivuutetaan se tässä. Esimerkiksi lukulistan tyyppi voitaisiin kirjoittaa µα null : (), notnull : (Num, α) ; operaattorin µα vaikutusalueella tyyppimuuttuja α edustaa koko pitkää µ:lla alkavaa tyyppilauseketta. Esimerkki 6. NumList = µα null : (), notnull : (Num, α) sum = µ f : NumList Num λl : NumList case l of null = x 0 notnull = x let (a, r) = x in a + f r Sama voidaan toki ilmaista myös letrecin avulla: sum = letrec f : NumList Num = λl : NumList case l of in f null = x 0 notnull = x let (a, r) = x in a + f r Esimerkissä toplevel-nimet ovat vain lyhennysmerkintöjä; rekursiota niiden kautta ei sallita. Tämän laajennuksen tyypitys- ja laskentasäännöt vaativat jonkin verran pohdintaa. Olisi nimittäin kiva, jos tyyppien yhtäläisyys noudattaisi kiintopisteperiaatetta µα T[α] = T[µα T[α]]

150 LUKU 14. REKURSIIVISET TYYPIT missä T on tyyppikonteksti. Lukulistan tapauksessa sama kiintopisteyhtälö saa muodon µα null:(),notnull:(num,α) = null:(),notnull:(num,µα null:(),notnull:(num,α) ) Rekursiivisten tyyppien ongelma on, että on varsin hankalaa määritellä algoritmi, joka toteuttaa sellaisen tyyppien yhtäläisyysvertailun, jolle tuo yhtälö pätee. Algoritmi, joka toteuttaa tämän yhtälön sellaisenaan, toteuttaa ekvirekursion (engl. equirecursion). 14.2 Nimiyhtäläisyys Yksinkertaisin ja ohjelmointikielissä yleisin tapa ratkaista rekursiivisuuden ongelma on vaatia, että jokainen tyyppi nimetään, ja sallia tällaisten nimettyjen määritelmien keskinäinen rekursio (yleensä siten rajoitettuna, että rekursion tulee kulkea osoittimen kautta, mikäli kielessä on erillinen osoitintyyppi). Lisäksi tässä ratkaisussa julistetaan, että kaksi eri nimistä tyyppiä ovat eri tyyppejä vaikka niillä olisikin sama rakenne. Tätä ratkaisutapaa sanotaan nimiyhtäläisyydeksi (engl. name equivalence). 14.3 Isorekursio Hieman monimutkaisempi ja edellä annettuun lambda-kielen laajennokseen sopiva mutta kuitenkin täyttä ekvirekursiota helpompi ratkaisu on isorekursio (engl. isorecursion) 1. Ajatuksena on, että ohjelmoija merkitsee näkyviin paikat, missä on tarpeen tehdä muunnos tyypistä µα T[α] tyyppiin T[µα T[α]] ja takaisin käyttämällä kielen sisään rakennettavia konversiofunktioita unfold µα T[α] : µα T[α] T[µα T[α]] fold µα T[α] : T[µα T[α]] µα T[α] Nyt edellä esimerkkinä annettu sum-funktio jouduttaisiin kirjoittamaan seuraavasti: sum = µ f : NumList Num λl : NumList case unfold NumList l of null = x 0 notnull = x let (a, r) = x in a + f r 1 Etuliite iso- ei tässä viittaa kokoon vaan isomorfismiin.

14.4. EKVIREKURSIO 151 Toisaalta käytännön ohjelmointikielessä tämä ei ole niin ongelmallista, koska fold- ja unfold-funktiot voidaan niissä piilottaa osaksi kielen muuta sotkuisuutta. Esimerkiksi jos kieli vaatii, että tyyppirekursion tulee aina kulkea jonkin varianttityypin kautta, 2 voidaan varianttityypin käsittelyn sisälle piilottaa tarvittavat fold- ja unfold-operaatiot. Isorekursiiviset tyypit voidaan määritellä seuraavasti: α TyVars T, U ::= α µα T t, u ::= fold T unfold T Nyt myös tyypeille pitää määritellä korvausoperaattori T[α := U] ja vapaat muuttujat FV(T). Määritelmät kirjoitetaan samaan tapaan kuin termeille. Γ unfold T : T U[α := T] Γ fold T : U[α := T] T T=µα U (14.8) T=µα U (14.9) unfold T (fold T v) v (14.10) 14.4 Ekvirekursio Ekvirekursiivinen lähestymistapa on kaikista simppelein ohjelmoijan kannalta, mutta vaatii varsin raskasta koneistoa tyyppijärjestelmään. Pääongelmana on, että tyyppitarkastimen pitää nyt päätellä, mihin fold- ja unfoldoperaatiot kuuluvat, ilman ohjelmoijalta saatavaa apua. Ekvirekursiivisesti tyypitetty lambda-kieli voidaan määritellä seuraa- 2 Tämä ei ole kovinkaan vakava vaatimus, sillä käytännössä tyyppirekursio joka tapauksessa kulkee (lähes) aina variantin kautta.

152 LUKU 14. REKURSIIVISET TYYPIT vasti: α, β, γ TyVars x, y, z Vars T, U ::= T U α µα T t, u ::= x tu λx : T t Γ, x : T x : T Γ t : U 1 T Γ u : U 2 U 1 U 2 Γ tu : T Γ, x : T t : U Γ (λx : T t) : T U (14.11) (14.12) (14.13) Tyypityssäännöt ovat siis samat kuin yksinkertaisesti tyypitetyssä lambdakielessä lukuunottamatta applikaation sääntöä. johon on lisätty eksplisiittinen tyyppien yhtäläisyysvaatimus. Laskentasäännöt eivät muutu, joten niitä ei tässä tarvitse toistaa. Ekvirekursiivisten tyyppien yhtäläisyyden idea on avata µ-tyypit äärettömiksi tyypeiksi ja sitten verrata, ovatko nämä äärettömät tyypit samoja. Yksinkertaistaen siis: expand(α) = α expand(t U) = expand(t) expand(u) expand(µα T) = T[α := expand(µα T)] T U expand(t) = expand(u) Käytännössä tämä on tietenkin käyttökelvoton algoritmi, sillä se vaatisi äärettömän tietorakenteen läpikäymistä. Brandt ja Henglein (1998) esittävät yhden mahdollisen algoritmin asian ratkaisemiseksi. Seuraavissa päättelysäännöissä Γ on tyyppiparien (joita merkitään T U) joukko. Γ T T (14.14)

14.5. REKURSIIVISTEN TYYPPIEN SEURAUKSIA 153 Γ, T U T U Γ U T Γ T U (14.15) (14.16) Γ T 1 T 2 Γ T 2 T 3 Γ T 1 T 3 (14.17) Γ µα T T[α := µα T] (14.18) T 2 Γ, T 1 T 2 U 1 U 2 T 1 U 1 Γ, T 1 T 2 U 1 U 2 T 2 U 2 Γ T 1 U 1 U 2 (14.19) Päättelysäännöstöstä on mahdollista johtaa seuraavanlainen algoritmi: Algoritmi 12 (Brandt ja Henglein (1998)). Syötteenä tyyppiparien joukko Σ sekä kaksi tyyppiä, tuloksena joko tosi tai epätosi. Sovelletaan järjestyksessä ensimmäistä sopivaa yhtälöä: S(Σ, µα.t, U) = S(Σ, T[α := µα.t], U) S(Σ, T, µα.u) = S(Σ, T, U[α := µα.u]) S(Σ, T, U) = true (1) S(Σ, T T, U U ) = S(Σ, T, U) S(Σ, T, U ) (2) S(Σ, α, α) = true (3) S(Σ T, U) = false (1) jos (T, U) Σ (2) missä Σ = Σ {(T T, U U )} (3) α I Algoritmin (ja sen oikeellisuustodistuksen) taustalla on koinduktiivinen metodi, joka sivuutetaan tässä. 14.5 Rekursiivisten tyyppien seurauksia Esimerkki 7 (Nälkäiset funktiot). Tarkastellaan tyyppiä Hungry = µα Num α. Yksi mahdollinen tuohon tyyppiin kuuluva funktio on f = µ f : Num Hungry λx : Num f. Tällainen funktio tarvitsee äärettömän monta argumenttia.

154 LUKU 14. REKURSIIVISET TYYPIT Esimerkki 8 (Virrat). Hyödyllisempi on tyyppi NumStream = µα () (Num, α) joka palauttaa joka kutsulla luvun ja uuden virran: ones = µ f : NumStream λx : () (1, f ) nats = (µ f : Num NumStream λx : Num λy : () (x, f (x + 1))) 0 Esimerkki 9 (Yksinkertaiset oliot). Määritellään tyyppi Counter = µα { get : Num, inc : () α } Nyt voidaan määritellä tehdasfunktio createcounter = µ f : { x : Num } Counter λs : { x : Num } { get = s.x, inc = λy : () f ({ x = s.x + 1 }) } ja sen avulla olio createcounter { x = 0 }. Tällä oliolla on tosin kaksi puutetta: sillä ei ole tilasta riippumatonta identiteettiä (jokainen muutos luo uuden olion) ja alityypitys ei luonnistu. Kumpikin ongelma on ratkaistavissa identiteetti käyttämällä osoitintyyppiä, alityypityksestä puhutaan myöhemmällä luennolla. Mainittakoon vielä, että rekursiivisesti tyypitetty lambda-kieli on yhtä vahva kuin tyypittämätön lambda-kieli, eli siinäkin on mahdollista määritellä kiintopisteoperaattori muiden ominaisuuksien avulla.