Laajennetaan vielä Ydin-Haskellia ymmärtämään vakiomäärittelyt. Määrittely on muotoa

Samankaltaiset tiedostot
tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Tyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIEA341 Funktio-ohjelmointi 1, kevät 2008

2.4 Normaalimuoto, pohja ja laskentajärjestys 2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13

5.3 Laskimen muunnelmia 5.3. LASKIMEN MUUNNELMIA 57

Luku 4. Tietorakenteet funktio-ohjelmoinnissa. 4.1 Äärelliset kuvaukset

Ydin-Haskell Tiivismoniste

Funktionimien kuormitus. TIES341 Funktio ohjelmointi 2 Kevät 2006

Tämän vuoksi kannattaa ottaa käytännöksi aina kirjoittaa uuden funktion tyyppi näkyviin, ennen kuin alkaa sen määritemää kirjoittamaan.

Tyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet. TIES341 Funktio-ohjelmointi 2 Kevät 2006

TIEA341 Funktio-ohjelmointi 1, kevät 2008

5.5 Jäsenninkombinaattoreista

Abstraktit tietotyypit. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Haskell ohjelmointikielen tyyppijärjestelmä

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Luku 3. Listankäsittelyä. 3.1 Listat

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Geneeriset tyypit. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

Demo 7 ( ) Antti-Juhani Kaijanaho. 9. joulukuuta 2005

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Esimerkki: Laskin (alkua) TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Java-kielen perusteet

Yksinkertaiset tyypit

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Lisää laskentoa. TIEA341 Funktio ohjelmointi 1 Syksy 2005

5.1 Tyyppiparametrit. Nyt lisäämme parametrit myös data-määrittelyihin: data Nimi tp 1 tp 2 tp 3... tp k =...

12 Mallit (Templates)

sama tyyppi (joka vastaa kaikkien mahdollisten arvojen summa-aluetta). Esimerkiksi

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Uusi näkökulma. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Taas laskin. TIES341 Funktio ohjelmointi 2 Kevät 2006

Talousmatematiikan perusteet, L3 Prosentti, yhtälöt Aiheet

TIES542 kevät 2009 Rekursiiviset tyypit

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Luku 5. Monadit. 5.1 Siirrännän ongelma

ja λ 2 = 2x 1r 0 x 2 + 2x 1r 0 x 2

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Matematiikan tukikurssi, kurssikerta 3

FUNKTION KUVAAJAN PIIRTÄMINEN

Luku 2. Ohjelmointi laskentana. 2.1 Laskento

Monadeja siellä, monadeja täällä... monadeja kaikkialla? TIES341 Funktio ohjelmointi 2 Kevät 2006

Reaalilukuvälit, leikkaus ja unioni (1/2)

Ohjelmointiharjoituksia Arduino-ympäristössä

Laiska laskenta, korekursio ja äärettömyys. TIEA341 Funktio ohjelmointi Syksy 2005

Jäsennys. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Java-kielen perusteet

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Injektio (1/3) Funktio f on injektio, joss. f (x 1 ) = f (x 2 ) x 1 = x 2 x 1, x 2 D(f )

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

11. Javan valintarakenteet 11.1

Pythonin Kertaus. Cse-a1130. Tietotekniikka Sovelluksissa. Versio 0.01b

14.1 Rekursio tyypitetyssä lambda-kielessä

Tietueet. Tietueiden määrittely

S BAB ABA A aas bba B bbs c

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

11. Javan valintarakenteet 11.1

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

Matematiikan peruskurssi 2

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Dierentiaaliyhtälöistä

Ohjelmoinnin perusteet Y Python

Staattinen tyyppijärjestelmä

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Ohjelmoinnin perusteet, syksy 2006

Matriiseista. Emmi Koljonen

Kielioppia: toisin kuin Javassa

Tekijä Pitkä matematiikka Pisteen (x, y) etäisyys pisteestä (0, 2) on ( x 0) Pisteen (x, y) etäisyys x-akselista, eli suorasta y = 0 on y.

Tekijä Pitkä matematiikka Suoran pisteitä ovat esimerkiksi ( 5, 2), ( 2,1), (1, 0), (4, 1) ja ( 11, 4).

