5.3 Laskimen muunnelmia 5.3. LASKIMEN MUUNNELMIA 57

Samankaltaiset tiedostot
5.5 Jäsenninkombinaattoreista

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

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

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

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Taas laskin. TIES341 Funktio ohjelmointi 2 Kevät 2006

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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.

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

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

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

Abstraktit tietotyypit. TIEA341 Funktio ohjelmointi 1 Syksy 2005

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Ydin-Haskell Tiivismoniste

Luku 3. Listankäsittelyä. 3.1 Listat

Ohjelmoinnin peruskurssien laaja oppimäärä

14.1 Rekursio tyypitetyssä lambda-kielessä

Haskell ohjelmointikielen tyyppijärjestelmä

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

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

Ohjelmoinnin peruskurssien laaja oppimäärä

TIES542 kevät 2009 Rekursiiviset tyypit

Luento 5. Timo Savola. 28. huhtikuuta 2006

5.2.5 Konstruktoriluokat Edellisessä esimerkissä määrittelimme oman tyyppiluokan Isqrt jonka jäsenet olivat tyyppejä (kuten Int, Integer, Word,...).

6 Algebralliset tietotyypit

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

ITKP102 Ohjelmointi 1 (6 op)

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

RINNAKKAINEN OHJELMOINTI A,

Z-skeemojen animointi Haskellilla

Ohjelmointiharjoituksia Arduino-ympäristössä

Yksinkertaiset tyypit

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssien laaja oppimäärä

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin perusteet, syksy 2006

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Jäsennys. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Java-kielen perusteet

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Java-kielen perusteet

Scheme-kesäkurssi luento 3

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

ICS-C2000 Tietojenkäsittelyteoria Kevät 2016

Ohjelmoinnin peruskurssien laaja oppimäärä

Matriisit ovat matlabin perustietotyyppejä. Yksinkertaisimmillaan voimme esitellä ja tallentaa 1x1 vektorin seuraavasti: >> a = 9.81 a = 9.

Johdatus lukuteoriaan Harjoitus 11 syksy 2008 Eemeli Blåsten. Ratkaisuehdotelma

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Lineaarinen toisen kertaluvun yhtälö

Kesälukio 2000 PK2 Tauluharjoituksia I Mallivastaukset

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

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

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 6: Python

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

Ohjelmointi 1 / 2009 syksy Tentti / 18.12

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

Ohjelmoinnin perusteet Y Python

811312A Tietorakenteet ja algoritmit Kertausta kurssin alkuosasta

Ohjelmoinnin peruskurssien laaja oppimäärä

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

Python-ohjelmointi Harjoitus 2

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Ohjelmoinnin peruskurssien laaja oppimäärä

Funktio-ohjelmoinnin hyödyntäminen peliohjelmoinnissa

1 Määrittelyjä ja aputuloksia

main :: IO () main = interact (concatmap ((++"\n"). reverse). lines)

Harjoitustyö: virtuaalikone

Matlab- ja Maple- ohjelmointi

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

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

1. Miten tehdään peliin toinen maila?

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

15. Ohjelmoinnin tekniikkaa 15.1

Lineaarikuvausten. Lineaarikuvaus. Lineaarikuvauksia. Ydin. Matriisin ydin. aiheita. Aiheet. Lineaarikuvaus. Lineaarikuvauksen matriisi

Ohjelmoinnin perusteet Y Python

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

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

Ohjelmoinnin peruskurssien laaja oppimäärä

TAMPEREEN TEKNILLINEN YLIOPISTO

Ohjelmoinnin peruskurssi Y1

Matematiikan tukikurssi

Johdatus diskreettiin matematiikkaan Harjoitus 5, Ratkaise rekursioyhtälö

Insinöörimatematiikka D

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

Matematiikan tukikurssi, kurssikerta 5

Ohjelmoinnin perusteet Y Python

Transkriptio:

5.3. LASKIMEN MUUNNELMIA 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ä? 5.3 Laskimen muunnelmia Olkoon meillä tehtävänä toteuttaa yksinkertaisen laskimen laskentaosa. Joku toinen toteuttaa meille jäsentimen, joka tekee lausekkeista abstrakteja syntaksipuita. Voisimme vastata tehtävään tällaisella modulilla: module Eval where data Expr = Const Integer Expr :+ Expr Expr :- Expr Expr :* Expr Expr :/ Expr deriving Show eval :: Expr -> Integer

