Luku 5. Monadit. 5.1 Siirrännän ongelma

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

5.3 Laskimen muunnelmia 5.3. LASKIMEN MUUNNELMIA 57

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

Esimerkki: Laskin (alkua) TIEA341 Funktio ohjelmointi 1 Syksy 2005

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

5.5 Jäsenninkombinaattoreista

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

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

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

Jatkeet. TIES341 Funktio ohjelmointi 2 Kevät 2006

1. Omat operaatiot 1.1

Monadeja siellä, monadeja täällä... monadeja kaikkialla? TIES341 Funktio ohjelmointi 2 Kevät 2006

Ohjelmoinnin perusteet Y Python

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

Ohjelmoinnin peruskurssien laaja oppimäärä

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

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

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

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

ELM GROUP 04. Teemu Laakso Henrik Talarmo

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

Ohjelmoinnin perusteet Y Python

Ydin-Haskell Tiivismoniste

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

ASCII-taidetta. Intro: Python

Harjoitus 2 (viikko 45)

TIEA341 Funktio-ohjelmointi 1, kevät 2008

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Java-kielen perusteet

1.3Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Bootstrap / HTDP2 / Realm of Racket. Vertailu

Luku 3. Listankäsittelyä. 3.1 Listat

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

Luento 5. Timo Savola. 28. huhtikuuta 2006

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

12. Näppäimistöltä lukeminen 12.1

Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä

1.3 Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Mitä funktionaalinen ohjelmointi on

TIE Principles of Programming Languages. Seminaariesityksen essee. Ryhmä 18: Heidi Vulli, Joni Heikkilä

7. Näytölle tulostaminen 7.1

System.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);

System.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);

Pythonin Kertaus. Cse-a1130. Tietotekniikka Sovelluksissa. Versio 0.01b

Java-kielen perusteet

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Luku 4. Tietorakenteet funktio-ohjelmoinnissa. 4.1 Äärelliset kuvaukset

Kielioppia: toisin kuin Javassa

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

Ohjelmoinnin peruskurssien laaja oppimäärä

Apuja ohjelmointiin» Yleisiä virheitä

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Zeon PDF Driver Trial

ITKP102 Ohjelmointi 1 (6 op)

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Osoittimet ja taulukot

Ohjelmoinnin perusteet Y Python

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Ohjelmoinnin peruskurssien laaja oppimäärä

3. Muuttujat ja operaatiot 3.1

812336A C++ -kielen perusteet,

Ohjelmoinnin perusteet Y Python

Ohjelmointiharjoituksia Arduino-ympäristössä

Harjoitus 5 (viikko 48)

Ohjelmoinnin perusteet Y Python

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Hohde Consulting 2004

Ohjelmoinnin peruskurssien laaja oppimäärä

Loppukurssin järjestelyt

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

Ohjelmoinnin perusteet Y Python

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Ohjelmoinnin peruskurssi Y1

Java kahdessa tunnissa. Jyry Suvilehto

Ohjelmoinnin perusteet Y Python

Harjoitustyö: virtuaalikone

811120P Diskreetit rakenteet

6 Algebralliset tietotyypit

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

Ohjelmoinnin peruskurssien laaja oppimäärä

Harjoitus 3 (viikko 39)

TIEA341 Funktio-ohjelmointi 1, kevät 2008

FinFamily PostgreSQL installation ( ) FinFamily PostgreSQL

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

811120P Diskreetit rakenteet

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin jatkokurssi, kurssikoe

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

Sisältö. 22. Taulukot. Yleistä. Yleistä

Ohjelmoinnin perusteet, syksy 2006

Ohjelmoinnin perusteet Y Python

Harjoitus 4 (viikko 47)

Loppukurssin järjestelyt C:n edistyneet piirteet

Ohjelmoinnin peruskurssien laaja oppimäärä

Transkriptio:

