2.5. YDIN-HASKELL 19 tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla kirjaimilla. Jos Γ ja ovat tyyppilausekkeita, niin Γ on tyyppilauseke. Nuoli kirjoitetaan koneella ->. Nuoli assosioi oikealle, joten A B C tarkoittaa A (B C). Jos A on tyyppikoostin ja Γ 1,..., Γ n ovat tyyppilausekkeita, niin A Γ 1... Γ n on tyyppilauseke. Tyyppilausekkeidenkin rakenne ilmaistaan sulutuksella tavanomaiseen tapaan. Tyypit voivat olla monomorfisia tai polymorfisia. Polymorfisia ne ovat silloin, kun niissä esiintyy yksikin tyyppimuuttuja, monomorfisia muulloin. Monomorfiset tyypit ovat samoja, jos niillä on sama rakenne (samat tyyppikoostimet ja nuolet samoissa paikoissa). Polymorfiset tyypit (tai monomorfinen tyyppi ja polymorfinen tyyppi) voidaan samastaa, jos kumpikin voidaan muuttaa monomorfiseksi korvaamalla siinä esiintyvät tyyppimuuttujat tyyppilausekkeilla ja jos niistä tulee näin samat. Lausekkeen tyyppi on pääteltävä ja tarkastettava ennen kuin mitään redusointia tehdään. Tämä tapahtuu seuraavien sääntöjen avulla lukemalla lauseketta sisältä ulospäin: Lausekkeen e :: t tyyppi on t. Jos e:n tyyppi ei ole t, on tapahtunut tyyppivirhe ja lauseke hylätään. Lausekkeen tyyppi on α. Lausekkeen () tyyppi on (). Muuttujan tyyppi on α ellei sitä ole kontekstissa muuksi määrätty. Operaattorin, koostimen ja koostinoperaattorin tyyppi riippuu sen määritelmästä. Jos sitä ei ole määritelty, kyseessä on tyyppivirhe ja lauseke hylätään. Literaalin tyyppi riippuu literaalista (tässä vaiheessa voidaan olettaa kokonaislukujen tyypiksi Integer, liukulukujen tyypiksi Double ja merkkijonojen tyypiksi String). Olkoon lausekkeen e tyyppi u, ja olkoon siinä esiintyvän muuttujan x tyyppi t (päätelty e:tä tutkimalla). Tällöin abstraktion λx e tyyppi on t u. Lausekkeessa f e, missä e:n tyyppi on t, tulee f:n tyypin olla t u, missä u on jokin tyyppi (jos näin ei ole, kyseessä on tyyppivirhe ja lauseke hylätään). Tällöin lausekkeen f e tyyppi on u. Lausekkeessa e e, missä e:n tyyppi on t ja e :n tyyppi on t, tulee :n tyypin olla t t u, missä u on jokin tyyppi (jos näin ei ole, kyseessä on tyyppivirhe ja lauseke hylätään). Tällöin lausekkeen f e tyyppi on e e.
20 LUKU 2. OHJELMOINTI LASKENTANA Olkoon lausekkeessa let {x 1 = e 1 ; x 2 = e 2 ;... ; x n = e n } in e 0 lausekkeiden e i tyyppi t i. Tällöin e 0 :n tyypin tulee olla t 0, kun tämä tyyppi päätellään kontekstissa, jossa x i :n tyyppi on t i. Lausekkessa case e of{p 1 q 1,1 e 1,1 q 1,k1 e 1,k1 ;...... ; p n q n,1 e n,1 q n,kn e n,kn } jokaisen p i :n tulee sopia johonkin e:n tyyppiä olevaan lausekkeeseen; jokaisen q i,j :n tyypin tulee olla Bool, kun se tarkastetaan kontekstissa, jossa p i :ssä esiintyvillä muuttujilla on ne tyypit, mitkä niille e:n tyypin mukaan kuuluu; ja jokaisen e i,j tyypin tulee olla sama (olkoon tämä tyyppi u), kun se tarkastetaan kontekstissa, jossa p i :ssä esiintyvillä muuttujilla on ne tyypit, mitkä niille e:n tyypin mukaan kuuluu. Tällöin kyseisen lausekkeen tyyppi on u. Esimerkki 11 Tyypitetään muutama lauseke (merkitään :: :llä tyypitystä): 1. λx x :: α α 2. λx 0 :: α Integer 3. 2 + 2 :: Integer 4. λx x + 0 :: Integer Integer (Tähän tulee myöhemmin muutos.) 5. ( let f = \ x -> case x of _ x == 0 -> 1 x > 0 -> x * f (x - 1) in f ) :: Integer -> Integer Algebralliset tyypit Ydin-Haskellin (melkein) kaikki tyypit määritellään algebrallisina tyyppeinä. Niille ominaista on se, että niiden määrittely muistuttaa kovasti tyyppiin kuuluvien lausekkeiden abstraktia syntaksia. Yksinkertainen algebrallinen tyyppi määritellään seuraavasti: data Bool = True False Tämä määrittelee yhden tyyppikoostimen, Bool, joka toimii sellaisenaan tämän tyypin nimenä, sekä kaksi (tieto)koostinta, True ja False. Tämän määritelmän
2.5. YDIN-HASKELL 21 jälkeen seuraavat tyypitykset pätevät: True :: Bool False :: Bool Vastaavalla tavalla voidaan määritellä muita luettelotyyppejä. Periaatteessa kaikki Ydin-Haskellin perustyypit (triviaalityyppiä () lukuunottamatta) määritellään näin luettelelmalla, joten voidaan ajatella, että on annettu seuraavanlaisia määritelmiä: data Integer = 3 2 1 0 1 2 3 data Char = A B C D Näin asiat eivät tietenkään käytännössä ole (nämä tyypit määritellään käytännössä kielen osana), mutta näin ajatteleminen on hyödyllistä. Myös säiliöitä voidaan määritellä antamalla koostimille parametrityyppejä. data Maybe α = Just α Nothing Tyyppikoostin Maybe on kiinnostava useallakin tavalla. Ensinnäkään se ei ole sinänsä tyypin nimi, vaan sille pitää antaa jokin tyyppi parametriksi (α). Maybe α on geneerinen eli (geneerisesti) polymorfinen tyyppi: se voidaan erikoistaa (specialize) sisältämään jotain toista tyyppiä olevaa dataa antamalla tyyppikoostimelle Maybe tyyppiparametri (esimerkiksi Maybe Bool. Edellä annetusta Maybe α -tyypin määritelmästä seuraavat seuraavat tyypitykset: Nothing :: Maybe α Just :: α Maybe α Tyypin Maybe Bool lausekkeet ovat seuraavat: Nothing Just True Juat False Huomaa, kuinka tyypin määrittely toimii (abstraktina) kielioppina näille lausekkeille! Maybe on hyödyllinen esimerkiksi silloin, kun funktio ei pysty kaikista argumenteistaan antamaan hyödyllistä tulosta. Esimerkiksi:
22 LUKU 2. OHJELMOINTI LASKENTANA ( 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 ) :: Integer -> Maybe Integer Kuten kieliopit yleensäkin, myös algebralliset tietotyypit voivat olla rekursiivisia: data List α = Cons α (List α) Nil Tyypin List Bool lausekkeita ovat esimerkiksi seuraavat: Nil Cons True Nil Cons False Nil Cons True (Cons True Nil) Cons True (Cons False Nil) Cons False (Cons True Nil) Cons False (Cons False Nil) Lista on tosin funktio-ohjelmoinnissa niin yleinen käsite, että sitä varten on olemassa ihan oma erityissyntaksinsa, jonka seuraava (kuvainnollinen) määritelmä on seuraava: data [α] = α : [α] [] 2.6 Tietokone laskimena Funktio-ohjelmoijan tietokone on laskin tavallista tasku- tai pöytälaskinta parempi toki, mutta silti laskin. Poikkeuksetta jokainen funktio-ohjelmointijärjestelmä sisältää interaktiivisen osan, johon koneen käyttäjä (on kyse sitten paljon puhutusta loppukäyttäjästä tai ohjelmoijasta) voi käskeä konetta laskemaan jonkin laskun. Funktio-ohjelmointi on pohjimmiltaan sitä, että ohjelmoija kirjoittaa tiedostoon funktioiden ja tyyppien määrittelyä, jolloin hän laajentaa tämän funktiolaskimen laskentakeinoja. Laskimelle voidaan syöttää mikä tahansa kelvollinen lauseke, joissa näitä määritelmiä on käytetty.
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 Fibo.hs: module Fibo where fibo :: Integer -> Maybe Integer
24 LUKU 2. OHJELMOINTI LASKENTANA fibo n = case n of _ n < 0 -> Nothing n == 0 -> Just 1 n > 0 -> case fibo (n - 1) of Just m -> Just (n * m) Nothing -> Nothing jonka jälkeen voidaankin käynnistää GHCi komennolla ghci Fibo: _ / _ \ /\ /\/ (_) / /_\// /_/ / / GHC Interactive, version 6.0.1, for Haskell 98. / /_\\/ / / http://www.haskell.org/ghc/ \ /\/ /_/\ / _ Type :? for help. Loading package base... linking... done. Compiling Fibo ( Fibo.hs, interpreted ) Ok, modules loaded: Fibo. *Fibo> fibo 5 Just 120 *Fibo>