58 LUKU 5. MONADIT eval (Const n) = n eval (n :+ m) = eval n + eval m eval (n :- m) = eval n - eval m eval (n :* m) = eval n * eval m eval (n :/ m) = eval n div eval m Kaikki on helppoa, eikö? Ja luulisi tuon toimivankin. Vaan ei toimi: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Compiling Eval ( Eval.hs, interpreted ) Ok, modules loaded: Eval. *Eval> eval (Const 4 :/ Const 0) *** Exception: divide by zero *Eval> Emme olleet ottaneet huomioon nollalla jaon mahdollisuutta! Vaikka GHCi sen ystävällisesti meidän puolestamme huomasikin, ei sen varaan voi heittäytyä: jos tämä moduli myöhemmin liitetään osaksi hienoa ACME Handy- Dandy Calculator 2004 -ohjelmistoa, ei tämä moduli, kuten ei mikään muukaan sen moduli, saa hajota hallitsemattomasti. Sen tulee välittää virhe kutsujalleen hoideltavaksi. Niinpä kirjoitamme siitä seuraavan version: module EvalV where data Expr = Const Integer Expr :+ Expr Expr :- Expr Expr :* Expr Expr :/ Expr deriving Show data Throws res exc = Return res Throw exc deriving Show eval :: Expr -> Throws Integer String eval (Const n) = Return n eval (a :+ b) = case (eval a, eval b) of (Return n, Return m) -> Return (n + m)

5.3. LASKIMEN MUUNNELMIA 59 eval (a :- b) = case (eval a, eval b) of (Return n, Return m) -> Return (n - m) eval (a :* b) = case (eval a, eval b) of (Return n, Return m) -> Return (n * m) eval (a :/ b) = case (eval a, eval b) of (Return n, Return 0) -> Throw "divide by zero" (Return n, Return m) -> Return (n div m) Nyt myös nollalla jako käyttäytyy hallitusti: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Compiling EvalV ( EvalV.hs, interpreted ) Ok, modules loaded: EvalV. *EvalV> eval (Const 4 :/ Const 0) Throw "divide by zero" *EvalV> Kuitenkin eval-funktion koodi on muuttunut paljon rumemmaksi kuin mitä se oli ensimmäisessä esimerkissä. Vielä rumemmaksi se muuttuu, jos lisäämme tuen let-lausekkeille: 3 module EvalL where import FiniteMap data Expr = Const Integer Var String Expr :+ Expr Expr :- Expr Expr :* Expr Expr :/ Expr Let String Expr Expr deriving Show data Throws res exc = Return res Throw exc 3. FiniteMap on käsitelty tehtävässä 1.

60 LUKU 5. MONADIT deriving Show eval :: Expr -> Throws Integer String eval = let f :: FiniteMap String Integer -> Expr -> Throws Integer String f fm (Const n) = Return n f fm (Var s) = case lookupfm fm s of Just n -> Return n Nothing -> Throw $ "undefined variable " ++ s f fm (a :+ b) = case (eval a, eval b) of (Return n, Return m) -> Return (n + m) f fm (a :- b) = case (eval a, eval b) of (Return n, Return m) -> Return (n - m) f fm (a :* b) = case (eval a, eval b) of (Return n, Return m) -> Return (n * m) f fm (a :/ b) = case (eval a, eval b) of (Return _, Return 0) -> Throw "divide by zero" (Return n, Return m) -> Return (n div m) f fm (Let x e e ) = case eval e of t@(throw _) -> t Return n -> f (addtofm fm x n) e in f emptyfm Sotkuisuudesta huolimatta tuo oli suhteellisen helppo kirjoittaa matkien edellisen version ajattelua. Ja se näyttäisi toimivankin: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Compiling RedBlackTree ( RedBlackTree.hs, interpreted ) Compiling FiniteMap ( FiniteMap.hs, interpreted ) Compiling EvalL ( EvalL.hs, interpreted ) Ok, modules loaded: EvalL, FiniteMap, RedBlackTree. *EvalL> eval (Const 4 :/ Const 0) Throw "divide by zero" *EvalL> eval (Let "x" (Const 4 :/ Const 0) (Var "x")) Throw "divide by zero" *EvalL> eval (Let "x" (Const 4 :/ Const 2) (Var "x")) Return 2

