Luku 7. Aliohjelmat. 7.1 Kutsusekvenssit. Aliohjelma (subroutine) on useimpien kielten tärkein kontrollivuon ohjausja abstrahointikeino.

Samankaltaiset tiedostot
TIES542 kevät 2009 Aliohjelmien formalisointia lambda-kieli

4.2. ALIOHJELMAT 71. Tulosvälitteisyys (call by result) Tulosvälitteinen parametri kopioidaan lopuksi

Aliohjelmat. 1 Kutsusekvenssit. Antti-Juhani Kaijanaho 5. helmikuuta 2007

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

kontrollivuon analyysejä optimointiensa tueksi ja myös tiettyjen merkitysopillisten

TIEA341 Funktio-ohjelmointi 1, kevät 2008

formalismeja TIEA241 Automaatit ja kieliopit, syksy 2015 Antti-Juhani Kaijanaho 15. joulukuuta 2015 TIETOTEKNIIKAN LAITOS

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

Lisää pysähtymisaiheisia ongelmia

Yksinkertaiset tyypit

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

Luku 2. Ohjelmointi laskentana. 2.1 Laskento

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

Luento 4 (verkkoluento 4) Aliohjelmien toteutus

11/20: Konepelti auki

14.1 Rekursio tyypitetyssä lambda-kielessä

Luento 4 (verkkoluento 4) Aliohjelmien toteutus

Jatkeet. TIES341 Funktio ohjelmointi 2 Kevät 2006

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

Lisää laskentoa. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Jakso 4 Aliohjelmien toteutus

Aliohjelmatyypit (2) Jakso 4 Aliohjelmien toteutus

2) Aliohjelma, jonka toiminta perustuu sivuvaikutuksiin: aliohjelma muuttaa parametrejaan tai globaaleja muuttujia, tulostaa jotakin jne.

Jakso 4 Aliohjelmien toteutus

Luento 4 Aliohjelmien toteutus

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

Ydin-Haskell Tiivismoniste

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

TIES542 kevät 2009 Kontrollivuon ohjaus

Chomskyn hierarkia. tyyppi 0 on juuri esitelty (ja esitellään kohta lisää) tyypit 2 ja 3 kurssilla Ohjelmoinnin ja laskennan perusmallit

Ohjelmoinnin peruskurssien laaja oppimäärä

Staattinen tyyppijärjestelmä

Algoritmit 1. Luento 3 Ti Timo Männikkö

Turingin koneet. Sisällys. Aluksi. Turingin koneet. Turingin teesi. Aluksi. Turingin koneet. Turingin teesi

Aliohjelmatyypit (2) Jakso 4 Aliohjelmien toteutus

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

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

TIES542 kevät 2009 Rekursiiviset tyypit

Tietorakenteet ja algoritmit

Ohjelmoinnin peruskurssien laaja oppimäärä

Rekursiiviset palautukset [HMU 9.3.1]

Jakso 4 Aliohjelmien toteutus

Tutoriaaliläsnäoloista

Jakso 4 Aliohjelmien toteutus. Tyypit Parametrit Aktivointitietue (AT) AT-pino Rekursio

Johdatus diskreettiin matematiikkaan Harjoitus 5, Ratkaise rekursioyhtälö

TIES542 kevät 2009 Denotaatio

Luento 4 Aliohjelmien toteutus

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

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

vaihtoehtoja TIEA241 Automaatit ja kieliopit, syksy 2016 Antti-Juhani Kaijanaho 13. lokakuuta 2016 TIETOTEKNIIKAN LAITOS

Luento 4 Aliohjelmien toteutus

ICS-C2000 Tietojenkäsittelyteoria Kevät 2016

Luento 4 Aliohjelmien toteutus. Tyypit Parametrit Aktivointitietue (AT) AT-pino Rekursio

Insinöörimatematiikka A

Todistus: Aiemmin esitetyn mukaan jos A ja A ovat rekursiivisesti lueteltavia, niin A on rekursiivinen.

5.3 Ratkeavia ongelmia

1. Universaaleja laskennan malleja

Johdatus λ-kalkyyliin

Täydentäviä muistiinpanoja Turingin koneiden vaihtoehdoista

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

M = (Q, Σ, Γ, δ, q 0, q acc, q rej )

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

on rekursiivisesti numeroituva, mutta ei rekursiivinen.

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 8. syyskuuta 2016

Rajoittamattomat kieliopit (Unrestricted Grammars)

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

A TIETORAKENTEET JA ALGORITMIT

Monipuolinen esimerkki

Laskennan rajoja. TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 10. joulukuuta 2015 TIETOTEKNIIKAN LAITOS.

Ohjelmoinnin peruskurssien laaja oppimäärä

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

uv n, v 1, ja uv i w A kaikilla

Ongelma(t): Miten mikro-ohjelmoitavaa tietokonetta voisi ohjelmoida kirjoittamatta binääristä (mikro)koodia? Voisiko samalla algoritmin esitystavalla

811120P Diskreetit rakenteet

Ohjelmoinnin peruskurssien laaja oppimäärä

ITKP102 Ohjelmointi 1 (6 op)