Java-kielen perusteita

ITKP102 Ohjelmointi 1 (6 op)

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

3.1 Mitä tarkoittaan heredoc? Milloin sitä kannattaa käyttää? Kirjoita esimerkki sen käyttämisestä.

Taulukot. Jukka Harju, Jukka Juslin

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

15. Ohjelmoinnin tekniikkaa 15.1

T Syksy 2006 Tietojenkäsittelyteorian perusteet T Harjoitus 7 Demonstraatiotehtävien ratkaisut

Harjoitus 5 (viikko 48)

Loppukurssin järjestelyt

Toinen harjoitustyö. ASCII-grafiikkaa 2017

T Syksy 2002 Tietojenkäsittelyteorian perusteet Harjoitus 8 Demonstraatiotehtävien ratkaisut

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Käy vastaamassa kyselyyn kurssin pedanet-sivulla (TÄRKEÄ ensi vuotta ajatellen) Kurssin suorittaminen ja arviointi: vähintään 50 tehtävää tehtynä

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

7. Näytölle tulostaminen 7.1

Ohjelmoinnin perusteet Y Python

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

Luento 5. Timo Savola. 28. huhtikuuta 2006

Transkriptio:

2.6. TIETOKONE LASKIMENA 23 Edellä esitetty Ydin-Haskell on hyvin lähellä sitä kieltä, jota GHCi (Glasgow Haskell Compiler, Interactive) sekä muut Haskell-järjestelmät suostuvat ymmärtämään. Esimerkiksi: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Prelude> (let { f = \ x -> case x of _ x == 0 -> Just 1 x > 0 -> case f (x - 1) of { Just x -> Just (x*x ) ; Nothing -> Nothing } x < 0 -> Nothing } in f) 5 Just 120 Prelude> (let { f = \ x -> case x of _ x == 0 -> Just 1 x > 0 -> case f (x - 1) of { Just x -> Just (x*x ) ; Nothing -> Nothing } x < 0 -> Nothing } in f) (-3) Nothing Laajennetaan vielä Ydin-Haskellia ymmärtämään vakiomäärittelyt. Määrittely on muotoa vakionnimi :: Tyyppilauseke vakionnimi = lauseke Tyypinmäärittelyrivi ei ole pakollinen, mutta se on suositeltava. Funktiovakioita voidaan määritellä myös siten, että vakionnimen ja yhtäsuuruusmerkin väliin kirjoitetaan yksi tai useampi muuttuja: vakionnimi :: Tyyppilauseke vakionnimi x y z = lauseke Tämä tarkoittaa (oleellisesti) samaa kuin vakionnimi :: Tyyppilauseke vakionnimi = λx λy λz lauseke Määrittelyt (sekä vakioiden että tyyppien) sijoitetaan tiedostoon, jonka nimi alkaa isolla alkukirjaimella ja päättyy päätteeseen.hs ja jonka ensimmäinen rivi kuuluu Module Nimi where, missä Nimi on tiedoston nimi ilman.hs-päätettä. Tiedostossa jokainen määrittely alkaa rivin vasemmasta laidasta (jatkorivit noudattavat aiemmin esitettyä sisennyskäytäntöä). Esimerkiksi voidaan tehdä seuraavanlainen Fact.hs: module Fact where fact :: Integer -> Maybe Integer