5.3. LASKIMEN MUUNNELMIA 61 Mutta ei kuitenkaan toimi: *EvalL> eval (Let "x" (Const 4 :/ Const 2) (Var "x" :+ Const 1)) Throw "undefined variable x" *EvalL> Mikä meni pieleen? Ikään kuin tieto määritellyistä muuttujista ei välittyisi :+operaattorin läpi sen alilausekkeille. Ovatko rekursiiviset kutsut kunnossa? Katsotaanpa muistin virkistykseksi yhtä tapausta: f fm (a :+ b) = case (eval a, eval b) of (Return n, Return m) -> Return (n + m) Kyllä siinä näyttäisi kutsuttavan eval-funktiota niin kuin pitääkin... hei hetkinen! Eihän eval ota lainkaan FiniteMapia parametrikseen, eikä sille sitä anneta. Muuttujamäärittelyt todella heitetään pois rekursiivisissa kutsuissa! Mutta miksi sitten ensimmäinen let-esimerkki toimi? Noh, näyttäisi siltä f fm (Let x e e ) = case eval e of t@(throw _) -> t Return n -> f (addtofm fm x n) e että let-tapauksessa FiniteMap välitetään korrektisti seuraavalle rekursiotasolle (mutta vain e :n tapauksessa!). Nyt täytyykin vain muuttaa kaikki muutkin rekursiokutsut vastaavasti: f fm (a :+ b) = case (f fm a, f fm b) of (Return n, Return m) -> Return (n + m) Nyt se toimii. Edellisen ohjelmointivirheen ja yleisen estetiikan tajun vuoksi herää kysymys siitä, olisiko alkuperäisen ohjelman rakentaa sellaiseksi, että sen laajentaminen näin olisi suhteellisen helppoa ja ei niin virhealtista ja että laajennetut versiot ovat suunnilleen yhtä kivoja lukea kuin alkuperäinen? Kuten Philip Wadler kirjoittaa 4 : The essence of an algorithm can become buried under the plumbing required to carry data from its point of creation to its point of use. Kuinka voisimme haudata putkistot maan alle algoritmin idean asemesta? 4. Philip Wadler: Monads for functional programming. In M. Broy, editor, Marktoberdorf Summer School on Program Design Calculi, Springer Verlag, NATO ASI Series F: Computer and systems sciences, Volume 118, August 1992. Also in J. Jeuring and E. Meijer, editors, Advanced Functional Programming, Springer Verlag, LNCS 925, 1995. Some errata fixed August 2001, see http://www.research.avayalabs.com/user/wadler/topics/monads.html.

62 LUKU 5. MONADIT 5.4 Monadinen laskin Hieman yllättäen vastaus edelliseen kysymykseen on sukua aiemmin esitetylle siirrännän ongelman ratkaisulle: rakennetaan ohjelma primitiivisistä ohjelmista ja yhdistellään niitä peräkkäistyskombinaattorilla. Emme kuitenkaan käytä IO α -tyyppiä sillä ei ole tarvittavia primitiivejä vaan rakennamme omamme. Kaikkeja tyyppikoostimia, joiden arvot ovat ohjelmia, joita voidaan suorittaa peräkkäin niin, että jälkimmäinen saa edellisen lopetusarvon parametrikseen, sanotaan monadeiksi 5. Kaikki monadit kuuluvat Haskellissa Monad-tyyppiluokkaan, joka määritellään seuraavasti: class Monad m where ( ) :: m α (α m β) m β ( ) :: m α m β m β return :: α m α fail :: String m α m k = m λ _ k fail s = error s Kuten huomataan, operaattorille ( ) ja funktiolle fail on annettu oletustoteutukset, joten niitä ei välttämättä tarvitse määritellä itse. Monadeilta vaaditaan seuraavat ominaisuudet: return a k = k a m return = m m (λx k x h) = (m k) h Peruslaskimelle (jossa on nollallajakoon varauduttu) käy mikä tahansa monadi: eval :: Monad m => Expr -> m Integer eval (Const n) = return n eval (a :+ b) = do n <- eval a return $ n + m eval (a :- b) = do n <- eval a 5. Sana monadi on tässä merkityksessään peräisin tietojenkäsittelyteorian ja abstraktin matematiikan alalta nimeltä kategoriateoria.

5.4. MONADINEN LASKIN 63 eval (a :* b) eval (a :/ b) return $ n - m = do n <- eval a return $ n * m = do n <- eval a when (m == 0) $ fail "divide by zero" return $ n div m Huomautus 13 Funktio when :: Monad m Bool m () m () when p s = case p of { True s; False return () } kuuluu varuskirjaston moduliin Monad, joka pitää erikseen importata. Periaatteessa tuo toimisi IO α -monadissakin: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Compiling EvalMV ( EvalMV.hs, interpreted ) Ok, modules loaded: EvalMV. *EvalMV> eval (Const 1) >>= print Loading package haskell98... linking... done. 1 *EvalMV> eval (Const 4 :/ Const 1) >>= print 4 *EvalMV> eval (Const 4 :/ Const 0) >>= print *** Exception: user error Reason: divide by zero *EvalMV> mutta tähän ei IO α -monadia oikeastaan tarvita. Muitakin varuskirjastoon kuuluvia monadeita voidaan harkita: Maybe α *EvalMV> eval (Const 4 :/ Const 2) :: Maybe Integer Just 2 *EvalMV> eval (Const 4 :/ Const 0) :: Maybe Integer Nothing [α]