S BAB ABA A aas bba B bbs c

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

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssien laaja oppimäärä

Tietorakenteet ja algoritmit

Algoritmit 2. Luento 13 Ti Timo Männikkö

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

TIEP114 Tietokoneen rakenne ja arkkitehtuuri, 3 op. Assembly ja konekieli

Matematiikan peruskurssi 2

Chomskyn hierarkia ja yhteysherkät kieliopit

Scheme-kesäkurssi luento 3

3. Muuttujat ja operaatiot 3.1

Tietojenkäsittelyteorian alkeet, osa 2

Matematiikan tukikurssi, kurssikerta 3

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

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 7. joulukuuta 2009

Matematiikan tukikurssi

Rekursiiviset tyypit

Algoritmin määritelmä [Sipser luku 3.3]

Transkriptio:

Luku 7 Aliohjelmat Aliohjelma (subroutine) on useimpien kielten tärkein kontrollivuon ohjausja abstrahointikeino. 7.1 Kutsusekvenssit Aliohjelmaan kontrolli siirtyy sen kutsun (call) kautta. Kun aliohjelman suoritus päättyy, kontrolli siirtyy takaisin sinne, missä kutsu tehtiin. Tämä johtaa siihen, että aliohjelmien kutsurakenne on lifo-tyyppinen (last in, first out), ja toteutustekniikaksi sopii mainiosti pino. Mikäli aliohjelma ei ole (suoraan tai epäsuorasti) rekursiivinen, myös staattisesti varattu tila aliohjelman tiedoille käy. Konekielitasolla kutsuoperaatio laittaa aktivaatiopinoon (activation stack) itseään seuraavan käskyn osiotteen ja sitten siirtää kontrollin aliohjelmalle (eli hyppää aliohjelman alkuun, suorittaa go to -lauseen, jonka kohteena on aliohjelman alku). Viimeisenä toimenaan aliohjelma ottaa pinosta päällimmäisenä olevan osoitteen ja hyppää tuohon osoitteeseen. Tavallisesti aliohjelmat ovat parametrisoituja. Parametrisoidun aliohjelman kutsu välittää aliohjelmalle dataa, jota aliohjelman odotetaan käyttävän hyväksi. Tätä dataa kutsutaan aliohjelman argumenteiksi (argument) kielitasolla kyse on lausekkeista, joiden arvo annetaan aliohjelmalle. Myös argumentit on tapana antaa aliohjelmalle pinon välityksellä. Konekielitasolla siis kutsuoperaatio laittaa pinoon paluuosoitteen jälkeen viimeisen argumentin, sitten toiseksi viimeisen argumentin, ja lopulta se laittaa pinoon ensimmäisen argumentin. Sitten se suorittaa hypyn. Joissakin koneissa argumentit sijoitetaan pinoon ennen paluuosoitetta, joissakin koneissa ne laitetaan rekistereihin, joissakin myös paluuosoite laitetaan rekisteriin. Tällaiset eroavuudet ovat väistämättömiä. Säännöt 71

72 LUKU 7. ALIOHJELMAT siitä, mitä pinossa ja rekistereissä tarkkaan ottaen tulee olla kutsuun liittyvän hypyn hetkellä, sekä siitä, miten kutsun jäljet siivotaan aliohjelman päätyttyä, muodostavat kutsusekvenssin (calling sequence), joka on osa sovellusbinäärirajapintaa (engl. application binary interface, ABI). On tärkeää, että kutsuja ja aliohjelma noudattavat samaa kutsusekvenssiä. Tärkeää on nyt huomata, että paluuosoite voidaan ajatella aliohjelmalle annettuna ylimääräisenä, viimeisen argumentin jälkeen tulevana argumenttina. Näin aliohjelmakutsu on tulkittavissa argumentteja välittävänä go to -lauseena Steele (1977)! Tämä havainto on tärkeä, sillä kutsun tulkitseminen hypyksi mahdollistaa monia merkittäviä optimointeja. Esimerkiksi jos jokin aliohjelma kutsuu toista aliohjelmaa viimeisenä tekonaan, voidaan tätä yksinkertaistaa reilusti: ensiksi se poistaa itselleen tulleet argumentit (paluuosoitetta lukuunottamatta) pinosta, sitten se laittaa kutsun argumentit (paluuosoitetta lukuunottamatta) pinoon. Pino on nyt sellaisessa tilassa, että näyttäisi kuin tämän aliohjelman kutsuja olisi tekemässä tätä kutsua. Nyt voidaan tehdä hyppy kutsuttavaan aliohjelmaan. Koska pinossa oleva paluuosoite on suoraan kutsuvan aliohjelman kutsujaan, ei hypyn jäljessä tarvitse olla enää yhtään koodia, aliohjelma päättyy tuohon hyppyyn. Myöskin on niin, että kutsuvasta aliohjelmasta ei jää merkkiäkään pinoon, jolloin tällaisia häntäkutsuja (tail call) voidaan tehdä loputtomasti ilman, että pinon koko kasvaisi sen seurauksena rajattomasti. Tätä optimisaatiota sanotaan häntäkutsun poistoksi (tail call elimination). Sillä on myös merkitysopillista merkitystä. Kun häntäkutsu ei kasvata pinon kokoa, sitä voidaan käyttää iteraation ilmaisemiseen. Tunnetuin esimerkki kielestä, jossa iteraatio ilmaistaan häntärekursiona (tail recursion), on Scheme, jonka määrittelydokumentti vaatii jokaiselta Schemetoteutukselta luotettavan häntäkutsun poiston. Schemessä olisi esimerkiksi kertoman laskeminen luonnollista ilmaista näin: (define (! n) (define (loop i accu) (if (eq? i 1) accu (loop (- i 1) (* accu i)))) (loop n 1)) Jotkut aliohjelmat palauttavat (return) tietoa kutsujalle päätyttyään. Tämä tieto laitetaan paluun yhteydessä pinoon paluuosoitteen tilalle (joissakin koneissa se laitetaan rekisteriin). Aliohjelmaa, joka palauttaa tietoa kutsujalle, sanotaan funktioksi (function). Muut aliohjelmat ovat proseduureja (procedure). Joissakin kielissä kaikkii aliohjelmat ovat funktioita,