24 LUKU 2. OHJELMOINTI LASKENTANA fact n = case n of _ n < 0 -> Nothing n == 0 -> Just 1 n > 0 -> case fact (n - 1) of Just m -> Just (n * m) Nothing -> Nothing jonka jälkeen voidaankin käynnistää GHCi komennolla ghci Fact: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Compiling Fact ( Fact.hs, interpreted ) Ok, modules loaded: Fact. *Fact> fact 5 Just 120 *Fact> 2.7 Kohti aitoa Haskellia Ydin-Haskell on yksinkertainen ja sikäli kiva kieli käsin laskettavaksi. Koneen toimiessa laskimena on kuitenkin paljon tärkeämpää, että käytetty kieli on ohjelmien kirjoittajalle käytännöllinen kuin että se olisi laskijalle yksinkertainen. Sen takia, vaikka pääosin Ydin-Haskell onkin sellaisenaan Haskellia, on itse Haskell-kieli paljon rikkaampi. Seuraavassa esitellään muutama tärkeä laajennus, jotka eivät enää kuulu Ydin-Haskelliin mutta joita käytännössä käytetään jatkuvasti. 2.7.1 Tyyppialiakset Mille tahansa tyypille voidaan antaa nimi eli alias. Tämä tapahtuu type-määrittelyllä. Esimerkiksi String on tyypin [Char] alias: type String = [Char] Tyyppialiasta voidaan käyttää kaikissa tilanteissa, joissa kyseistä tyyppiä voidaan käyttää. Tyyppialias voi olla parametrisoitu siinä kuin mikä tahansa muukin tyyppikoostin. Tyyppialias ei ole uusi tyyppi. Sen tärkein rajoite on se, että type-määrittelyt eivät voi olla keskenään rekursiivisia (kuin korkeintaan algebrallisen tyyppimäärittelyn kautta kulkemalla). Esimerkki 12 Seuraava määrittely ei ole sallittu: type

2.7. KOHTI AITOA HASKELLIA 25 2.7.2 Monikot Tyyppi data Pair α β = Pair α β on niin yleinen funktio-ohjelmoinnissa, että sille on Haskellissa oma syntaksinsa: parityyppi kirjoitetaan tyyppilausekkeissa (α, β), sen koostin on pilkkuoperaattor (, ), jota voidaan käyttää sekä tyyliin (, ) 2 3 että (2, 3). Kumpikin muodoista (, ) x y ja (x, y) kelpaavat hahmoiksi case-lausekkeissa. Itse asiassa edellä sanottu pätee kaikille monikoille, ei pelkästään pareille: esimerkiksi kolmikkotyyppi on (α, β, γ), ja yksi sen lausekkeista on (3, 4, 5). Esimerkki 13 Seuraavassa on muutama monikkoarvo tyyppeineen: 1. (3, 4) :: (Integer, Integer) 2. ((0, a ), (λx x), ()) :: ((Integer, Char), α α, ()) 3. (,, ) b 46 (λx (x, x)) :: (Char, Integer, α (α, α)) 2.7.3 Funktiosidonta Funktiosidonta on yleistetty versio edellä esitetystä vakionmäärittelystä. Yleisesti funktiosidonta on seuraavaa muotoa: f p 1,1... p 1,k g 1,1 = e 1,1...... g 1,m1 = e 1,m1 f p n,1... p n,k g n,1 = e n,1... g n,mn = e n,mn Tämä tarkoittaa oleellisesti samaa kuin seuraava Ydin-Haskellin määritelmä: f = λx 1 λx k case (x 1,..., x n ) of { (p 1,1,..., p 1,k ) g 1,1 e 1,1 g 1,m1 e 1,m1 ; ; (p n,1,..., p n,k ) g n,1 e n,1 g n,mn e n,mn }, missä x i ovat muuttujia, jotka eivät esiinny koko funktiosidonnassa. Esimerkki 14 Moduli Fact.hs voidaan kirjoittaa näinkin: module Fact where fact :: Integer -> Maybe Integer fact n n < 0 = Nothing