Luku 5 Monadit There are lots of books about functional programming in Haskell. They tend to concentrate on the beautiful core of functional programming: higher order functions, algebraic data types, polymorphic type systems, and so on. These lecture notes are about the bits that usually aren t written about. To write programs that are useful as well as beautiful, the programmer must, in the end, confront the Awkward Squad, a range of un-beautiful but crucial issues, generally concerning interaction with the external world I began this introduction by saying that we must confront the Awkward Squad if we are to write useful programs. Does that mean that useful programs are awkward? You must judge for yourself, but I believe that the monadic approach to programming, in which actions are first class values, is itself interesting, beautiful, and modular. In short, Haskell is the world s finest imperative programming language. Simon Peyton Jones 1 5.1 Siirrännän ongelma Jokainen hyödyllinen ohjelma muuttaa maailmaa. Tämän muutoksen ei tarvitse olla missään nimessä niin voimakas kuin ydinaseen laukaisua kontrolloivan ohjelman; esimerkiksi yksinkertainen Hello World muuttaa näytöllä olevien väripisteiden valoisuutta. Toisaalta ohjelma, jolla ei ole mitään vaikutusta, on täysin hyödytön, sillä sen voisi ihan yhtä hyvin korvata ohjelman olemattomuudella! Funktio-ohjelmoinnissa on perinteisesti lähdetty liikkeelle laskukoneanalogiasta, jossa tietokone varustettuna funktiokielen tulkilla tai kääntäjällä on kehittynyt laskin, ja ohjelmoijan tehtävänä on kirjoittaa tämän laskimen käyttäjän käyttöön lisää valmisfunktioita, jotka sopivat tietyn ongelmatyypin ratkaisemiseen. Kuitenkin Oikeassa Maailmassa TM on totuttu ohjelmiin, jotka ottavat 1. Peyton Jones, Simon: Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell. http://research.microsoft.com/users/ simonpj/papers/marktoberdorf/, 2002. 53

54 LUKU 5. MONADIT tietokoneen kontrollin (osittain tai kokonaan) itselleen ja itse huolehtivat tarvitsemansa syötteen saamisesta ja tuloksen antamisesta käyttäjälle. Kuinka tämän voisi sovittaa funktio-ohjelmoinnin ideologiaan? 2 Tiukat funktiokielet (kuten Standard ML ja Scheme) ovat tiukan pragmaattisia. Ne tarjoavat käyttäjälle joukon funktioita, joilla on sivuvaikutuksia, kuten printchar. Näitä kieliä ei haittaa, vaikka tällaiset vekottimet ovat funktioita vain nimellisesti (niillä ei ole funktion perusominaisuutta: (f x, f x) tulisi olla sama kuin let y = f x in (y, y)). Laiskasti laskevilla (lazy evaluation) kielillä (kuten Haskell tavanomaiseen tapaan toteutettuna) on se ongelma, että tuo pragmaattinen ratkaisu ei ole mahdollinen. Innokkaasti laskevassa (eager evaluation) kielessä lausekkeen let x = [printchar a, printchar b ] in length x arvo olisi 2, ja sillä olisi lisäksi sivuvaikutus: se tulostaa merkkijonon "ab". Haskellissa tuon lausekkeen arvo olisi toki edelleen 2, mutta koska length ei ole kiinnostunut listan alkioista, niitä ei koskaan lasketa, jolloin mitään sivuvaikutuksia ei kyseisellä lausekkeella olisi. Laiska laskenta ja sivuvaikutukset eivät sovi yhteen. Aiemmin tässä monisteessa mainittiin eräs tapa kiertää tämä ongelma: ajatellaan, että ohjelma on laiska funktio String String. Tällöin kielen toteutukseen kuuluu ajonaikainen suoritusjärjestelmä (runtime system), joka hankkii syötteen käyttäjältä ja antaa sen ohjelmalle ja joka ottaa ohjelmalta sen tulosteen ja antaa sen käyttäjälle. Näin epäpuhtaudet on saatu siirrettyä ohjelmasta syrjään. Ongelmallinen tämä merkkijonofunktioidea on sikäli, että se ei mitenkään salli usean tiedoston käsittelyä. Tämä ongelma voidaan toki myös kiertää: tehdään ohjelmista funktioita [Response] [Request], jossa tyypit Response ja Request ovat esimerkiksi seuraavanlaisia: type Filepath = String data Request = ReadFile FilePath WriteFile FilePath data Response = RequestFailed ReadSucceeded String Tässäkin tarvitaan suoritusjärjestelmää; nyt se lukee funktion pyyntöjä sen laiskasta paluuarvolistasta, toteuttaa niitä ja laittaa niiden tulokset funktion laiskaan agrumenttilistaan. Tämä toimii suhteellisen hyvin, ja vanhat Haskellin versiot käyttivät tätä menetelmää. Tässäkin on kyllä omat ongelmansa, esimerkiksi ohjelman on helppo vahingossa vaatia vastausta pyyntöön, jota se ei 2. Vaikka oikeasti nykyisin odotetaankin pitkälti graafisia käyttöliittymiä, käsittelemme tässä vain perinteistä merkkipohjaista siirräntää. Kun sen ongelma on ratkaistu, käyvät perinteisestä ohjelmoinnista tutut graafisen ohjelmoinnin keinot tässäkin.

