TIEA341 Funktio-ohjelmointi 1, kevät 2008 Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 10. tammikuuta 2008
Arvot... ovat laskutoimituksen lopputuloksia... ovat lausekkeita, joihin ei voi soveltaa mitään laskusääntöä... voivat olla atomisia: 42, a... voivat olla rakenteisia: [1,2,3] on lista, johon kuuluvat atomiset arvot 1, 2 ja 3 (tässä järjestyksessä ja mahdolliset duplikaatit säilyttäen) "kissa" on merkkijono, johon kuuluvat atomiset arvot k, i, s, s ja a (tässä järjestyksessä ja mahdolliset duplikaatit säilyttäen) (42, "kissa") on pari, jonka ensimmäinen alkio on atominen arvo 42 ja toinen alkio on merkkijono "kissa"
Tyypit jokaisella lausekkeella (ja siten arvolla) on tyyppi, merkitään: lauseke :: tyyppi esimerkiksi: 42 :: Integer a :: Char "kissa":: String 3 * (9 + 5) :: Integer simple 3 9 5 :: Integer (2+3, "kissa") :: (Integer, String) huomaa: tyypinnimet alkavat isolla kirjaimella, funktionnimet pienellä!
Perustyyppi Integer arvot (matemaattisia) kokonaislukuja ei kiinteätä ala- eikä ylärajaa vain vapaan muistin määrä rajoittaa laskenta hidastuu lukujen suuruuden kasvaessa harjoituskäytössä paras kokonaislukutyyppi tarpeellinen myös monissa sovelluksissa kryptografia laskentatoimi
Perustyyppi Int vastaa suunnilleen Javan ym. int-tyyppiä lukualue vähintään 2 29... 2 29 1 (30 bittiä) minbound :: Int maxbound :: Int suhteellisen nopea, nopeus riippumaton luvun suuruudesta kun lukualue riittää ja nopeudella on merkitystä ylivuoto on bugi ohjelmassa, varo!
Perustyypit Float ja Double tutut liukulukutyypit Float vähintään IEEE single precision Float vähintään IEEE double precision IEEE-semantiikka ei ole taattua muista liukulukulaskennan vaarat
Lukuvakiot kokonaisluvut: 1234 (kymmenjärjestelmä) 0o2322 (kahdeksanjärjestelmä) 0x4d2 (kuusitoistajärjestelmä) liukuluvut: 32.0 0.2e-9 HUOM!.2 ja 2. ovat kiellettyjä ts. numeroita pitää olla desimaalipisteen molemmin puolin kuormitettuja liukulukuvakio on Float tai Double sen mukaan, mitä käyttöyhteydessä tarvitaan kokonaislukuvakio on mitä tahansa lukutyyppiä sen mukaan, mitä käyttöyhteydessä tarvitaan jos tilanteeseen sopii useampi lukutyyppi, valitaan Integer, jos se sopii, ja muuten Double
Aritmetiikka -a vastaluku kaikki lukutyypit a + b yhteenlasku kaikki lukutyypit a * b kertolasku kaikki lukutyypit a / b jakolasku liukulukutyypit a div b jakolasku kokonaislukutyypit a mod b jakojäännös kokonaislukutyypit a quot b jakolasku kokonaislukutyypit a rem b jakojäännös kokonaislukutyypit Huomaa: on takahipsu operandien tulee olla keskenään samaa tyyppiä div pyöristää alaspäin quot pyöristää nollaa kohti m * (n div m) + n mod m == n m * (n quot m) + n rem m == n
Lisää aritmetiikkaa abs a 8 a kaikki lukutyypit >< 1 jos a > 0 signum a 0 jos a = 0 kaikki lukutyypit >: 1 jos a < 0 1 recip a liukulukutyypit a pi π liukulukutyyppiä exp a e n liukulukutyypit log a ln a liukulukutyypit sqrt a a liukulukutyypit a ˆ b a b kokonaislukutyypit a ˆˆ b a b a liuku-, b kokonaislukutyyppiä a ** b a b liukulukutyypit logbase a b log a b liukulukutyypit sin a sin a liukulukutyypit cos a cos a liukulukutyypit tan a tan a liukulukutyypit
Lukujen tyyppimuunnokset liukuluvusta kokonaisluvuksi: truncate a katkaisemalla round a lähimpään kokonaislukuun (puolikas parilliseen) floor a a eli suurin enintään a:n kokoinen kokonaisluku ceiling a a eli pienin vähintään a:n kokoinen kokonaisluku kokonaisluvusta liukuluvuksi: fromintegral a automaattisia muunnoksia ei ole!
Perustyyppi Char merkkien tyyppi (vrt. Javan char) periaatteessa kattaa koko Unicode-merkistön (ISO 10646) käytännössä Unicode-tuki on puutteellinen suomea pystyy toki käyttämään merkkivakiot pitkälti kuten Javassa E \n \32 on sama kuin \o40 on sama kuin \x20 on sama kuin ei ole lukutyyppi! tyyppimuunnokset: fromenum = 32 toenum 32 :: Char = toenum on kuormitettu, kohdetyypin pitää selvitä yhteydestä (kuten edellä)
Operaattorit ovat funktioita 2 + 3 (+) 2 3 3 * 4 (*) 3 4 3 mod 4 mod 3 4 Huomaa syntaksi: funktion nimi takahipsuissa tekee siitä operaattorin operaattorin nimi sulkeissa tekee siitä funktion operaattorinimet muodostuvat symboleista funktionimet muodostuvat kirjaimista ja numeroista (ja alkavat kirjaimella)
Mikä mahtaa olla funktion tyyppi? Kysytään kääntäjältä: $ ghci _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.6.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Prelude> let simple x y z = x * (y + z) Prelude> :type simple simple :: (Num a) => a -> a -> a -> a Prelude> :type (+) (+) :: (Num a) => a -> a -> a Prelude> :type quot quot :: (Integral a) => a -> a -> a Prelude> :type floor floor :: (RealFrac a, Integral b) => a -> b Prelude> :quit Leaving GHCi. $
Tulkintaohje Tyyppi1 -> Tyyppi2 -> Tyyppi3 tarkoittaa funktiota, joka ottaa kaksi parametria (tyypeiltään Tyyppi1 ja Tyyppi2) ja palauttaa tyyppiä Tyyppi3 olevan arvon; (Num a) => tarkoittaa, että funktio on ylikuormitettu siten, että a:n paikalle tyypissä kelpaa mikä tahansa lukutyyppi; (Integral a) => tarkoittaa, että a:n paikalle kelpaa mikä tahansa kokonaislukutyyppi. (Fractional a) =>, (RealFrac a) => ja (Floating a) => tarkoittavat jokainen, että a:n paikalle kelpaa mikä tahansa liukulukutyyppi. Näitä voidaan yhdistellä, esim. (RealFrac a, Integral b) => tarkoittaa, että a:n paikalle käy liukulukutyyppi ja b:n paikalle kokonaislukutyyppi.
Kääntäjä laskimena... $ ghci _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.6.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Prelude> let simple x y z = x * (y + z) Prelude> simple 2 3 4 14 Prelude> simple 2 3 4 + 5 19 Prelude> 42 div 3 + 4 18 Prelude> :quit Leaving GHCi. $
Mitkä ovat ohjelmoinnin kolme tärkeintä käsitettä?
Mitkä ovat ohjelmoinnin kolme tärkeintä käsitettä? abstrahoiminen abstrahoiminen abstrahoiminen
Abstrahoiminen I: Nimeäminen circumference :: Double -> Double circumference r = 2 * pi * r tässä nimetään funktio tyypin määritteleminen ei ole pakollista mutta se on hyvää tyyliä circumference :: Double -> Double circumference r = let diameter = 2 * r in pi * diameter let ja in ovat avainsanoja diameter on tässä paikallinen nimi diameter näkyy vain let-in-rakenteen sisällä nimi voi olla joko muuttuja tai funktio funktiolla on parametreja muuttujalla ei ole oikeastaan muuttuja on funktio, jolla on nolla parametria!
diameter :: Double diameter = 42 circumference :: Double -> Double circumference r = let diameter = 2 * r in pi * diameter Kysymys: Kumpaa diameteriä käytetään lausekkeessa pi * diameter?
diameter :: Double diameter = 42 circumference :: Double -> Double circumference r = let diameter = 2 * r in pi * diameter Kysymys: Kumpaa diameteriä käytetään lausekkeessa pi * diameter? Vastaus: Sisempää (siis 2 * r), sillä sisempi määritelmä peittää ulomman määritelmän.
Määrittelyt ja lausekkeet Haskellin syntaksi jakaantuu selkeästi määrittelyjoukkoihin ja lausekkeisiin lauseke laskee arvon määrittelyjoukkoon kuuluu yhden tai useamman nimen määrittely, esim. diameter :: Double diameter = 42 circumference :: Double -> Double circumference r = let diameter = 2 * r in pi * diameter näitä ei pidä sekottaa keskenään! lauseke voi sisältää paikallisia määrittelyjoukkoja let-in-rakenteen avulla
GHCi:n kehoite haluaa normaalisti lausekkeita, mutta let-avainsanalla voi aloittaa rivin loppuun asti jatkuvan määrittelyjoukon (erota määrittelyt puolipisteellä): $ ghci _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.6.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Prelude> let diameter = 42 ; circumference r = let diameter = 2 * r in pi * diameter Prelude> circumference 2 12.566370614359172 Prelude> :quit Leaving GHCi. $
Abstrahoiminen II: Tietorakenteet Ongelma: Haluan laskea kolmen ympyrän ympärysmitan summan Ratkaisu?: circumference :: Double -> Double circumference r = 2 * pi * r threecirclescircum :: Double -> Double -> Double -> Double threecirclescircum a b c = circumference a + circumference b + circumference c
Yleistetään: tehdään funktio, joka laskee niin monen ympyrän ympärysmitan summan kuin halutaan. circumference :: Double -> Double circumference r = 2 * pi * r manycirclescircum ::??? -> Double manycirclescircum xs =???
Listat Jos T on tyyppi, niin [T] on T-tyyppisten arvojen lista. Lista luodaan luettelemalla: [1,2,3,4]. Tyhjä lista on []. x : xs tarkoittaa listaa, joka saadaan lisäämällä alkio x listan xs alkuun Lista xs ei tässä muutu.
Näin ollen: Funktion tyyppi lienee manycirclescircum :: [Double] -> Double Jos ei ole ympyröitä, ympärysmittojen summa lienee nolla: manycirclescircum [] = 0 Jos on monta ympyrää, summa lienee ekan ympyrän ympärysmitan ja muiden ympyröiden ympärysmitan summan summa: manycirclescircum (x:xs) = circumference x + manycirclescircum xs
circumference :: Double -> Double circumference r = 2 * pi * r manycirclescircum :: [Double] -> Double manycirclescircum [] = 0 manycirclescircum (x:xs) = circumference x + manycirclescircum xs
Kokeillaanpa. Tallennetaan edellinen tiedostoon many.hs ja sitten: $ ghci many.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 ( many.hs, interpreted ) Ok, modules loaded: Main. *Main> manycirclescircum [] 0.0 *Main> manycirclescircum [3,4,5] 75.39822368615503 *Main> :quit Leaving GHCi. $
Hahmonsovitus funktion parametri voi olla hahmo hahmoksi kelpaa aina vakio hahmonsovitus epäonnistuu, jos argumentin arvo ei ole sama kuin ko. vakion arvo hahmoksi kelpaa aina myös muuttuja sovitus onnistuu aina muuttuja näkyy paikallisena muuttujana ja sen arvo on argumentin arvo listahahmoksi kelpaa myös (hahmo1 : hahmo2) sovitus epäonnistuu, jos argumentti on tyhjä lista listan ekaan alkioon sovitetaan hahmo1:stä listan siihen osaan, joka saadaan poistamalla listasta ensimmäinen alkio, sovitetaan hahmo2:sta funktiolla voi olla useita peräkkäisiä määritelmiä; jos ensimmäisen hahmonsovitus epäonnistuu, niin kokeillaan seuraavaa jne.
Kuviomodulin alkua Tarvitaan moduli 1 erilaisten geometristen kuvioiden käsittelemiseen. Aluksi sen tulee kyetä laskemaan pinta-aloja. 1 Uudelleen käytettävissä oleva kokoelma tyyppien, vakioiden ja funktioiden määritelmiä.
Modulin rakenne module Shape (...) where... module ja where ovat avainsanoja wheren jälkeen tulee määritelmäjoukko Shape on itse valittu modulin nimi alkaa aina isolla kirjaimella moduli laitetaan aina omaan tiedostoonsa tiedoston nimi on Modulinnimi.hs nyt siis Shape.hs
Tyyppejä voitaisiin tehdä areacircle, arearectangle jne onnistuisiko määritellä oma tyyppi Shape ja siitä funktio area :: Shape -> Double?
Tyyppejä voitaisiin tehdä areacircle, arearectangle jne onnistuisiko määritellä oma tyyppi Shape ja siitä funktio area :: Shape -> Double? onnistuu: data Shape = Circle Double Square Double Shape on tyypin nimi Circle ja Square ovat muodostimia muodostimet alkavat isolla kirjaimella muodostimia voi käyttää funktioina: Circle :: Double -> Shape Square :: Double -> Shape
Hyödyllisempi tyyppi data Shape = Rectangle Double Double Ellipse Double Double RtTriangle Double Double Polygon [(Float, Float)] deriving Show Rectangle s1 s2 on suorakaide, jonka sivujen pituudet ovat s1 ja s2 Ellipse s1 s2 on ellipsi, jonka säteet ovat s1 ja s2 RtTriangle s1 s2 on suorakulmainen kolmio, jonka kateettien pituudet ovat s1 ja s2 Polygon [v1, v2,..., vn] on N-sivuinen monikulmio, jonka kulmat ovat pisteissä vi, jotka ovat (x,y)-pareja. deriving on avainsana; deriving Show mahdollistaa tyypin arvojen muuttamisen merkkijonoiksi haluttaessa
Tai ehkäpä data Shape = Rectangle Side Side Ellipse Radius Radius RtTriangle Side Side Polygon [Vertex] deriving Show type Radius= Double type Side = Double type Vertex= (Double, Double) type on avainsana Radius, Side ja Vertex ovat tyypinnimiä Radius ja Side ovat Doublen synonyymejä Vertex on (Double,Double):n synonyymi