7.2. PARAMETRINVÄLITYSMEKANISMIT 73 ja paluuarvoton tilanne hoidetaan palauttamalla arvo, jolla ei ole väliä. Joissakin kielissä on erityinen tyyppi tällaista paluuarvoa varten. Funktion määrittelyssä on kieliopillinen pulma. Onko funktion runko lauseke vai lause? Jos se on lauseke, funktion paluuarvo on luonnollisesti tuon lausekkeen arvo, mutta jotta tällainen funktio olisi hyödyllinen, tulee lausekkeisiin sisältyä kaikki tarvittavat kontrollia ohjaavat rakenteet. Jos se on lause (yleensä lohko), niin on ratkaistava, miten paluuarvo ilmaistaan. Joissakin kielissä (esimerkiksi Pascal) funktion nimeen sijoittaminen ilmaisee paluuarvon. Toisissa kielissä (kuten C-sukuiset) on erityinen return-lause, jolla funktiosta poistutaan paluuarvo ilmaisten. 7.2 Parametrinvälitysmekanismit Edellä mainittiin aliohjelman argumentit, ne datat, jotka aliohjelmalle annetaan ne ovat lausekkeita, jotka ovat osa aliohjelmakutsua. Asian toinen puoli on se, että aliohjelman sisällä nämä näkyvät paikallisina muuttujina. Niitä sanotaan aliohjelman parametreiksi (parameter). Niinpä siinä missä kutsuja näkee argumentteja, kutsuttava näkee parametreja. Toisinaan argumentista käytetään nimeä todellinen parametri (actual parameter), jolloin parametria sanotaan muodolliseksi parametriksi (formal parameter). Mekanismia, jolla argumentti ja parametri kytketään toisiinsa, sanotaan parametrinvälitysmekanismiksi (parameter passing mechanism). Seuraavat neljä mekanismia ovat tavallisimmat: Arvovälitteisyys (call by value) Arvovälitteinen parametri on kopio argumentista: parametrin arvo on aliohjelman alussa argumentin arvo, ja parametriin tehdyt muutokset eivät näy argumentissa. Tulosvälitteisyys (call by result) Tulosvälitteinen parametri kopioidaan lopuksi argumentiksi: parametrin arvo on aliohjelman alussa määräämätön, mutta siihen tehdyt muutokset välitetään argumenttiin kopioimalla parametri argumenttiin aliohjelman palatessa. Arvo tulosvälitteisyys (call by value result) Arvo tulosvälitteinen parametri on kopio argumentista, ja se kopioidaan lopuksi argumentiksi: parametrin arvo on aliohjelman alussa argumentin arvo, ja parametriin tehdyt muutokset välitetään argumenttiin kopioimalla parametri argumenttiin aliohjelman palatessa.