5.2. KOMBINATORINEN RATKAISU 55 ole vielä lähettänyt, mikä johtaa ohjelman lukkiutumiseen (deadlock). Toinen ongelma on se, että pyynnöt ja niiden vastaukset ovat erillään toisistaan, ja on suhteellisen helppoa menettää niiden välinen synkronointi. Vielä yksi tapa on ajatella ohjelmaa funktiona World (a, World), missä World on abstrakti tietotyyppi, joka mallittaa (riittävää osaa) maailmasta. 5.2 Kombinatorinen ratkaisu Fiksuimmalta ratkaisulta siirrännän ongelmaan vaikuttaa kuitenkin kombinatorinen tyyli: kieli tarjoaa joukon primitiivisiä, atomaarisia (jakamattomia) ohjelmia sekä joukon kombinaattoreita, joilla näitä ohjelmia voidaan toisiinsa yhdistellä. Haskell 98 käyttää tätä menetelmää. Haskellissa ohjelman tyyppi on IO α. Tyyppiin IO α kuuluvat sellaiset ohjelmat, jotka palauttavat käynnistäjälleen tyyppiä α olevan arvon. Esimerkiksi IO () sisältää ohjelmat, jotka eivät palauta mitään (hyödyllistä); nämä ovat toki hyödyllisiä, koska ohjelmat vaikuttavat suoraan maailmaan. Ohjelma, joka on tyyppiä IO String palauttaa suorituksensa lopuksi käynnistäjälleen merkkijonon. Haskellin varuskirjasto tarjoaa mm. seuraavat atomaariset ohjelmat: putchar :: Char IO () on funktio, joka ottaa parametrinaan merkin ja palauttaa ohjelman, joka tulostaa kyseisen merkin vakiotulostusvirtaan. getchar :: IO Char on ohjelma, joka lukee vakiosyötevirrasta yhden merkin ja palauttaa sen suorituksensa lopuksi käynnistäjälleen. return :: α IO α on funktio, joka ottaa parametrinaan jonkin arvon ja palauttaa ohjelman, joka ei tee yhtikäs mitään mutta palauttaa suorituksensa lopuksi kyseisen arvon käynnistäjälleen. Kannattaa huomata, että pelkkä lausekkeen getchar laskeminen tuottaa ulos vain ohjelman, se ei aiheuta sen ajamista. Ohjelmat ovat Haskellissa arvoja siinä missä merkkijonot ja luvut: niitä voidaan laittaa listoihin, ne voidaan sitoa muuttujiin, niitä voidaan antaa funktioille parametriksi, palauttaa paluuarvoina ja niin edelleen. Haskellissa on vain kaksi tapaa, jolla IO α-ohjelma tulee ajetuksi: 1. Jos GHCi:n komentoriville kirjoitetaan lauseke, joka on tyyppiä IO α jollakin α, niin GHCi suorittaa tämän lausekkeen tarkoittaman ohjelman. 2. Kun moduli Main käännetään, tulokseksi tulee se ohjelma, johon kyseisen modulin main-vakio on sidottu.