26 LUKU 2. OHJELMOINTI LASKENTANA n == 0 = Just 1 n > 0 = case fact (n - 1) of Just m -> Just (n * m) Nothing -> Nothing Esimerkki 15 Seuraavalla funktiolla (joka löytyy Haskellin varuskirjastosta Prelude) voidaan curry ttu funktio muuttaa curry mattomaksi funktioksi: uncurry :: (a -> b -> c) -> ((a, b) -> c) uncurry f (p, q) = f p q Nyt esimerkiksi (uncurry (+)) (2, 3) sievenee normaalimuotoon 5. Funktiosidontaa voi käyttää myös let-lausekkeissa. 2.7.4 Hahmosidonta Toinen yleistys vakionmäärittelylle on hahmonsidonta, joka on syntaktisesti samanlainen kuin yhden parametrin funktiosidonta paitsi että funktionnimeä ei ole. Hahmossa esiintyvät muuttujat tulevat näin määriteltyä vakioiksi ja niiden arvo on se, mikä hahmon ja vartioimien mukaan niille kuuluu. Jos vartioimien valitsema lauseke ei sovi (osittain tai kokonaan) hahmoon, kaikkien hahmossa mainittujen muuttujien arvoksi tulee. Itse määrittely ei varsinaisesti epäonnistu koskaan. Esimerkki 16 Hahmonsidonnoissa (x:y:[]) = 3:[] (a:b:[]) = 3:4:[] muuttujien x ja y arvoksi tulee, muuttujan a arvoksi tulee 3 ja muuttujan b arvoksi tulee 4. Myös hahmonsidontaa voi käyttää let-lausekkeessa. 2.7.5 Where-määrittelyt Paikallisia määrittelyjä voi let-lausekkeen lisäksi tehdä myös where-lisäkkeellä, joka koostuu where-avainsanasta ja sen jälkeen aaltosulkeisiin kirjoitetuista, puolipisteillä erotetuista vakionmäärittelyistä, funktiosidonnoista ja hahmosidonnoista. Where-lisäkettä voidaan käyttää kaikissa sellaisissa yhteyksissä, missä käytetään hahmoja. Where-lisäke sijoitetaan viimeisen hahmoon liittyvän lausekkeen perään, ja siinä annetut määrittelyt näkyvät kaikissa kyseiseen

2.7. KOHTI AITOA HASKELLIA 27 hahmoon liittyvissä vartioimissa ja lausekkeissa. Where-lisäkkeessä voi käyttää tavalliseen tapaan sisennystekniikkaa. Esimerkki 17 Seuraava funktio ratkaisee enintään toista astetta olevan yhtälön ax 2 + bx + c = 0 reaalijuuret, kun sille annetaan kertoimet a, b ja c: roots :: Double -> Double -> Double -> [Double] roots a b c a == 0 && b == 0 && c == 0 = error "Ääretön ratkaisujoukko" a == 0 && b == 0 = [] -- ei ratkaisuja a == 0 && b /= 0 = [c / b] e < 0 = [] -- kompleksijuuret e == 0 = [-b/d] otherwise = [(-b-r)/d, (-b+r)/d] where r = sqrt e d = 2*a e = b*b - 4*a*c Huomautus 1 Huomaa, kuinka r:n määrittely ei johda virheeseen, vaikka e olisikin negatiivinen. Tämä johtuu siitä, että tällaisessa tapauksessa r:ää ei käytetä missään, ja sen -arvoa ei siis tarjota minnekään. Huomautus 2 Edellisessä esimerkissä näkyy myös kolme varuskirjastoon Prelude kuuluvaa vakiota: error :: String α sqrt :: Double Double otherwise :: Bool Funktiovakio error ottaa parametrinaan virheilmoituksen ja palauttaa aina :n. Haskellin toteutukset osaavat tunnistaa errorin tuottaman :n, ja näyttävät argumenttimerkkijonon virheilmoituksessa. Funktiovakio sqrt laskee parametrinsa neliöjuuren. Vakio otherwise on synonyymi koostimelle True. 2.7.6 Tyyppiluokat Ydin-Haskellin käyttämä Hindleyn ja Milnerin tyyppijärjestelmä on monessa suhteessa mainio, mutta sillä on yksi huomattava haittapuoli. Mikä esimerkiksi on (+)-operaattorin tyyppi? Aiemmin totesimme, että se on Integer Integer Integer. Se ei ole kuitenkaan tyydyttävä vastaus. Yllä esimerkissä 17