74 LUKU 7. ALIOHJELMAT Viitevälitteisyys (call by reference) Argumentti ja parametri ovat sama olio, joten kaikki parametriin tehdyt muutokset näkyvät välittömästi argumentissa. Nykyaikana näistä tavallisimmat ovat arvovälitteisyys ja viitevälitteisyys. Harvinaisempi (ja virhealttiimpi) mekanismi on nimivälitteisyys (call by name). Idea on se, että argumenttilauseketta ei lasketa kutsuhetkellä, vaan ohjeet sen laskemiseksi annetaan aliohjelmalle. Kun aliohjelma käyttää parametria, se laskee argumenttilausekkeen arvon. Tämä tehdään uudestaan joka kerta, kun parametria käytetään. Sivuvaikutusten kanssa tällainen voi aiheuttaa kaikenlaista vinkeää (mm. ns. Jensenin temppu) ja myös ikävää (koetapa kirjoittaa toimiva swap-aliohjelma). Toimivampi mutta varsin harvinainen versio nimivälitteisyydestä on nimeltään tarvevälitteisyys (call by need). Idea on sama kuin nimivälitteisyydessä, mutta tarvevälitteisyydessä huolehditaan siitä, että jokainen tarvevälitteinen parametri lasketaan samalla aktivaatiokerralla korkeintaan kerran seuraavilla käyttökerroilla käytetään aiemman laskennan tulosta. Tarvevälitteisyyttä kutsutaan toisinaan laiskaksi laskennaksi (lazy evaluation). Logiikkaohjelmointikielissä kuten Prolog tai Mercury on käytössä vielä yksi muu mekanismi, unifikaatio (unification). Siitä puhutaan tuonnempana. 7.3 Aktivaatiotietue Pinoon, tai joissakin tapauksissa kekoon, aliohjelmakutsun yhteydessä rakentuva tietue on nimeltään aktivaatiotietue (activation record) tai kehys (frame). Jokainen kerta, kun aliohjelmaa kutsutaan, syntyy uusi aliohjelman aktivaatio (activation), ja sen aktivaatiotietue sisältää kaiken sen tiedon, mitä tuosta nimenomaisesta aktivaatiotiedosta on pidettävä yllä ja erillään muista aktivaatioista. Joissakin tilanteissa aktivaatiotietue voi olla staattinen olio; tällöin kyseinen aliohjelma ei voi olla rekursiivinen eikä vapaakäyntinen (reentrant). Joissakin kielten toteutuksissa aktivaatiotietue on kekodynaaminen olio joskus tämä on jopa välttämätöntä. Aktivaatiotietueen paikka ei ole tavallisesti tiedossa ennen suorituksen alkua (se on tiedossa vain, jos aktivaatiotietue on staattinen olio). Sen sijaan sen osien sijainti suhteessa tietueen alkuun on hyvinkin tiedossa ennen suorituksen alkua. Senpä takia pääsy aktivaatiotietueessa oleviin tietoihin järjestetään käyttäen lähes jokaisesta prosessorista löytyvällä rekisteri ja siirtymä -osoituksella. Kielen toteutus varaa yhden rekisterin osoit-

