Esimerkki: Laskin (alkua) TIEA341 Funktio ohjelmointi 1 Syksy 2005
Esimerkki: Laskin Liukulukulaskentaa Yhteen, vähennys, kerto ja jakolaskut Syötteenä laskutehtävä, tulosteena tulos tai virheilmoitus
Ohjelman rakenne Syöte on merkkijono Laskennan helpottamiseksi se muutetaan ns. rakennepuumuotoon (laskutehtävän jäsennys ) Rakennepuumuotoinen laskutehtävä ratkaistaan Tuloksena tulos Se on muutettava merkkijonoksi tulostusta varten
Ohjelman rakenne data Laskutehtava =... data Tulos =... jasenna :: String > Either String Laskutehtava laske :: Laskutehtava > Tulos muotoile :: Tulos > String main :: IO ()
Laskutehtävä ja tulos data Laskutehtava = Luku Double Yhteen Laskutehtava Laskutehtava Vahennys Laskutehtava Laskutehtava Tulo Laskutehtava Laskutehtava Osamaara Laskutehtava Laskutehtava deriving (Show) data Tulos = LukuTulos Double VirheTulos String deriving (Show)
Laske (1) Primitiivirekursiolla: laske :: Laskutehtava > Tulos laske (Luku l) = LukuTulos l laske (Yhteen t1 t2) = case (laske t1, laske t2) of (VirheTulos s, VirheTulos s') > VirheTulos (s ++ \n ++ s') ensin lasketaan osatehtävät osatehtävän virhe on hoidettava (VirheTulos s, _) > VirheTulos s (_, VirheTulos s) > VirheTulos s (LukuTulos a, LukuTulos b) > LukuTulos (a + b) laske (Vähennä t1 t2) = (samaan tapaan) itse tehtävän ratkaisu laske (Tulo t1 t2) = (samaan tapaan) (jatkuu)
Laske (2) laske :: Laskutehtava > Tulos (jatkoa) laske (Osamaara t1 t2) = case (laske t1, laske t2) of (VirheTulos s, VirheTulos s') > VirheTulos (s ++ \n : s') (VirheTulos s, _) > VirheTulos s (_, VirheTulos s) > VirheTulos s (LukuTulos a, LukuTulos 0) > VirheTulos Nollalla jako (LukuTulos a, LukuTulos b) > LukuTulos (a / b)
Grrr... Aika sotkuista, vai mitä?... ja aika lailla samojen asioiden toistoa, mutta siten, että keskellä on vaihtelua Noh, tuohan vain tarkoittaa, että pitää abstrahoida eli tehdä funktio tai funktioita, joilla tuon tekeminen on helppoa Abstrahoidaan ensin virheenkäsittely
Tadaa andthen :: Tulos > (Double > Tulos) > Tulos andthen... laske :: Laskutehtava > Tulos laske (Luku l) = LukuTulos l laske (Yhteen t1 t2) = laske t1 `andthen` \d1 > laske t2 `andthen` \d2 > LukuTulos (d1+d2) laske (Vahennys t1 t2) = (samaan tapaan) laske (Tulo t1 t2) = (samaan tapaan) laske (Osamaara t1 t2) = laske t1 `andthen` \d1 > laske t2 `andthen` \d2 > if d2 == 0 then VirheTulos nollalla jako else LukuTulos (d1/d2)
andthen andthen :: Tulos > (Double > Tulos) > Tulos jos eka parametri on LukuTulos, anna se parametrifunktiolle, tulos on funktion tulos jos eka parametri on VirheTulos, palauta se andthen :: Tulos > (Double > Tulos) > Tulos andthen (LukuTulos d) f = f d andthen t@(virhetulos _) _ = t
laske (Yhteen (Luku 1) (Luku 1))
laske (Yhteen t1 t2) = laske t1 `andthen` \d1 -> laske t2 `andthen` \d2 -> LukuTulos (t1+t2) laske (Yhteen (Luku 1) (Luku 1)) laske (Luku 1) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2)
laske (Luku d) = LukuTulos d laske (Yhteen (Luku 1) (Luku 1)) laske (Luku 1) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) LukuTulos 1 `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2)
andthen (LukuTulos d) f = f d laske (Yhteen (Luku 1) (Luku 1)) laske (Luku 1) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) LukuTulos 1 `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2) (\d1 > (\d2 > LukuTulos (d1 + d2)) 1) 1
(\x ->... x...) t... t... laske (Yhteen (Luku 1) (Luku 1)) laske (Luku 1) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) LukuTulos 1 `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2) (\d1 > (\d2 > LukuTulos (d1 + d2)) 1) 1 (\d2 > LukuTulos (1 + d2)) 1
(\x ->... x...) t... t... laske (Yhteen (Luku 1) (Luku 1)) laske (Luku 1) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) LukuTulos 1 `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2) (\d1 > (\d2 > LukuTulos (d1 + d2)) 1) 1 (\d2 > LukuTulos (1 + d2)) 1 LukuTulos (1 + 1)
1 + 1 2 laske (Yhteen (Luku 1) (Luku 1)) laske (Luku 1) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) LukuTulos 1 `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2) (\d1 > (\d2 > LukuTulos (d1 + d2)) 1) 1 (\d2 > LukuTulos (1 + d2)) 1 LukuTulos (1 + 1) LukuTulos 2
Uusi tehtävä laske (Yhteen (Osamaara (Luku 1) (Luku 0)) (Luku 1))...
laske (Yhteen t1 t2) = laske t1 `andthen` \d1 -> laske t2 `andthen` \d2 -> LukuTulos (d1 + d2) laske (Yhteen (Osamaara (Luku 1) (Luku 0)) (Luku 1)) laske (Osamaara (Luku 1) (Luku 0)) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2)...
laske (Osamaara t1 t2) = laske t1 `andthen` \d1' -> laske t2 `andthen` \d2' -> if d2' == 0 then VirheTulos nollalla jako else LukuTulos (d1' / d2')... laske (Osamaara (Luku 1) (Luku 0)) `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) laske (Luku 1) `andthen` \d1' > laske (Luku 0) `andthen` \d2' > if d2' == 0 then VirheTulos nollalla jako else LukuTulos (d1' / d2') `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2)...
laske (Luku d) = LukuTulos d... laske (Luku 1) `andthen` \d1' > laske (Luku 0) `andthen` \d2' > if d2' == 0 then VirheTulos nollalla jako else LukuTulos (d1' / d2') `andthen` \d1 > laske (Luku 1) `andthen` \d2 > LukuTulos (d1 + d2) LukuTulos 1 `andthen` \d1' > LukuTulos 0 `andthen` \d2' > if d2' == 0 then VirheTulos nollalla jako else LukuTulos (d1' / d2') `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2)...
andthen (LukuTulos d) f = f d (\x ->... x...) t... t...... LukuTulos 1 `andthen` \d1' > LukuTulos 0 `andthen` \d2' > if d2' == 0 then VirheTulos nollalla jako else LukuTulos (d1' / d2') `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2) if 0 == 0 then VirheTulos nollalla jako else LukuTulos (1 / 0) `andthen` \d1 > LukuTulos (d1 + 1)...
if True then e1 else e2 e1... LukuTulos 1 `andthen` \d1' > LukuTulos 0 `andthen` \d2' > if d2' == 0 then VirheTulos nollalla jako else LukuTulos (d1' / d2') `andthen` \d1 > LukuTulos 1 `andthen` \d2 > LukuTulos (d1 + d2) if 0 == 0 then VirheTulos nollalla jako else LukuTulos (1 / 0) `andthen` \d1 > LukuTulos (d1 + 1) VirheTulos nollalla jako `andthen` \d1 > lukutulos (d1 + 1)...
andthen t@(virhetulos _) _ = t... if 0 == 0 then VirheTulos nollalla jako else LukuTulos (1 / 0) `andthen` \d1 > LukuTulos (d1 + 1) VirheTulos nollalla jako `andthen` \d1 > lukutulos (d1 + 1) VirheTulos nollalla jako
Muistattehan andthen :: Tulos > (Double > Tulos) > Tulos andthen (LukuTulos d) f = f d andthen t@(virhetulos _) _ = t laske :: Laskutehtava > Tulos laske (Luku l) = LukuTulos l laske (Yhteen t1 t2) = laske t1 `andthen` \d1 > laske t2 `andthen` \d2 > LukuTulos (d1+d2) laske (Vahennys t1 t2) = (samaan tapaan) laske (Tulo t1 t2) = (samaan tapaan) laske (Osamaara t1 t2) = laske t1 `andthen` \d1 > laske t2 `andthen` \d2 > if d2 == 0 then VirheTulos nollalla jako else LukuTulos (d1/d2)
Ähh Selkeämpi, mutta on edelleen toistoa Voisiko tuon jotenkin kirjoittaa niin, että kussakin haarassa kutsutaan funktiota, jolle vain annetaan laskutoimitus, joka on suoritettava No tottahan toki:
andthen :: Tulos > (Double > Tulos) > Tulos (andthen kuten edellä) lift2t :: (Double > Double > Double) > Tulos > Tulos > Tulos precond2 :: (Double > Double > Bool) > String > (Tulos > Tulos > Tulos) > Tulos > Tulos > Tulos laske :: Laskutehtava > Tulos laske (Luku d) = LukuTulos d laske (Yhteen t1 t2) = lift2t (+) (laske t1) (laske t2) laske (Vahennys t1 t2) = lift2t ( ) (laske t1) (laske t2) laske (Tulo t1 t2) = lift2t (*) (laske t1) (laske t2) laske (Osamaara t1 t2) = (precond2 p s (lift2t (/))) (laske t1) (laske t2) where p _ x = x /= 0 s = nollalla jako
lift2t nostaa kaksiparametrisendouble funktion kaksiparametriseksi Tulos funktioksi lift2t :: (Double > Double > Double) > Tulos > Tulos > Tulos lift2t f t1 t2 = t1 `andthen` \d1 > t2 `andthen` \d2 > LukuTulos (f d1 d2) precond2 :: (Double > Double > Bool) > String > (Tulos > Tulos > Tulos) > Tulos > Tulos > Tulos precond2 p s f t1@(lukutulos d1) t2@(lukutulos d2) p d1 d2 = f t1 t2 otherwise = VirheTulos s precond2 _ t@(virhetulos _) _ = t precond2 t@(virhetulos _) = t precond2 estää funktion käyttämisen tilanteessa, jossa parametrit eivät sovi (kuten nollalla jako)
Ensimmäinen testiversio (1) data Laskutehtava =... deriving (Read, Show)... jasenna :: String > Either String Laskutehtava jasenna = Right. read muotoile :: Tulos > String muotoile = show main :: IO () main = do s < getline case s of "" > return () _ > do let (Right lt) = jasenna s tul = laske lt putstrln (muotoile tul) main
Ensimmäinen versio (2) Ei kovin käyttäjäystävällinen mutta on hyvä välivaihe testausta varten Kunnon jäsennys puuttuu Jäsennyksen virhetarkistus Kunnollinen tuloksen muotoilu puuttuu
Parempi muotoilu muotoile :: Tulos > String muotoile (LukuTulos d) = show d muotoile (VirheTulos s) = s
Jäsennys (1) Jäsennyksen tehtävänä on muuttaa merkkijono laskutehtävän rakennepuuksi: String > Laskutehtava virhetilanteiden vuoksi kuitenkin String > Either String Laskutehtava Jäsennys on erittäin paljon tutkittu ja hyvin ymmärretty ongelma
Jäsennys (2) Tavallisesti jäsennysongelma jaetaan kahtia: merkkijonon paloittelu sanasiksi vakiot, välimerkit ym. sanaslistan jäsentäminen Laskimen sanaset: data Sanaset = Vakio Double SulkuAuki SulkuKiinni Plus Miinus Kertaa Jako OutoMerkki Char deriving (Show)
palastele :: String > [Sanaset] palastele (' ':xs) = palastele xs palastele ('\t':xs) = palastele xs palastele ('\n':xs) = palastele xs palastele ('(':xs) = SulkuAuki : palastele xs palastele (')':xs) = SulkuKiinni : palastele xs palastele ('+':xs) = Plus : palastele xs palastele ('*':xs) = Kertaa : palastele xs palastele ('/':xs) = Jako : palastele xs palastele (' ':xs@(c:_)) '0' <= c && c <= '9' = let (d, r) = luku xs in Vakio ( d) : palastele r palastele (' ':xs) = Miinus : palastele xs palastele xs@(c:cs) '0' <= c && c <= '9' = let (d, r) = luku xs in Vakio d : palastele r otherwise = OutoMerkki c : palastele cs
luku :: String -> (Double, String) luku s = case span isdigit s of (n, '.':r) -> case span isdigit r of (m, r') -> (read (n++. ++m), r') (n, r) -> (read n, r) where isdigit c = '0' <= c && c <= '9'
Itse jäsennys Varsinainen jäsennys tehdään vaikkapa recursive descent tyylisesti siitä torstaina enemmän