28 LUKU 2. OHJELMOINTI LASKENTANA sitä käytettiin Double-tyyppisten lukujen yhteenlaskuun. Onko sen tyyppi sitten α α α, sillä se on ainoa Ydin-Haskellin tyyppi, joka sopii molempiin yhteyksiin? Ei voi niinkään ajatella, sillä silloinhan True + False olisi mahdollinen lauseke. 2 Kyse on siitä, että (+) on kuormitettu (overloaded) eli ad hoc -polymorfinen operaattori. Oikeastaan kyse on eri funktioista, joilla on vain sama nimi. Mikä funktio tarvitaan, selviää argumenttityypeistä. Haskellissa kuormitukseen käytetään tyyppiluokkia. Operaattorin (+) oikea tyyppi on α. Num α α α α. Tämä luetaan seuraavasti: operaattorin (+) tyyppi on, kun α on mikä tahansa Numluokkaan kuuluva tyyppi, α α α. Tässä siis rajoitutaan tarkastelemaan vain sellaisia tyyppejä α, joilla on joitakin luvuilta odotettavia ominaisuuksia. Koneelle kirjoitettaessa osuus α. jätetään pois. Vastaavasti operaattorin (==) tyyppi on α. Eq α α α Bool. Tässä vaaditaan, että tyypin α arvoja voi mielekkäästi verrata; esimerkiksi funktiotyypit ovat sellaisia, ettei niiden arvojen yhtäsuuruutta ole järkevää koneen testata. Tyyppiluokkaan kuuluvilta tyypeiltä vaadittavat ominaisuudet määritellään tyyppiluokan määrittelyssä: class Eq a where (==), (/=) :: a -> a -> Bool Tämä sanoo, että tyyppiluokkaan Eq kuuluvalla tyypillä a toimii operaattorit (==) ja (/=), joiden tyyppi on α. Eq α α α Bool. Operaattoreita (==) ja (/=) sanotaan tyyppiluokan Eq metodeiksi. Eräs hyvin yleinen ja hyödyllinen tyyppiluokka on Show, jolla on useita metodeja, mutta niistä tärkein on ehdottomasti show :: α. Show α α String. Funktio show ottaa minkä tahansa Show-luokkaan kuuluvan tyypin arvon ja muuttaa sen ihmisen luettavaksi merkkijonoksi 3. Esimerkki 18 Prelude> show (3:4:5:6:9:[]) "[3,4,5,6,9]" Prelude> show (Just True) "Just True" Muita tarpeellisia tyyppiluokkia ovat Ord, jonka metodeita ovat vertailuope- 2. Jättäkäämme nyt huomiotta Boolen algebra... 3. Se on vieläpä mahdollista muuttaa takaisin kyseisen tyypin arvoksi, jos tyyppi kuuluu tyyppiluokkaan Read.

2.7. KOHTI AITOA HASKELLIA 29 raattorit (< yms.), Enum, jonka metodeita ovat succ ja pred, sekä Bounded, jonka metodeita ovat vakiot minbound ja maxbound. Itse määritelty algebrallinen tietotyyppi on mahdollista saattaa jonkin tietyn tyyppiluokan jäseneksi käyttämällä deriving-lisäkettä. Tämä lisäke kirjoitetaan tyyppimäärittelyn loppuun; se alkaa avainsanalla deriving, jonka jälkeen tulee sulkeissa pilkuilla erotettuna luettelo tyyppiluokista, joihin tyypin halutaan kuuluvan. Tällöin kääntäjä generoi vakiomuotoisen määrittelyn kyseisten tyyppiluokkien metodeille. Deriving-lisäke toimii vain tyyppiluokilla Eq, Ord, Enum, Bounded, Show ja Read ja lisäksi kaikkien niiden tyyppien, jotka esiintyvät kyseisessä tyypinmäärittelyssä, tulee kuulua niihin tyyppiluokkiin, jotka mainitaan deriving-lisäkkeessä. Käytännössä kannattaa opetella liittämään deriving (Eq, Ord, Show) kaikkiin omiin tyypinmäärittelyihin, ellei ole erityistä syytä toimia toisin. Yleinen tapa määritellä jokin tyyppi (kyseessä voi olla kirjastotyyppikin) jonkin tyyppiluokan jäseneksi on käyttää instance-määrittelyä. Siinä annetaan kyseisen tyyppiluokan metodien määrittelyt. Esimerkiksi: data BinarySearchTree key elt = BTNode (BinarySearchTree key elt) (key, elt) (BinarySearchTree key elt) BTNone bttolist :: BinarySearchTree key elt -> [(key, elt)] bttolist BTNone = [] bttolist (BTNode l kep r) = bttolist l ++ [kep] ++ bttolist r instance (Eq key, Eq elt) => Eq (BinarySearchTree key elt) where a == b = (bttolist a) == (bttolist b)