Taas laskin TIES341 Funktio ohjelmointi 2 Kevät 2006
Rakennepuutyyppi data Term = C Rational T F V String Term :+: Term Term : : Term Term :*: Term Term :/: Term Term :==: Term Term :/=: Term Term :<=: Term Term :>=: Term Term :<: Term Term :>: Term Let (String,Term) Term If Term Term Term funktiokutsu Term :@: Term rekursiivinen funktiomäärittely Fun [(String,[String],Term)] Term deriving (Show,Eq,Ord)
Laskin Laskimen keskeiset määrittelyt: eval :: (MonadError String e,monadreader ValueEnv m) => Expr > m Value data Value = R Rational B Bool FV [String] [Value] ValueEnv type ValueEnv = Map String Value eval on primitiivirekursiivinen! eval käyttää MonadReaderia laskentaympäristön hallintaan laskentaympäristö: muuttujien arvot ko.kohdassa koska muuttujiin ei voi sijoittaa, MonadReader riittää
Paloja laskimesta eval F = return (B False) eval (V s) = do e < ask case lookupfm e s of Just v > return v eval (t1 :+: t2) = do R v1 < eval t1 R v2 < eval t2 return $ apply (+) v1 v2 eval (Let (x,e) e') = do v < eval e local (\env > addtofm env x v) $ eval e' eval (If te th el) = do B ter < eval te case ter of True > eval th False > eval el Huomaa: laskin kaatuu, jos syötteessä on tyyppivirheitä sen sijaan nollallajakaminen aiheuttaa siistin poikkeuksen tarkoituksellista!
Funktiot Funktio on arvo, joka pitää sisällään: termin muuttujanimien ( parametrien ) listan laskentaympäristön termin vapaiden muuttujien arvot funktionluontikohdassa! (Täysi) funktiokutsu: funktiotermi lasketaan ympäristössä, jossa parametreille on annettu argumenttilausekkeiden arvot huom! laskentaympäristö johdetaan funktiossa mukana pidetystä laskentaympäristöstä, ei kutsun ympäristöstä muuten tulee ns. dynamic scoping, jossa funktio näkee kutsujansa paikalliset muuttujat
Osittaiskutsu Jos funktiolla on 3 parametria ja sitä kutsutaan kahdella, mitä tapahtuu? palautetaan funktio, joka ottaa sen puuttuvan, kolmannen parametrin tätä sanotaan osittaiskutsuksi (partial application) Osittaiskutsun vuoksi funktioarvo sisältää em:n lisäksi: listan arvoja, jotka on osittaiskutsuilla annettu osalle parametreista
Arvot data Value = R Rational B Bool FV [String] [Value] Term ValueEnv deriving (Show) Funktion parametrit Jo annettujen argumenttien arvot
Kutsu eval (f :@: e) = do FV xs vs b env < eval f ev < eval e let vs' = ev : vs case length xs == length vs' of True > local (\_ > Map.union (Map.fromList (zip xs (reverse vs'))) env) $ eval b False > return $ FV xs vs' b env
Funktioiden määrittely eval (Fun defs t) = do env < ask let fs = map (\(f,ps,b) > (f, FV ps [] b env)) defs local (\env > Map.fromList fs `Map.union` env) $ eval t
Tyyppitarkastus Tyyppitarkastus pitää huolen siitä, että lauseke on hyvinmääritelty ei yritetä laskea funktioita yhteen ei yritetä käyttää muuttujia, joita ei ole määritelty Laskimen ei tarvitse tarkistaa näitä mahdollisesti jopa nopeusetu tyyppivirheet havaitaan heti alussa Periaate: lasketaan lausekkeen arvo abstraktisti eli lasketaan tyypeillä (arvojoukoilla) ei arvoilla primitiivirekursio, pääsääntöisesti etenee rakennepuun latvoista tyveen
Tyypit data Type = RT BT Type : > Type deriving (Show,Eq,Ord)
Ongelma: funktioiden tyyppitarkastus Miten selvittää funktion tyyppi, jos sitä ei ole kerrottu? Miten selvittää keskenään rekursiivisten funktioiden tyypit?! Ratkaisu: arvataan, että kaksiparametrisen funktion tyyppi on vaikkapa a > b > c annetaan tyyppitarkastuksen tarkentaa nuo tyyppimuuttujat
Tyypit, uudestaan data Type = RT BT TVar Int Type : > Type deriving (Show,Eq,Ord)
Korvaus Korvaus on kuvaus tyyppimuuttujilta tyypeille Korvasta voidaan soveltaa tyyppeihin: korvataan tyypissä esiintyvät tyyppimuuttujat samanaikaisesti korvauksen mukaisilla tyypeillä type Subst = Map Int Type subst :: MonadState Subst m => Type > m Type subst RT = return RT subst BT = return BT subst (TVar n) = do sb < get case Map.lookup n sb of Just ty > return ty Nothing > return $ TVar n subst (t : > u) = do t' < subst t u' < subst u return (t' : > u')
Unifikaatio (1) Unifikaatio ongelma: Onko olemassa korvaus, joka saa nämä kaksi tyyppiä olemaan samat? Robinsonin unifikaatio algoritmi (JACM 1965): vertaile kahden tyypin konstruktoria: onko samat? jos kyseessä ei ole TVar, käy rekursiivisesti läpi alityypit esim. t > u ==> unify t, unify u jos jompi kumpi on TVar, lisää korvaukseen sijoitus tuosta muuttujasta toiseen tyyppiin jos kumpikaan ei ole TVar eikä niillä ole samat konstruktorit, unifikaatio epäonnituu
Unifikaatio (2) occursin :: Int > Type > Bool n `occursin` TVar n' = n == n' n `occursin` (t : > u) = n `occursin` t && n `occursin` u _ `occursin` _ = False unify :: (MonadError String m, MonadState Subst m) => Type > Type > m () unify RT RT = return () unify BT BT = return () unify (t : > u) (t' : > u') = do unify t t' v < subst u v' < subst u' unify v v' unify (TVar n) ty n `occursin` ty = throwerror "occurs check failed" otherwise = modify (\sb > addtofm sb n ty) unify t1 t2@(tvar _) = unify t2 t1 unify = throwerror "unification failed"
Occurs check Jos ollaan lisäämässä sijoitusta muuttujasta a tyyppiin t, pitää tarkistaa, esiintyykö a t:ssä jos esiintyy, niin tyyppi on äärettömän pitkä se on hylättävä
Tyyppitarkastus Tyyppi, jota ei heti pystytä päättelemään, jätetään avoimeksi laitetaan tyypiksi uusi tyyppimuuttuja sellainen, jota ei ole vielä käytetty vrt. D4:n UniqueSupply Tyyppien samuuden tarkastamisen sijasta koetetaan unifioida ne Koodi nähtiin viime luennolla
Polymorfismi? Edellä esitelty tyyppimuuttujatemppu ratkaisee mainiosti funktioiden tyypityksen ongelman Sivutuotteena syntyy melkein geneerisiä tyyppejä esim. id x = x, tyypitys a > a mutta ei täysin fun g x y = 0; id x = x in g (id 3) (id True) ei tyypity oikein, koska id on monomorfinen: sitä voi käyttää vain yhden tyypin kanssa Ratkaisu: joka kerta kun funktion nimi kysytään ympäristöstä, annetaan sille eri tyyppimuuttujat
Tyyppiskeemat ja tyypin yleistäminen Tyyppiskeema on tyyppimuuttujajoukon (listan) ja tyypin muodostama pari idea: listatut tyyppimuuttujat ovat geneerisiä eli polymorfisia, muut monomorfisia Tyyppiympäristö yhdistää nyt muuttujia tyyppiskeemoihin Fun rakenteen tyypitys etenee seuraavasti: arvataan funktioiden tyypit tyypitetään funktiot ympäristössä, jossa arvatut tyypit ovat monomorfisia (tyyppiskeeman tyyppimuuttujalista on tyhjä) tyypitetään in lauseke ympäristössä, jossa funktioiden tyypit ovat geneerisiä
Yleistämisestä Yleistäminen tapahtuu fun lausekkeen tyypityksen yhteydessä kun määritellyt funktiot on tyypitetty mutta ennen kuin in lauseke tyypitetään Kunkin funktion tyypistä yleistetään kaikki ne tyyppimuuttujat, jotka eivät esiinny vapaana funlausekkeen tyyppiympäristön tyypeissä vapaana tarkoittaa: ei ole geneerinen muuttuja ko. yhteydessä