56 LUKU 5. MONADIT Kummassakin tapauksessa ohjelman suorituksensa lopuksi palauttama arvo jätetään huomiotta. Edellä annetut atomaariset ohjelmat ovat yksinään varsin hyödyttömiä. Varsinaisia kunnon ohjelmia saadaan aikaiseksi yhdistelemällä niitä. Siihen Haskellin varuskirjasto tarjoaa kaksi primitiivistä kombinaattoria: ( ) :: IO α IO β IO β ottaa kaksi ohjelmaa ja tuottaa uuden ohjelman, joka ensin tekee sen, mitä ensimmäinen ohjelma tekee, ja sitten tekee sen, mitä toinen ohjelma tekee. Ensimmäisen ohjelman paluuarvon se jättää huomiotta, ja uusi ohjelma palauttaa sen, mitä toinen ohjelma palauttaa. Esimerkiksi getchar putchar! on ohjelma, joka lukee merkin, heittää sen pois ja tulostaa huutomerkin. ( ) :: IO α (α IO β) IO β ottaa ohjelman sekä funktion, joka palauttaa ohjelman. Se tuottaa uuden ohjelman, joka ensin ajaa ensimmäisenä parametrina saamansa ohjelman ja sitten ajaa sen ohjelman, jonka toisena parametrina annettu funktio palauttaa, kun sille annetaan parametriksi se, mitä ensimmäinen ohjelma palauttaa. Uusi ohjelma palauttaa sen, mitä funktion palauttama ohjelma palauttaa. Esimerkiksi getchar putchar on ohjelma, joka lukee merkin ja sitten tulostaa sen. Operaattorit ja kirjoitetaan koneelle muodossa >> ja >>=. Ne lausutaan toisinaan sitten (then, ) sekä sido (bind, ). Oikeastaan ( ) ei ole lainkaan primitiivinen: se voidaan määritellä ( ):n avulla seuraavasti: ( ) :: IO α IO β IO β p q = p (λ _ q) Haskellin varuskirjastoon kuuluu useita edellä esitettyjen ohjelmien ja ohjelmakombinaattorien avulla rakennettavia ohjelmia ja ohjelmafunktioita: putstr :: String IO () putstr s = foldr ( ) $ map putchar s putstrln :: String IO () putstrln s = putstr s putstr "\n" print :: Show α α IO () print x = putstrln (show x)

5.2. KOMBINATORINEN RATKAISU 57 Samaan sarjaan kuuluu seuraavakin funktio, jonka määritelmä esittelee muutenkin hyödyllisen tavan kirjoittaa ohjelmia: getline :: IO String getline = getchar λc case c of \n return "\n" _ getline λl return (c : l) Kun rivinä on foo λx, sitä voidaan ajatella erikoisena tapana kirjoittaa imperatiivisista kielistä tuttu rivi x := foo;. Kombinaattori yhdistettynä λ- lausekkeeseen kun on tapa ilmaista jokin suoritettava ohjelma(lause), jonka tulos sijoitetaan muuttujaan x. Tämän vuoksi Haskellissa on kielioppimakeinen tällaisten kirjoittamiseen: muotoa do { P; Q } oleva lauseke tarkoittaa lauseketta P (do { Q }) ja muotoa do { x P; Q } oleva lauseke tarkoittaa (suunnilleen) lauseketta P λx (do { Q }). Kuten tavallista, myös tässä voidaan käyttää sisennystekniikkaa sulkujen ja puolipisteiden kera. Tällä tavalla ilmaistuna edellinen ohjelma olisi muotoa getline :: IO String getline = do c getchar case c of \n return "\n" _ do l getline return (c : l) Näyttää aika imperatiiviselta, vai mitä?