7.3. AKTIVAATIOTIETUE 75 tamaan sen aliohjelman aktivaatiotietuetta, jolla kontrolli on. Tätä rekisteriä sanotaan kehysosoitinrekisteriksi (frame (pointer) register) tai kantaosoitinrekisteriksi (base pointer register). Kun jotain toista aliohjelmaa kutsutaan, tulee kehysosoitinrekisterin sisältö tallentaa jonnekin. Käytännössä se tallennetaan aliohjelman alkaessa kutsutun aliohjelman omaan aktivaatiotietueeseen, ja se palauttaa rekisterin arvon ennen paluutaan. Tätä tallennettua kehysosoitinrekisterin arvoa sanotaan dynaamiseksi linkiksi (dynamic link) sitä voitaisiin käyttää myös esimerkiksi dynaamisen näkyvyysalueen toteuttamiseen. Kaikki ne aktivaatiotietueet, joihin pääsee dynaamisten linkkien kautta siitä tietueesta, jonka osoite on kehysosoitinrekisterissä, muodostavat kutsupinon (call stack) eli aktivaatiopinon (activation stack) eli dynaamisen ketjun (dynamic chain). Joissakin kielissä aliohjelmia voidaan kirjoittaa toisten aliohjelmien sisään. Mikäli tällaisessa kielessä on käytössä staattinen näkyvyysalue, tulee jotenkin aliohjelmasta olla pääsy sitä staattisesti ympäröivien aliohjelmien sopivan aktivaation paikallisiin muuttujiin (eli aktivaatiotietueisiin). Yksinkertaisessa tapauksessa tällaista aliohjelmaa voidaan kutsua vain itsestään, siitä aliohjelmasta, jossa se on määritelty, sekä niistä aliohjelmista, jotka on määritelty sen itsensä sisällä (staattisessa mielessä). Tällaisessa tilanteessa luonteva valinta staattisesti ympäröivän aliohjelman aktivaatioksi, johon pitää päästä käsiksi, on se, jossa kutsu tapahtuu. Kun kutsu tapahtuu, annetaan aliohjelmalle piilotettuna argumenttina osoitin tähän aktivaatiotietueeseen. Tällöin tuo osoite jää osaksi kutsutun aliohjelman aktivaatiotietuetta; tuota osoitetta sanotaan staattiseksi linkiksi (static link). Aktivaatiotietueet, joihin pääsee staattisten linkkien kautta siitä tietueesta, jonka osoite on kehysosoitinrekisterissä, muodostavat staattisen ketjun (static chain). Staattisen näkyvyysalueen ollessa voimassa mihin tahansa näkyvään paikalliseen muuttujaan pääsee käsiksi hakemalla sitä staattisen ketjun kautta. Kussakin tilanteessa tiedetään ennen suorituksen alkua, kuinka syvällä staattisessa ketjussa kukin muuttuja on, ja tämän perusteella voidaan muuttujanhakukoodi generoida suoraviivaisesti: ladataan johonkin rekisteriin ensin kehysosoitinrekisterin arvo, sitten ladataan tuon osoittimen kautta staattinen linkki tuohon rekisteriin ja iteroidaan tätä tarpeeksi monta kertaa. Lopuksi tuota rekisteriä voidaan käyttää kuin kehysosoitinrekisteriä ikään hakemaan löydetyn aktivaatiotietueen sisältämä muuttuja. Mikäli aliohjelma voidaan antaa argumenttina toiselle aliohjelmalle, monimutkaistuu staattisen linkin hakeminen. Tällöin käytännössä argumenttina antajan (joka on siinä asemassa, kuin edellä oli aliohjelman kut-

76 LUKU 7. ALIOHJELMAT suja) tulee antaa argumenttina (piilossa) myös staattinen linkki, jotta sen saaja voisi kutsua aliohjelmaa antaen sille tuo staattinen linkki (omaansa se ei yleisessä tapauksessa voi antaa). Joissakin kielissä aliohjelmat ovat täysivaltaisia (first class), eli niitä voidaan tallentaa muuttujiin, viedä parametrina ja palauttaa paluuarvona vapaasti. Tällöin aliohjelmalla tulee olla olio, joka sitä edustaa muuttujissa, aliohjelman paluuarvona ja argumenttina. Mikäli kielessä aliohjelmat eivät voi olla sisäkkäisiä, pelkkä aliohjelman koodin alun osoite riittää olion sisällöksi. Näin toimitaan esimerkiksi C:ssä (funktio-osoitin). Muussa tapauksessa oliossa pitää tuon osoittimen lisäksi olla myös staattinen linkki, joka annetaan kutsuttaessa aliohjelmalle piiloargumenttina. Tällaista oliota sanotaan toisinaan sulkeumaksi (closure). 7.4 Vuorottaisrutiinit Sukua aliohjelmakäsitteelle on sellainen käsite kuin vuorottaisrutiini (coroutine). Vuorottaisrutiinia kutsutaan kuten aliohjelmaa, ja siinä syntyy vastaavalla tavalla aktivaatiotietue. Ero on siinä, että vuorottaisrutiini voi palauttaa kontrollin väliaikaisesti kutsujalleen; tällöin kutsuja voi toimia, kunnes se antaa kontrollin takaisin vuorottaisrutiinille, jolloin sen suoritus jatkuu siitä mihin se jäi. Tällainen pallottelu voi jatkua pitkäänkin, kunnes vuorottaisrutiini palaa aliohjelman tavoin lopullisesti. Vuorottaisrutiinien tuki on nykykielissä varsin harvinaista, mutta niitä muistuttaa tietorakenteiden läpikäyntiin tarkoitettu generaattoritekniikka (tunnetaan myös nimellä iteraattori), jota tukee mm. Python ja C#. Generaattori on funktio, joka voi palauttaa kontrollin väliaikaisesti kutsujalle (esimerkiksi Pythonissa yield-lauseella) välittäen samalla kutsujalle arvon. 7.5 Aliohjelmien formalisointia Aliohjelmia tukevassa kielessä ohjelma koostuu joukosta aliohjelmia sekä pääohjelmasta (joka joissakin kielissä on vakiomuotoinen ja kutsuu kiinnitetyn nimistä aliohjelmaa). Aliohjelma on olennaisesti peruskielinen (suoraviiva- tai while-kielinen, tms) ohjelma, jolla on kutsurajapinta ja paikallisia muuttujia. Yksinkertaisuuden vuoksi tarkastellaan vain yksiparametrisia aliohjelmia.

