TIEA341 Funktio-ohjelmointi 1, kevät 2008 Luento 3 Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 14. tammikuuta 2008
Viittausten läpinäkyvyyden 1 periaatteet 1. Lausekkeen arvo ei riipu muusta kuin sen osien arvosta. 2. Lauseke voidaan aina korvata arvollaan minkään (muun kuin tehokkuuden) muuttumatta. 3. Haskell noudattaa edellä sanottua periaatetta kaikkialla poikkeuksena eräät vaarallisiksi (engl. unsafe) merkityt funktiot niitä ei tällä kurssilla saa käyttää ellei erikseen ole muuta sanottu 1 engl. referential transparency
Hypoteettinen tilanne Olkoon olemassa funktio getchar :: Char, joka lukee merkin käyttäjältä ja palauttaa sen. Kysymys: Mikä on lausekkeen getchar + getchar arvo?
Hypoteettinen tilanne Olkoon olemassa funktio getchar :: Char, joka lukee merkin käyttäjältä ja palauttaa sen. Kysymys: Mikä on lausekkeen getchar + getchar arvo? Vastaus: Viittausten läpinäkyvyyden perusteella sama kuin lausekkeen let c = getchar in c + c arvo!
Hypoteettinen tilanne Olkoon olemassa funktio getchar :: Char, joka lukee merkin käyttäjältä ja palauttaa sen. Kysymys: Mikä on lausekkeen getchar + getchar arvo? Vastaus: Viittausten läpinäkyvyyden perusteella sama kuin lausekkeen let c = getchar in c + c arvo! @#!@$ eikö siirräntä onnistu Haskellissa?
Nerokas vastaus Haskell-ohjelma voi palauttaa imperatiivisen ohjelman kyseisiä ohjelmia kutsutaan akteiksi (engl. action) lauseketta tai funktiota, joka palauttaa aktin, sanotaan komennoksi komennon, jonka tuloksena on tyyppiä T oleva arvo, tyyppi on IO~T esim. getchar :: IO Char
Haskell-ohjelman suoritus 1. lasketaan Main-modulissa olevan main -funktion arvo main :: IO T jollekin tyypille T T on tavallisimmin () ainoa arvo on () :: () Main on ainoa moduli, jonka tiedostonimen ei tarvitse vastata modulin nimeä 2. tuloksena saatu akti suoritetaan Käytännössä nämä askeleet lomittuvat, koska osa aktista sisältää Haskell-lausekkeita, joiden arvon laskemiseen tarvitaan syötettä, jota taas saadaan vasta, kun akti on jo tulostanut jotain.
module Main where main : : IO ( ) main = putstrln " H e l l o World! "
Jos GHCi:n kehoitteeseen kirjoittaa komennon, se suorittaa sen tuottaman aktin: $ ghci hello.hs _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.6.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. [1 of 1] Compiling Main ( hello.hs, interpreted ) Ok, modules loaded: Main. *Main> main Hello World! *Main> :quit Leaving GHCi. $
Ohjelman voi suorittaa suoraan käyttämällä runghc-komentoa: $ runghc hello.hs Hello World! $ Ohjelman voi myös kääntää: $ ghc -W --make hello.hs -o hello [1 of 1] Compiling Main ( hello.hs, hello.o ) Linking hello... $./hello Hello World! $
type FilePath = String Kirjastokomentoja putchar :: Char > IO () tulostaa annetun merkin putstr :: String > IO () tulostaa annetun merkkijonon putstrln :: String > IO () tulostaa annetun merkkijonon ja rivinvaihdon getchar :: IO Char lukee merkin käyttäjältä getline :: IO String lukee rivin käyttäjältä getcontents :: IO String lukee koko syötteen readfile :: FilePath > IO String lukee nimetyn tiedoston writefile :: FilePath > String > IO () kirjoittaa annetun tiedoston appendfile :: FilePath > String > IO () kirjoittaa annetun tiedoston loppuun Modulissa System.IO (import System.IO modulin where-sanan jälkeen): data IOMode = ReadMode WriteMode AppendMode ReadWriteMode stdin :: Handle käyttäjän syöte (vakiosyötevirta) stdout :: Handle käyttäjän näyttö (vakiotulostevirta) stderr :: Handle virhetulostus (vakiovirhevirta) openfile :: FilePath > IOMode > IO Handle avaa nimetyn tiedoston hputchar :: Handle > Char > IO () tulostaa annetun merkin tiedostoon hputstr :: Handle > String > IO () tulostaa annetun merkkijonon tiedostoon hputstrln :: Handle > String > IO () tulostaa annetun merkkijonon ja rivinvaihdon tiedostoon hgetchar :: Handle > IO Char lukee merkin tiedostosta hgetline :: Handle > IO String lukee rivin tiedostosta hgetcontents :: Handle > IO String lukee koko tiedoston hflush :: Handle > IO () tyhjentää kirjoituspuskurin tiedostoon hiseof :: Handle > IO Bool palauttaa True jos ollaan tiedoston lopussa hclose :: Handle > IO () sulkee tiedoston Huom! Tiedoston lukeminen readfile llä tms. ja siihen kirjoittaminen samassa ohjelmassa on vaarallista.
Komentojen koostaminen module Main where import System. IO main : : IO ( ) main = do putstr "Anna n i m e s i : " hflush stdout nimi < getline putstr " Terve, " putstr nimi putstrln ". "
do-avainsana aloittaa peräkkäistyslohkon vrt. Javan {...} -lohko peräkkäin suoritettavat komennot luetellaan peräkkäin, kukin omalla rivillään sisennettävä samaan kohtaan! lohkon tyyppi ja paluuarvo on viimeisen komennon tyyppi ja paluuarvo komennon tuottama arvo voidaan tallettaa paikalliseen muuttujaan < -rakenteen avulla nuolen vasemmalla puolella hahmo nuolen oikealla puolella komento hahmon luomat muuttujat näkyvät lohkon sisällä ko. riviä seuraavilla riveillä ei sijoituslause! muuttuja on oikeastaan vakio paikallisia muuttujia voi luoda myös let-avainsanan avulla kuten GHCi-kehoitteessa GHCi-kehoite on oikeastaan yksi iso do-lohko
Asettelusääntö let x = 1 + 1 y = 1 + 7 z = x + 1 in x + y + z
engl. layout rule jotkut avainsanat (mm. let ja do) aloittavat sisennysjoukon sisennysjoukossa kukin samalla tavalla sisennetty rivi on yksi kohta (määrittely, komento tms.) kohta jatkuu seuraavalle riville, jos sitä sisennetään enemmän sisennysjoukko päättyy, kun sisennetään vähemmän tai kun sisennysjoukon jatkuminen olisi syntaksivirhe kohdat voi erotta toisistaan myös puolipisteillä (huonoa tyyliä, mutta joskus hyödyllistä)
Aktit ovat arvoja module Main where a k t i l i s t a : : [ IO ( ) ] a k t i l i s t a = [ putstrln " r i v i 1", putstrln " r i v i 2", putstrln " r i v i 3" ] s e k v e n s o i : : [ IO ( ) ] > IO ( ) s e k v e n s o i [ ] = r e t u r n ( ) s e k v e n s o i ( a c t : a c t s ) = do a c t s e k v e n s o i a c t s main : : IO ( ) main = s e k v e n s o i a k t i l i s t a return :: a > IO a on komento, jolla ei ole sivuvaikutuksia ja joka palauttaa parametrinsa; huomaa, että se ei ole samanlainen kuin Javan return. Vakiokirjastossa on sequence_ :: [IO a] > IO () joka toimii kuten sekvensoi.
Grafiikka module Main where i m p o r t Graphics. SOE. Gtk s p a c e C l o s e : : Window > IO ( ) s p a c e C l o s e w = do k < getkey w c a s e k o f > closewindow w _ > s p a c e C l o s e w main : : IO ( ) main = r u n G r a p h i c s $ do w < openwindow "Eka g r a f i i k k a o h j e l m a n i " ( 3 0 0, 3 0 0 ) drawinwindow w $ t e x t ( 1 0 0, 2 0 0 ) " Hei, g r a f i i k k a m a a i l m a! " s p a c e C l o s e w
Dollarinmerkki korvaa aloittavan sulkeen; lopettavaa suljetta ei laiteta itse vaan sulkeisiin tuleva lauseke jatkuu niin pitkälle kuin mahdollista openwindow :: Title > Size > IO Window avaa ikkunan type Title = String type Size = (Int,Int) rungraphics :: IO () > IO () ajaa grafiikkaohjelman; jotkut käyttöjärjestelmät tarvitsevat alustusta, ja tämä hoitaa sen drawinwindow :: Window > Graphic > IO () piirtää grafiikan ikkunaan text :: Point > String > Graphic luo grafiikan, joka koostuu annetusta pisteestä (vasen alakulma) alkavasta merkkijonosta type Point = (Int, Int) (0,0) on ikkunan vasen yläkulma getkey :: Window > IO Char odottaa käyttäjän painavan näppäintä ikkunassa ja palauttaa vastaavan merkin closewindow :: Window > IO () sulkee ikkunan
silmukka on yksinkertaisesti funktio, joka kutsuu itseään viimeisenä tekonaan ns. häntärekursio: ei vie pinotilaa case... of... -rakenne mahdollistaa hahmonsovituksen funktion sisällä: case l a u s e k e of hahmo1 > l a u s e k e 1 hahmo2 > l a u s e k e 2 hahmo3 > l a u s e k e 3 hahmo-lausekepareja voi olla kuinka monta tahansa halutaan hahmoja sovitetaan peräjälkeen lausekkeeseen kunnes joku täsmää of aloittaa sisennysjoukon
Lisää grafiikkakomentoja Modulissa Graphics.SOE.Gtk: data Color = Black Blue Green Cyan Red Magenta Yellow White ellipse :: Point > Point > Graphic piirtää ellipsin, joka mahtuu pisteiden määrittämään suorakaiteeseen shearellipse :: Point > Point > Point > Graphic piirtää ellipsin, joka mahtuu suunnikkaaseen, jonka kolme neljästä kulmasta on annettu line :: Point > Point > Graphic piirtää janan annettujen pisteiden välille polyline :: [Point] > Graphic piirtää annettujen pisteiden kautta kulkevan murtoviivan (ei suljettu) polygon :: [Point] > Graphic piirtää monikulmion, jonka kulmat on annettu polybezier :: [Point] > Graphic piirtää annettujen pisteiden määrittämän Bezier-käyrän withcolor :: Graphic > Graphic vaihtaa grafiikka-arvon piirtoväriä
module Main where i m p o r t Graphics. SOE. Gtk p i c 1 : : G r a p h i c p i c 1 = w i t h C o l o r Red $ e l l i p s e ( 1 5 0, 1 5 0 ) ( 3 0 0, 2 0 0 ) p i c 2 : : G r a p h i c p i c 2 = w i t h C o l o r B l u e $ p o l y l i n e [ ( 1 0 0, 5 0 ), ( 2 0 0, 5 0 ), ( 2 0 0, 2 5 0 ), ( 1 0 0, 2 5 0 ), ( 1 0 0, 5 0 ) ] main : : IO ( ) main = r u n G r a p h i c s $ do w < openwindow " K u v i o i t a " ( 3 0 0, 3 0 0 ) drawinwindow w p i c 1 drawinwindow w p i c 2 getkey w closewindow w
module Main where i m p o r t Graphics. SOE. Gtk f i l l T r i : : Window > P o i n t > I n t > IO ( ) f i l l T r i w ( x, y ) s i z e = drawinwindow w $ w i t h C o l o r Blue $ p o l y g o n [ ( x, y ), ( x+s i z e, y ), ( x, y s i z e ), ( x, y ) ] m i n S i z e : : I n t m i n S i z e = 8 s i e r p i n s k i T r i : : Window > P o i n t > I n t > IO ( ) s i e r p i n s k i T r i w ( x, y ) s i z e = c a s e s i z e <= m i n S i z e o f True > f i l l T r i w ( x, y ) s i z e F a l s e > do l e t s i z e 2 = s i z e d i v 2 s i e r p i n s k i T r i w ( x, y ) s i z e 2 s i e r p i n s k i T r i w ( x, y s i z e 2 ) s i z e 2 s i e r p i n s k i T r i w ( x+s i z e 2, y ) s i z e 2 main = r u n G r a p h i c s $ do w < openwindow " S i e r p i n s k i n k o l m i o " ( 4 0 0, 4 0 0 ) s i e r p i n s k i T r i w ( 5 0, 3 0 0 ) 256 getkey w closewindow w Vertailuoperaattorit: (==) :: Eq a =>a > a > Bool yhtä suuri kuin (/=) :: Eq a =>a > a > Bool eri suuri kuin (<) :: Ord a =>a > a > Bool pienempi kuin (<=) :: Ord a =>a > a > Bool pienempi tai yhtä suuri kuin (>) :: Ord a =>a > a > Bool suurempi kuin (>=) :: Ord a =>a > a > Bool suurempi tai yhtä suuri kuin