Demo 7 (14.12.2005) Antti-Juhani Kaijanaho 9. joulukuuta 2005 Liitteenä muutama esimerkki Ydin-Haskell-laskuista. Seuraavassa on enemmän kuin 12 nimellistä tehtävää; ylimääräiset ovat bonustehtäviä, joilla voi korvata tällä tai edellisillä kerroilla tekemättä jääneitä tehtäviä. 1. Laske matemaatikon tarkkuudella mutta perustellen seuraavat: (a) mgu(int Char, α Char) (b) mgu(maybe α Char, α Char) (c) mgu(maybe β Char, α Char) (d) mgu(maybe β Char, Either α γ Char) 2. Selvitä täsmällisesti (jokainen vaihe dokumentoiden), seuraavien Ydin- Haskell-lausekkeiden tyypit tyyppiympäristön List/1, Integer/1, Cons :: α.α List α List α, Nil :: α.list α, 0 :: Integer, 1 :: Integer, 2 :: Integer, pred :: Integer Integer vallitessa: (a) let xs = Cons 1 ys ys = Cons 2 xs in xs TIEA341 Funktio-ohjelmointi 1, syksy 2005 1
(b) let xs = Cons 1 ys ys = Cons 2 xs take n li = case n of 0 Nil _ case li of Nil Nil Cons x xs Cons x (take (pred n 1) xs) in take 2 xs 3. Selvitä matemaatikon tarkkuudella seuraavien Ydin-Haskell-lausekkeiden tyypit (edellisen tehtävän tyyppiympäristössä): (a) let map f li = case li of Nil Nil Cons x xs Cons (f x) (map f xs) in map (b) let foldr f a li = case li of Nil a Cons x xs f x (foldr f a xs) in foldr 4. Määrittele algebrallinen tyyppi Polynomial, joiden avulla on mahdollista esittää rationaalilukukertoimisia polynomeja (ts. lausekkeita, joissa esiintyy rationaalilukuvakioita, muuttujia, kertolaskua, yhteenlaskua ja kokonaislukupotenssiin korottamista). 2
5. (Jatkoa edelliseen.) Lisää edellisen tehtävän ratkaisuusi funktio prettyprint :: Polynomial -> String, joka muuttaa polynomin kauniiksi ja luettavaksi merkkijonoksi. 6. (Jatkoa edelliseen.) Toteuta jäsentäjä parse :: String -> Polynomial, joka jäsentää merkkijonona esitetyn rationaalilukukertoimisen polynomin ja tuottaa ulos siitä rakennepuuesityksen, joka on edellisessä tehtävässä määrittelemääsi tyyppiä. 7. (Edellinen tehtävä on kahden nimellisen tehtävän arvoinen.) 8. (Jatkoa edelliseen.) Kirjoita Haskell-funktio normalize :: Polynomial -> Polynomial, joka sieventää polynomin normaalimuotoon. Polynomin normaalimuoto on termien summa. Termi on tulo, jossa tekijöinä ovat vakio sekä muuttujia (jos termissä esiintyy sama muuttuja useaan kertaan, tämä ilmaistaan potenssina). Erikoistapauksena on mahdollista, että termistä puuttuu vakiotekijä (mikä on sama kuin jos se olisi 1) tai muuttujat, mutta joko vakion tai muuttujan tulee siinä esiintyä. Normaalimuodossa ei esiinny kahta termiä, joilla on samat muuttujatekijät. Normaalimuodossa termin vakiotekijä on ensimmäisenä ja muuttujat seuraavat aakkosjärjestyksessä; termit ovat summassa termin muuttujatekijöiden lukumäärän laskevassa järjestyksessä (toissijaisesti muuttujatekijöiden aakkosjärjestyksen mukaan). Esimerkki normaalimuotoisesta polynomista on 2x 2 y 4 + 3y 5 + 2x 4 7xy 2 xy + x y. 9. (Edellinen tehtävä on kahden nimellisen tehtävän arvoinen.) 10. (Jatkoa edelliseen.) Kirjoita Haskell-funktio derive :: String -> Polynomial -> Polynomial, jolle annetaan muuttujan nimi ja polynomi, ja se palauttaa ko. polynomin derivaatan ko. muuttujan suhteen. 11. Määrittele algebrallinen tyyppi HTML, jolla voi esittää ainakin joitakin hyödyllisiä HTML-sivuja rakennepuumuodossa. Koeta rakentaa tyyppi niin, että sillä ei ole mahdollista esittää epävalideja sivuja. 12. (Jatkoa edelliseen.) Kirjoita funktio showhtml :: HTML -> String, joka muuttaa rakennepuuesityksen validiksi HTML-sivuksi. 3
13. Kirjoita Haskellilla rajattoman pelialueen ristinollapeli, jossa on ainakin alkeellinen tekoäly niin, että sitä voi pelata yksin tietokonetta vastaan. Pelin säännöt: peliä pelataan rajattomalla ruudukolla, joka on aluksi tyhjä. Toinen pelaaja pelaa ristinä, toinen ympyränä. Kukin pelaaja merkitsee vuorollaan ruudukosta jonkin vapaan ruudun itselleen (merkiten siihen ristin tai ympyrän sen mukaan, kumpana hän pelaa). Se pelaaja voittaa, joka saa ensimmäisenä merkittyä itselleen viisi vierekkäistä ruutua (pystyyn vaakaan tai vinoon). Peli ei pääty koskaan tasapeliin. Älä viilaa käyttöliittymää tarpeettoman pitkään! 14. (Edellinen tehtävä on kolmen nimellisen tehtävän arvoinen.) 15. (Edellinen tehtävä on kolmen nimellisen tehtävän arvoinen.) 4
Liite: Korjauksia Ydin-Haskell-monisteeseen Normaalimuodon ja (vahvan) päänormaalimuodon määritelmissä lambdalausekkeen tapaukset ovat menneet väärin päin. Normaalimuodossa vaaditaan tietenkin, että lambda-lausekkeen alilauseke on myös normaalimuodossa, ja vahvassa päänormaalimuodossa siltä riittää heikko päänormaalimuoto. Tyyppiskeema voidaan kirjoittaa myös α.τ. Unikaation määritelmä on rikki. Tässä uusi. mgu(t τ 1... τ n, T τ 1... τ n)) = σ n missä σ i = σ i 1 mgu(τ i σ i 1, τ iσ i 1 ) ja σ 0 = {} mgu(α, τ) = {(α τ)} mgu(τ, α) = {(α τ)} kunhan τ:ssa ei esiinny α kunhan τ:ssa ei esiinny α mgu(τ 1 τ 2, τ 3 τ 4 ) = mgu(τ 2 σ, τ 4 σ)) missä σ = mgu(τ 1, τ 3 ) Merkintä σ 1 σ 2 määritellään seuraavasti: Jos σ 1 sisältää parin α τ ja σ 2 ei sisällä paria α:lle, niin σ 1 σ 2 sisältää parin α τ. Jos σ 2 sisältää parin α τ ja σ 1 ei sisällä paria α:lle, niin σ 1 σ 2 sisältää parin α τ. Jos σ 1 sisältää parin α τ 1 ja σ 2 sisältää parin α τ 2, niin σ 1 σ 2 sisältää parin α τ 2 σ 1. Operaattoria ei pidä käyttää. Tyypityssäännöissä esiintyvä σ σ pitää ymmärtää σσ. Muuttujan tyypityssääntö on rikki. Korjattu versio kuuluu seuraavasti: Jos syöte on Γ x :: τ ja Γ:ssa on tyypitys x :: τ, missä τ ei ole tyyppiskeema, tällöin σ korvataan σ mgu(τ, τ ):lla, jos se on olemassa. Jos tämä onnistuu, algoritmi onnistuu. Jos τ onkin tyyppiskeema muotoa α 1,..., α n.τ, toimitaan seuraavasti. Valitaan n kpl uutta tyyppimuuttujaa α i. Jos σ = mgu(τ, τ {α 1 α 1,..., α n α n} on olemassa, tällöin algoritmi onnistuu ja σ korvataan σσ :lla. (Oleellista tässä on, että tyyppiskeeman lokaalit tyyppimuuttujat pitää vaihtaa kullakin x:n käyttökerralla eri tyyppimuuttujiksi. Tämä mahdollistaa x:n polymorsen käytön.) 5
Vastaava korjaus tehdään konstruktorien tyypityssääntöön. Casen tyypityssääntöön pitää lisätä vielä: vaihtoehtolausekkeiden tyyppien tulee kaikkien unioitua, ja näin unioimalla saatu niiden yhteinen tyyppi on koko casen tyyppi. Jos tehtävänantona on selvittää lausekkeen ε tyyppi eikä tyyppiympäristöä ole määritelty, tapahtuu tämä soveltamalla tyyppisääntöjä tyyppiväitteeseen ε :: α. Jos tyyppiympäristö Γ on annettu, käytetään tyyppiväitettä Γ ε :: α, missä α on sellainen tyyppimuuttuja, joka ei esiinny Γ:ssa (paitsi ehkä tyyppiskeeman paikallisena tyyppimuuttujana). Haettu lausekkeen tyyppi on ασ, missä σ on tyyppijärjestelmän tilamuuttujan arvo lopuksi. 6
Liite: Muutamia mallilaskuja mgu(t α V, T V α) =? σ 1 = σ 0 mgu(ασ 0, Vσ 0 ) = mgu(α, V) = {α V} mgu(t α U, T V U) = σ 1 mgu(tσ 1, ασ 1 ) = {α V} mgu(t{α V}, α{α V}) = {α V} mgu(t, T) = {α V} Tyypitä λx C x tyyppiympäristössä Γ = T/1, C :: α.α T α. Γ λx C x :: α Γ, x :: β C x :: γ Funktiokutsun säännön ensimmäinen alikohta: Γ, x :: β C :: δ γ Onnistuu, nyt σ = mgu(δ γ, ψ T ψ) = {δ ψ, γ T ψ}. Toinen alikohta: ψ Γ, x :: β x :: δ Onnistuu, nyt σ = {δ ψ, γ T ψ} mgu(β, ψ) = {δ ψ, γ T ψ, β ψ}. Nyt alkuperäisen λ-lausekkeen sievennys onnistuu, ja σ = {δ ψ, γ ψ T ψ T ψ, β ψ} mgu(α, β γ) = {δ ψ, γ T ψ, β ψ, α ψ T ψ}. Nyt siis ασ = α{δ ψ, γ T ψ, β ψ, α ψ T ψ} = ψ T ψ. Vastaus on siis ψ T ψ. Tyypitä matemaatikon tarkkuudella case Cons 1 Nilof Nil 0; Cons x _ x tyyppiympäristössä Γ = List/1, Cons :: α.α List α, Nil :: α.list α, Integer/1, 0 :: Integer, 1 :: Integer. On selvää, että Cons 1 Nil tyypittyy, ja sen tyyppi on List Integer. Sama on kummankin hahmon tyyppi. Kummankin vaihtoehtolausekkeen tyyppi on Integer ja tästä tulee koko lausekkeen tyyppi. 7
Vastaus on siis Integer. Tyypitä matemaatikon tarkkuudella case Cons 1 Nilof 0 0; Cons x _ x tyyppiympäristössä Γ = List/1, Cons :: α.α List α, Nil :: α.list α, Integer/1, 0 :: Integer, 1 :: Integer. On selvää, että Cons 1 Nil tyypittyy, ja sen tyyppi on List Integer. Sama on jälkimmäisen hahmon tyyppi. Ensimmäisen hahmon tyyppi on kuitenkin Integer, ja koska tämä ei unioidu edellämainittujen tyyppien kanssa, ei lauseke tyypity. Vastaus on: lauseke ei tyypity. 8