64 LUKU 5. MONADIT *EvalMV> eval (Const 4 :/ Const 2) :: [Integer] [2] *EvalMV> eval (Const 4 :/ Const 0) :: [Integer] [] *EvalMV> [0,0,0,0] >> eval (Const 4 :/ Const 2) [2,2,2,2] *EvalMV> [0,0,0,0] >> eval (Const 4 :/ Const 0) [] Listamonadissa kukin ohjelma suoritetaan rinnakkain kullekin listan alkiolle erikseen. Yksinkertaisin (käytännössä) mahdollinen monadi on seuraavanlainen: newtype Id α = MkId α deriving Show instance Monad Id where return x = MkId x (MkId x) f = f x Tehtävä 2 Osoita, että Id α käy monadiksi. Tämä monadi ei kuitenkaan välitä tietoa virheestä. Tässä mielessä parempi olisi seuraava: data Throws α = Return α Throw String deriving Show instance Monad Throws where return x = Return x (Return x) f = f x (Throw s) _ = Throw s fail s = Throw s Toisaalta tämän monadin tarjoama poikkeuspalvelu olisi hyödyllinen lisä mihin tahansa monadiin, joka sitä ei itse tarjoa. Sen takia onkin kiva määritellä se monadimuuntimena kuvan 5.1 mukaisesti. Let-kykyinen laskin tarvitsee jytymmät työkalut. Se tarvitsee monadin, joka kykenee pitämään kirjaa määritellyistä muuttujista (eli ympäristöstä) sekä niiden määritelmien vaikutusalueista (scope):

5.4. MONADINEN LASKIN 65 class Transformer t where promote :: Monad m m α t m α data Throws α = Return α Throw String deriving Show newtype ThrowsT m α = MkT (m (Throws α)) runthrowst :: Monad m ThrowsT m α m (Throws α) runthrowst (MkT m) = m instance Transformer ThrowsT where promote mp = MkT $ do p mp return $ Return p instance Monad m Monad (ThrowsT m) where return = promote return fail s = MkT $ return $ Throw s (MkT mp) f = MkT $ do p mp case p of Return n case f n of MkT q q Throw s return $ Throw s Kuva 5.1: Poikkeusmonadimuunnin

66 LUKU 5. MONADIT class Monad m EnvMonad mwhere begin :: m α m α bind :: String Integer m () getvalue :: String m Integer m Integer Metodi begin ajaa argumenttina saamansa ohjelman nykyisen ympäristön kopiossa. Kun ohjelma päättyy, sen kopio ympäristöstä heitetään pois. Tämä mahdollistaa leksikaaliset vaikutusalueet (lexical scoping). Metodikutsu bind v n lisää nykyiseen ympäristöön sidonnan s n. Metodikutsu getvalue v f hakee muuttujaan v nykyisessä ympäristössä sidotun arvon ja palauttaa sen; jos ko. muuttujaa ei ole nykyisessä ympäiristössä sidottu mihinkään, se suorittaa ohjelman f. Näillä keinoilla saadaan aikaiseksi seuraavanlainen let-kykyinen laskin: eval :: EnvMonad m => Expr -> m Integer eval (Const n) = return n eval (Var s) = getvalue s $ fail "undefined variable" eval (a :+ b) = do n <- eval a return $ n + m eval (a :- b) = do n <- eval a return $ n - m eval (a :* b) = do n <- eval a return $ n * m eval (a :/ b) = do n <- eval a when (m == 0) $ fail "divide by zero" return $ n div m eval (Let x e e ) = do er <- eval e begin $ do bind x er eval e Kuten huomataan, laskimen koodi on lähes samanlainen kuin ensimmäisessä monadisessa laskimessa. Eräs EnvMonad saadaan aikaan yhdistämällä kuvassa 5.2 esitetty monadimuunnin johonkin toiseen monadiin.

5.4. MONADINEN LASKIN 67 type Env α = FiniteMap String α data EnvT m α = MkE (Env Integer m (Env Integer, α)) instance Transformer EnvT where promote mp = MkE $ λenv mp λr return $(env, r) instance Monad m Monad (EnvT m) where return = promote return (MkE p) f = MkE $ λenv p env λ(env, pr) case f pr of (MkE q) q env fail = promote fail instance Monad m EnvMonad (EnvT m) where begin (MkE p) = MkE $ λenv p env λ(_, r) return (env, r) bind v n = MkE $ λenv return (addtofm env v n, ()) getvalue v (MkE f) = MkE $ λenv case lookupfm env v of Just n return (env, n) Nothing f env Kuva 5.2: Ympäristömonadimuunnin