7.5. ALIOHJELMIEN FORMALISOINTIA 77 c N f, g, x, y, z Id e Expr p Prog p ::= d p s d SubDecl d ::= f (x) { x + s } s, t Stmt s, t ::= x f (e) return e x + IdList x + ::= x x +, x Sekä denotationaalisessa että operatiivisessa merkitysopissa laajennetaan ympäristön käsitettä aliohjelmien tallennusta varten: Env = Id Q {t, f, F} Clo Clo = Id P(Id) Stmt E ja B tarvitsevat tämän johdosta teknisluontoisen päivityksen. { E jos σ(x) tai σ(x) Q E x σ = σ(x) muuten { E jos σ(x) tai σ(x) {t, f } B x σ = σ(x) muuten (7.1) (7.2) Ohjelman käsittely pelkästään luetteloi aliohjelmat. Tässä voisi tehdä tarkempaakin virheentarkastusta esim. samannimisten aliohjelmien määrittelemisen esitämiseksi. P : Prog Env Env {E} P f (x){ x + s } p σ = P p (σ ( f, ( x, X x, s)) (7.3) P s σ = C s σ (7.4)

78 LUKU 7. ALIOHJELMAT X : IdList P(Id) X x + = { x } (7.5) X x +, x = X x + { x } (7.6) V : Expr Env Q {t, f, E} E e σ jos E e σ = E V e σ = B e σ jos B e σ = E E muuten (7.7) Aliohjelmakutsun esittäminen denotationaalisesti vaatisi varsin monimutkaisen kiintopistetarkastelun, joka sivuutetaan tässä. Operationaalisesti toimitaan seuraavasti: ( ) s, σ { y σ(y) Clo } (Y {0}) {(x, V e σ)} (return c, σ ) ( z f (e), σ) σ {( z, c )} ( f, (x, Y, s)) σ V e σ = E (7.8) ( return e, σ) ( return c, σ) c = E e σ (7.9) ( return e ; s, σ) ( return e, σ) (7.10) Aliohjelman runko suoritetaan siis väliaikaisessa ympäristössä, josta on poistettu kutsujan muuttujat ja johon on lisätty parametri ja aliohjelman paikalliset muuttujat.

Luku 8 Lambda-kieli Lambda-kielen taustalla on 1900-luvun alkuvuosikymmenien matematiikan perusteiden tutkimus ja siihen liittyvä logiikan kehittyminen. Noiden aikojen suuri kysymys oli, voidaanko koko matematiikka formalisoida mekaaniseksi laskentajärjestelmäksi niin, ettei siihen jää piileviä ristiriitoja 1 Alonzo Church oli yksi heistä, joka pyrki tämän ongelman ratkaisemaan. Hänen ratkaisuehdotelmaansa kehittivät eteenpäin myös hänen oppilaansa Stephen C. Kleene ja J. Barkley Rosser. Kleene ja Rosser osoittivat varsin pian, että Churchin järjestelmä on logiikaksi käyttökelvoton (ristiriitainen). Church oli kuitenkin jo oivaltanut, että se kykenee määrittelemään kaikki mekaanisesti laskettavissa olevat funktiot, ja vain ne. Käyttäen tätä ajatusta hyväkseen hän ratkaisi loogiikkoja monta kymmentä vuotta vaivanneen ratkeavuusongelman (ns. Entscheidungsproblem) Church osoitti, että ei ole mahdollista mekaanisesti selvittää, onko jokin mielivaltainen looginen väittämä tosi vai ei. 2 Churchin järjestelmä, kun siitä on siivottu ns. illatiiviset osat eli ne, jotka tarvitaan logiikkapuuhasteluun, on nimeltään lambda-kieli taikka lambda-laskento (engl. lambda calculus), ja se on yksi maailman vanhimmista ohjelmointikielistä kehitetty ennen ohjelmoitavia tietokoneita. Nykyaikana lambda-kieli on yksi tärkeimmistä teoreettisen ohjelmointikielitutkimuksen työkaluista. Esimerkiksi staattisten tyyppijärjestelmien teo- 1 Edelleen varsin tunnettu, yksi vanhimmista alkuaikojen loogisten järjestelmien piilevistä ristiriidoista on Bertrand Russellin paradoksi: kuuluuko se joukko itseensä, johon kuuluvat kaikki ne joukot, jotka eivät kuulu itseensä? 2 Saman ongelman ratkaisi suunnilleen samoihin aikoihin myös muuan Alan Turing, käyttäen nykyisin Turingin koneena tunnettua formalismia. Church ehti julkaista ensin, mutta Turing saa nykyisin suuremman osan kunniasta ja jopa tuo ongelmatyyppi tunnetaan nykyisin nimellä (Turingin koneen) pysähtymisongelma (engl. halting problem). Tämä johtunee siitä, että Turingin kone on vakuuttavampi mekaanisen laskennan malli kuin Churchin järjestelmä. 79

80 LUKU 8. LAMBDA-KIELI ria on kehittynyt pääosin lambda-kielen ympärille. Funktiokielet puolestaan ovat kaikki olennaisesti laajennettuja lambda-kieliä. Lambda-kielen keskeinen idea on mallittaa matemaattinen funktio (ja samalla myös aliohjelma) ymmärrettynä laskentaohjeena. Kielestä on siivottu pois häiritsemästä kaikki muu. Yllättäen kieli on tästä huolimatta erittäin ilmaisuvoimainen. Lambda-kielen alkuperäiset lähteet ovat Church (1932, 1933), Kleene (1934, 1935), Church (1936). Klassikkokirjoja lambda-kielestä ovat Church (1985) ja Curry ja Feys (1968). Kattava teos lambda-kielen teoriasta on Barendregt (1984). 8.1 Syntaktiset määritelmät Lambda-kielen abstrakti syntaksi on seuraavanlainen: x, y, z Var t, u Term t, u ::= x tu λxt Joukon Term alkioita sanotaan usein (lambda-)termeiksi. Termin λxt (abstraktio) intuitiivinen tulkinta on termi t muuttujan x funktiona. Vastaavasti termitu (applikaatio) tarkoittaa intuitiivisesti funktion t arvo kohdassa u. Lambda-kielessä ei ole moniparametrisia funktioita, vaan niitä simuloidaan curryamalla (λxλyt)u 1 u 2. Applikaatio tavanomaisesti assosioi vasemmalle ja abstraktio oikealle; applikaatiolla on suurempi presedenssi kuin abstraktiolla. Varsin usein käytetään lyhennysmerkintää λx 1 x 2 x n. t termin λx 1 λx 2 λx n t asemesta. Huomaa, kuinka lyhennysmerkinnässä muuttujalistan jälkeen tulee piste. Esimerkki 3. 1. λxx on intuitiivisesti identiteettifunktio. Siitä käytetään toisinaan nimeä I. 2. λxy.x on funktio, joka palauttaa vakiofunktion. Siitä käytetään toisinaan nimeä K. 3. λxyz.xz(yz) on nimeltään S.

8.1. SYNTAKTISET MÄÄRITELMÄT 81 Termit λx.xz ja λy.yz ovat intuitiivisesti samoja, mutta termit λx.xz ja λx.xy eivät ole. Syynä tähän on se, että λn jälkeen tulevan muuttujan nimellä ei ole mitään merkitystä tilanne on sama kuin esimerkiksi integraalissa 2 1 f (x) dx = 2 1 f (y) dy. Tätä eroa merkitsemään on luotu kaksi termiä vapaa muuttuja (engl. free variable) (kuten y termissä λxxy) ja sidottu muuttuja (engl. bound variable) (kuten x termissä λxxy). Vapaasta muuttujast sanotaan myös, että se esiintyy vapaana (engl. occurs free) termissä. Muuttuja esiintyy vapaana jossain termissä, jos se ylipäätään esiintyy kyseisessä termissä ja ainakin yksi sen esiintymistä ei ole sellaisen λn alla, jonka vieressä kyseinen muuttuja on. Muodollisesti määritellään (meta)funktio FV : Term P(Var) seuraavasti: FV(x) = {x} (8.1) FV(tu) = FV(t) FV(u) (8.2) FV(λxt) = FV(t) \ {x} (8.3) FV(t) on siis t:ssä esiintyvien vapaiden muuttujien joukko. Jatkossa tullaan tarvitsemaan myös korvausoperaattoria (engl. substitution operator), joka on (meta)funktio Term Var Term Term. Sen tulosta merkitään tässä monisteessa t[x := u], ja se korvaa kaikki x:n vapaat esiintymät t:ssä u:lla. Muita kirjallisuudessa esiintyviä merkintöjä ovat [x := u]t, t[u/x] ja S x u t. Korvausoperaattorin täsmällinen määritelmä on jokseenkin sotkuinen, koska sen pitää välttää ns. muuttujan kaappausta (engl. variable capture) kyse on olennaisesti paikallisen muuttujan staattisen sidonnan varmistamisesta: y[x := t] = { t y jos x = y jos x = y (8.4) (t 1 t 2 )[x := u] = t 1 [x := u] t 2 [x := u] (8.5) λyt jos x = y tai x FV(t) (λyt)[x := u] = λy(t[x := u]) jos x = y ja x FV(t) ja y FV(u) (8.6) λz(t[y := z][x := u]) jos x = y ja x FV(t) ja y FV(u) missä z on uusi muuttuja (engl. fresh variable) eli muuttuja, jota ei ole vielä käytetty mihinkään (oikeastaan riittää z = x ja z FV(u)).

82 LUKU 8. LAMBDA-KIELI 8.2 Denotationaalinen semantiikka Lambda-laskennon denotationaalinen semantiikka on erittäin yksinkertainen. Olkoon D sellainen täydellinen hila, että D sisältää kaikki Scottjatkuvat funktiot D D (tällaisen joukon olemassaolo on todistettu). Nyt voidaan määritellä semanttinen funktio seuraavasti: E : Term (Var D) D E x σ = σ(x) (8.7) { E t σ(e u σ) jos E t σ D D E tu σ = (8.8) muuten E λxt σ = f 8.3 Sievennykset missä f : D D f (a) = E t (σ {(x, a)}) (8.9) Ns. α-muunnos (engl. α-conversion) sallii sidotun muuttujan nimen vaihtamisen. Se määritellään (pienaskelsemantiikan keinoin) seuraavasti: λxt α λy(t[x := y]) t α t tu α t u Tehtävä 1. Osoita, että ( α ) on ekvivalenssirelaatio. y FV(t) (8.10) (8.11) u α u tu α tu (8.12) t α t λxt α λxt (8.13) Jos t α u pätee, sanotaan, että t ja u ovat α-ekvivalentteja. Käytännössä niitä pidetään samana terminä toisin sanoen termejä käsitellään α-muunnoksen ekvivalenssiluokkina. Koko lambda-laskennon ydin on β-sievennys (engl. β-reduction), joka määritellään seuraavasti: (λxt)u β t[x := u] t β t tu β t u (8.14) (8.15)

8.4. CHURCHIN KOODAUKSET 83 u β u tu β tu (8.16) t β t λxt β λxt (8.17) Muotoa (λxt)u sanotaan β-redeksiksi (engl. β-redex). Jos termi ei sisällä yhtään β-redeksiä, sen sanotaan olevan normaalimuoto (engl. normal form). Jos t β u pätee ja u on normaalimuoto, niin u:n sanotaan olevan t:n normaalimuoto. Normaalimuodolle voidaan antaa myös syntaktinen määritelmä: n NF n ::= s λxn s Spine s ::= s s n t pätee, sanotaan t:n ja u:n olevan β-ekviva- Jos t β u taikka u β lentteja, merkitään t = β u. Teoreema 3 (Church Rosser). Jos t = β u, niin on olemassa α-ekvivalentit t ja u siten, että t β t ja u β u pätevät. Todistus. Sivuutetaan. Churchin Rosserin-teoreemasta seuraa, että jos lauseella on ylipäätään normaalimuoto, se on yksikäsitteinen (modulo α-ekvivalenssi). 8.4 Churchin koodaukset Yllättäen lambda-laskennosta on muodostunut ohjelmointikielten teorian keskeisimmistä kulmakivistä! Tämän taustalla on muun muassa havainto, että lambda-laskennossa voi tehdä kaikenlaista kivaa. Esimerkiksi voidaan asettaa seuraavat lyhennysmerkinnät: zero = λ f x.x succ = λn f x. f (n f x) add = λmn f x.m f (n f x) mult = λmn f.m(n f )

84 LUKU 8. LAMBDA-KIELI Laskemalla muutamia esimerkkilaskuja voidaan huomata, että zero tarkoittaa nollaa, succ on funktio, joka lisää argumenttiinsa ykkösen, add on funktio, joka laskee kaksi argumenttiaan yhteen, ja mult on vastaava kertolaskufunktio. Yleisesti, jos meillä on mikä tahansa yhteen- ja kertolaskutehtävä, se voidaan koodata lambda-lausekkeeksi edellä esitettyjen määritelmien avulla, ja tämän lausekkeen normaalimuoto esittää laskutehtävän vastausta! Yleisesti tässä koodauksessa, jota kutsutaan Churchin numeraaleiksi, lukua n vastaa lambda-lauseke eli succ(succ(... (succ(zero)... ) }{{} n kpl λ f.λx. f ( f... ( f x)... ). }{{} n kpl On myös mahdollista asettaa seuraavanlaiset määritelmät: true = λxy.x false = λxy.y if = λx.x Nämä todella käyttäytyvät nimensä mukaisesti totuusarvoina ja if-lausekkeena! Ehkä kuitenkin hätkähdyttävin havainto on, että rekursiota on mahdollista simuloida lambda-laskennossa ilman, että rekursiivisia määritelmiä varsinaisesti sallittaisiin! Ideana on etsiä ns. kiintopisteoperaattori F, jolle seuraava β-ekvivalenssi pätee: Fg = β g(fg) Nimensä tällainen (hypoteettinen) kiintopisteoperaattori saa siitä, että ns. kiintopisteyhtälön x = β gx ratkaisuksi x:n suhteen kelpaa aina x = Fg. Sattuu olemaan niin, että lambda-laskennossa kiintopisteoperaattoreita on ääretön määrä; yksi niistä on Y-kombinaattoriksi kutsuttu λ f (λx f (xx))(λx f (xx)). Rekursion simulointi onnistuu kiintopisteoperaattorin avulla seuraavasti: Funktio f määritellään rekursiivisesti antamalla yhtälö f = E

8.5. LASKUJÄRJESTYKSET 85 jossa f esiintyy myös yhtälön oikealla puolella, siis lausekkeessa E, vapaana. Määritellään apufunktio f = λ f.e. Nyt funktio F f, missä F on kiintopisteoperaattori, on funktio f eli edellä annetun rekursioyhtälön ratkaisu! Koska lambda-laskento kykenee esittämään aritmetiikan ja simuloimaan rekursiota, on se Turing-täydellinen. 8.5 Laskujärjestykset Edellä määritelty β-sievennys on epädeterministinen. Vaikka Churchin Rosserin teoreema takaakin, että mahdollinen normaalimuoto on yksikäsitteinen, mikään ei takaa, että mielivaltaisessa järjestyksessä tehty sievennys päättyy. Esimerkki 4. Sievennetään argumentti ensin: (λxy) ((λxxxx)(λxxxx)) β (λxy) ((λxxxx)(λxxxx)(λxxxx)) β (λxy) ((λxxxx)(λxxxx)(λxxxx)(λxxxx)) Sievennetään uloin redeksi ensin: (λxy) ((λxxxx)(λxxxx)) β y Voidaan todistaa, että normaalijärjestys (engl. normal order) ( N ) löytää aina normaalimuodon, jos se on olemassa: (λxt)u N t[x := u] tt N t (tt )u N t u (8.18) (8.19) u N u nu N nu (8.20) t N t λxt N λxt (8.21) Normaalijärjestys siis valitsee uloimman redeksin, tai jos niitä on useita, niistä vasemmanpuolisimman.

86 LUKU 8. LAMBDA-KIELI Normaalijärjestys on turvallinen, mutta se on usein tehoton, koska se saattaa aiheuttaa argumentin kopioitumisen ja siten työmäärän tuplautumisen. Tätä tehottomuutta voidaan vähentää ns. graafisievennystä käyttäen. Käytössä on myös applikatiivinen järjestys (engl. applicative order) ( A ), jolla ei ole vastaavaa tehottomuusongelmaa: (λxn)n A n[x := n ] t A t tu A t u (8.22) (8.23) u A u nu A nu (8.24) t A t λxt A λxt (8.25) Applikatiivisen järjestyksen heikko (engl. weak) variantti, joka ei sievennä abstraktion sisällä, vastaa ohjelmointikielen aliohjelman argumentin arvovälitystä. Vastaavasti normaalijärjestyksen heikko muoto (joka myöskään ei sievennä abstraktion sisällä) vastaa nimivälitystä.