TIEA34 Funktio-ohjelmointi, kevät 2008 Luento 3 Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 2. tammikuuta 2008
Ydin-Haskell: Syntaksi Lausekkeita (e) ovat: nimettömät funktiot: \x e lyhennysmerkintänä \x... x n e on \x \x n e funktiokutsu e e 2 assosioi vasemmalle: e e 2 e 3 = (e e 2 ) e 3 koostinlauseke Koostin e,..., e n koostin ottaa aina tietyn määrän argumentteja (voi olla nolla) lyhennysmerkintänä Koostin on \x... x n Koostin x,..., x n
Ydin-Haskell: Syntaksi 2 Lausekkeita (e) ovat myös: case-lauseke case e of p e ;... ; p n e n hahmo (p i ) voi olla muuttuja hahmo voi olla koostin, jolla on muuttujia parametreina hahmot eivät ole rekursiivisia paikalliset määrittelyt let x = e ;... ; x n = e n in e määrittelyt voivat olla keskenään rekursiivisia määrittelyn vasemmalla puolella on aina muuttuja, ei mitään muuta määriteltävät muuttujat näkyvät kaikissa lausekkeissa yhtäläisesti määrittelyt peittävät samannimisten muuttujien let-lausekkeen ulkopuolella annetut määritelmät
Luvut ym. data Integer =... 2 0 2... Toisin sanoen Haskellissa kokonaisluvut käsitetään (syntaksia vaille) nollaparametrisiksi koostimiksi. Ydin-Haskellissa ei lukuja ole, mutta toisinaan tämä rajoitus unohdetaan käytännön syistä.
Laskusäännöt: β-muunnos (\x e ) e 2 let x = e 2 in e
Laskusäännöt: δ-muunnos let ; x i = e i ; in x i let ; x i = e i ; in e i
Laskusäännöt: case-muunnos case e of x e ; let x = e in e case Koostin e,..., e n of Koostin x... x n e ;... let x = e,..., x n = e n in e case Goostin e,..., e m of Koostin x... x n e ;... case Goostin e,..., e m of... case \x e of Koostin x... x n e ;... case \x e of...
Laskusäännöt: let-muunnokset let... in \x e ( ) \x let... in e let... in e e 2 (let... in e ) (let... in e 2 ) let... in Koostin e,..., e n Koostin let... in e,..., let... in e n
Laskusäännöt: let-muunnokset 2 let... in case e of p e ;... ; p n e n ( ) case let... in e of p let... in e ;... ; p n let... in e n let x = e ;... ; x k = e k in let x k+ = e k+ ;... ; x n = e n in e ( ) let x = e ;... ; x k = e k ; x k+ = e k+ ;... ; x n = e n in e let in e e Lisäksi let-lausekkeesta saadaan poistaa turhat määritelmät.
( ) laskusäännöissä, joissa let tuodaan toisen paikallisia muuttujia luovan rakenteen (funktiot, case-hahmot) ohi tai yhdistetään toiseen let-lausekkeeseen, on vaara; esim.: let x = True in \x x \x let x = True in x ratkaisu on nimetä sisemmät paikalliset muuttujat uudestaan ennen laskusäännön soveltamista niin, että konfliktia ei ole: let x = True in \x x \y let x = True in y metasääntö: paikallisen muuttujan nimen saa aina vaihtaa, kunhan muistaa vaihtaa sen myös siellä, missä sitä käytetään
Redeksit ja laskentajärjestykset lauseke, johon (koko lausekkeeseen) voi soveltaa jotain laskusääntöä, on redeksi applikatiivisessa järjestyksessä valitaan aina sisin ja vasemmanpuoleisin redeksi (laskenta alhaalta ylös ) normaalijärjestyksessä valitaan aina uloin ja vasemmanpuoleisin redeksi (laskenta ylhäältä alas )
Ydin-Haskellin normaalimuodot Lauseke on normaalimuoto, jos se ei sisällä yhtään redeksiä. Ydin-Haskellin lauseke on normaalimuoto, jos se on \x e, ja e on normaalimuoto, tai Koostin e,..., e n, ja e,..., e n ovat normaalimuotoja. Teoreema: Jos lausekkeella on normaalimuoto, jokainen ko. lausekkeesta aloitettu laskenta, jos se lainkaan päättyy, päättyy kyseiseen normaalimuotoon. Teoreema: Jos lausekkeella on normaalimuoto, normaalijärjestyksessä suoritettu ko. lausekkeesta aloitettu laskenta päättyy aina kyseiseen normaalimuotoon.
Heikko päänormaalimuoto (WHNF) engl. weak head normal form Lauseke on WHNF, jos sen muodosta on pääteltävissä suoraan, sopiiko se koostinhahmoon. Ydin-Haskellin lauseke on WHNF, jos se on \x e, tai Koostin e,..., e n. (Huomaa: ei vaadita, että e tai e,..., e n olisivat WHNF.) Case-lausekkeen laskennan idea : jos sovitettava hahmo on hylkäävä, laske testattava lauseke ensin WHNF:ksi, jonka jälkeen sovittaminen onnistuu.
Lausekkeen graafiesitys Lähtökohtana lausekkeen rakennepuu. Poikkeuksena let-lausekkeen esitys: kukin let-lausekkeessa esitellyn muuttujan käyttö esitetään viittaamalla suoraan ko. muuttujan määritelmään let-lauseketta ei näin tarvitse esittää erikseen
let fib = : : zipwith (+) fib (tail fib) in fib tail zipwith (+)
Graafinsievennys etsitään graafin juurta lähin redeksi, joka ei ole funktiomäärittelyn tai koostimen alla; jos useita, valitaan vasemmanpuoleisin koostimen alla olevia redeksejä saatetaan sieventää, jos on tarpeen selvittää koko lausekkeen normaalimuoto (esim. käyttäjälle tulostamista varten) redeksin juuri ylikirjoitetaan sievennyksen tuloksen juurella
let fib = : : zipwith (+) fib (tail fib) in take 3 fib take 3 tail zipwith (+)
take 2 tail zipwith (+)
take tail zipwith (+)
2 take 0 2 tail zipwith (+)
2 [] 2 tail zipwith (+)
graafinsievennystä sanotaan myös laiskaksi laskennaksi Haskell ei takaa, että ohjelmat lasketaan laiskasti (eli graafinsievennyksellä) Haskell takaa vain nonstriktiyden hylkäävään hahmoon sovittaminen aiheuttaa testattavan lausekkeen laskemisen WHNF:ksi jotkin primitiivit (kuten kokonaislukujen yhteenlasku) laskevat argumenttinsa ennen kuin palauttavat tuloksen muutoin lauseketta ei yritetä laskea silti käytännnön Haskell-ohjelmoinnissa yleensä oletetaan, että graafinsievennys on käytössä (ja se on yleensä totta)
Solmun solmiminen, korekursio vakion määrittelemistä itseviittaavasti (kuten edellä fib ) kutsutaan solmun solmimiseksi olennaista on, että vakion määrittelevän lausekkeen voi sieventää WHNF:ksi ilman, että itseviittaukseen joudutaan koskemaan tällä tavalla itseviittaava määritelmä on korekursiivinen rekursio: itseviittausten ketju päättyy joskus korekursio: määrittely tuottaa likiarvon tuloksestaan äärellisessä ajassa, ja arvoa voi täsmentää kulkemalla itseviittauksen kautta tällainen likiarvo (jota ei ole tarkoitus enää täsmentää) merkitään todistuksissa ja laskuissa usein niin, että itseviittauksen paikalle merkitään esim. fib : : 2 : 3 :
Likiarvojen jono fib : : fib : : 2 : fib : : 2 : 3 : fib : : 2 : 3 : 5 : fib : : 2 : 3 : 5 : 8 :... (Voidaan ajatella, että fib on tämän jonon raja-arvo.)
Kaksoislinkitty kiertolista d a t a D L i s t a = D L i s t { i t e m : : a, p r e v : : D L i s t a, n e x t : : D L i s t a } f r o m L i s t : : [ a ] > D L i s t a f r o m L i s t l i = l e t ( f, l ) = go l l i f i n f where go p r e v [ ] next = ( next, p r e v ) go p r e v ( x : x s ) n e x t = l e t t h i s = D L i s t x p r e v r e s t ( r e s t, l a s t ) = go t h i s x s n e x t i n ( t h i s, l a s t ) (Piirrä graafiesitys yksinkertaisesta esimerkkilistasta!)
Kirjastofunktio cycle k i r j a s t o n t o t e u t u s, s o l m i i solmun : cycle : : [ a ] > [ a ] cycle [ ] = undefined cycle xs = xs where xs = xs ++ xs n a i v i t o t e u t u s, e i s o l m i solmua : cycle [ ] = undefined cycle xs = xs ++ cycle xs
graafinsievennyksessä(kin) funktion määritelmä kopioidaan sitä sovellettaessa siksi funktiomäärittelyn läpi kulkeva solmu ei solmiudu! funktio voi kyllä palauttaa solmun, joka on lokaalisti määritelty (kuten cycle tekee)
Tuottaja, suodatin ja kuluttaja tietorakenteena lista tietorakenteena lista TUOTTAJA SUODATIN KULUTTAJA tuottaja :: A > [B] suodatin :: [B] > [C] kuluttaja :: [C] > D laskentajärjestyksen ansiosta putkessa kulkee aina vain muutama alkio kerrallaan voi olla myös monimutkaisempia putki-suodatin-rakenteita esim. tuottaja voi olla syötteen luin, suodattimia voivat olla lekseri, ja kuluttaja voi olla jäsennin