main :: IO () main = interact (concatmap ((++"\n"). reverse). lines)
|
|
- Kaija Nieminen
- 7 vuotta sitten
- Katselukertoja:
Transkriptio
1 saa syötteenään oletussyötevirran stdin koko (jäljellä olevan) sisällön laiskasti luettavana merkkijonona antaa tulosteensa toisena merkkijonona jota se voi rytmittää rivinvaihdoilla. Esimerkiksi: module Main(main) where main :: IO () main = interact (concatmap ((++"\n"). reverse). lines) Mutkikkaammat I/O-operaatiot kuten esimerkiksi tiedostojen käsittely löytyvät vakiokirjastosta System.IO. Esimerkiksi usein halutaan oletustulosvirta stdout sellaiseksi, ettei sitä puskuroidakaan. Siihen tämä kirjasto tarjoaa kutsun hsetbuffering stdout NoBuffering jonka voi sijoittaa sellaisen ohjelman mainin alkuun. Poikkeuksista Poikkeuksia (eception) on hankala sovittaa yhteen puhtaan funktionaalisen ohjelmoinnin kanssa: Se milloin poikkeus käsitellään olettaa, että ohjelmoija tietäisi milloin jotakin tapahtuu laskennan kuluessa mutta puhtaassa funktionaalisessa ohjelmoinnissahan ohjelmoija tietää korkeintaan että se joskus tapahtuu... Haskellin vakiokirjasto System.IO.Error tarjoaakin yksinkertaisen poikkeusmekanismin erilaisten I/O-virheiden käsittelyyn koska Monadissa IO on tällainen järjestys ensin lasketaan tämä, ja sitten vasta tuo... Toteutuksen ghc mukana tulee myös sen oma toinenkin kirjasto Control.Eception jolla poikkeuksia voi nostaa muuallakin kuin Monadissa IO mutta nekin voi käsitellä vain siellä tämän järjestyksen vuoksi. Vakiokirjaston I/O-poikkeusmekanismi koostuu tyypistä IOError eli jokin I/Ovirhe sekä funktioista ioerror :: IOError -> IO a joka nostaa parametrina ilmoitetun I/O-poikkeuksen catch :: IO a -> (IOError -> IO a) -> IO a jonka ensimmäinen parametri on I/O-tyyppinen lauseke (kuten esimerkiksi jokin do-lauseke) jonka laskenta voi aiheuttaa I/O-poikkeuksia. Jos niitä ei aiheudu, niin catchin arvoksi tulee tämän laskennan antama arvo. 104
2 jälkimmäinen parametri on funktio, jota kutsutaan, jos näin käy. Se saa parametrinaan sen aiheutuneen poikkeuksen. Se voi käsitellä kyseisen poikkeustilanteen palauttamalla jonkin arvon, josta tulee tämän catchin arvo. Jos se ei osaakaan käsitellä tilannetta, niin sen pitääkin nostaa sama poikkeus uudelleen, jolloin tilanteen käsittelyvastuu siirtyykin seuraavalle ulommalle catchille. Jos mikään catch ei osaakaan sitä käsitellä, niin silloin koko mainin laskenta keskeytyy suoritusaikaiseen virheeseen. try :: IO a -> IO (Either IOError a) joka koettaa suorittaa parametrinaan saamansa laskennan. Jos sen arvo on Right niin tämä laskenta onnistui virheettä ja on sen lopputulos Left e niin silloin tämä laskenta johtikin poikkeukseen e. Otetaan sitten esimerkiksi I/O-ohjelmoinnista nelilaskin, jossa pidetään muistissa yhtä liukulukua käyttäjä voi antaa komentonaan lisää/vähennä/kerro/jaa se luvulla... tämän komentorivin mahdolliset virheet käsitellään tyhjä komentorivi lopettaa ohjelman suorituksen. module Main(main) where import System.IO import System.IO.Error main :: IO () main = do hsetbuffering stdout NoBuffering laskin 0.0 laskin :: Double -> IO () laskin muisti = do putstr $ "Muistissani on: " ++ show muisti ++ "\nkomentosi? " komentorivi <- getline case komentorivi of "" -> do putstrln "Kiitos!" return () (komento:rivi) -> case lookup komento komennot of Nothing -> do putstrln $ "Tunnen vain seuraavat laskutoimitukset: " ++ map fst komennot ++ "!" laskin muisti 105
3 Just operaattori -> do arvo <- try $ readio rivi case arvo of Right luku -> laskin $ muisti operaattori luku Left _ -> do putstrln "Tajuan vain liukulukuja!" laskin muisti komennot :: [(Char,Double->Double->Double)] komennot = [( +,(+)),( -,(-)),( *,(*)),( /,(/)) ] 7.3 Maybe monadina Aiemmin näimme, miten Maybe on Monadi: instance Monad Maybe where (Just ) >>= k = k Nothing >>= k = Nothing return = Just fail s = Nothing Tarkastellaan sitä tässä valossa, että lausekkeen p >>= k voi lukea myös suorita ensin laskenta p ja sovella sitten sen tulokseen funktiota k. Silloin näemme uuden tulkinnan Maybelle: Jos laskenta p sai jonkin tuloksen niin sovella siihen funktiota k, mutta jos se ei saanutkaan mitään tulosta, niin koko loppulaskentakaan ei saa mitään tulosta koska siitä alkaen jokaisen (>>=)-operaattorin vasen parametri on aina Nothing. Silloin siis jokainen Maybe-tyyppisen do-lauseen hahmo <- kutsuttava ohitetaan kokonaan, jos jokin sitä edeltävistä tällaisista lauseista on jo tuottanut arvon Nothing siinä kutsuttava laskenta tuottaakin sellaisen arvon, joka ei olekaan muotoa olekaan muotoa Just hahmo. Silloin siis tästä alkaen kaikki lauseet ohitetaan. Siten tässä valossa Maybe antaakin tyypin sellaiselle laskennalle joka päättyy epäonnistumiseen jos yksikin siinä kutsuttava laskenta epäonnistuu. 106
4 7.4 Lista monadina Olemme nähneet jo aiemmin, miten listat ovat Monadi: instance Monad [] where m >>= k = concat (map k m) return = [] fail s = [] Tarkastellaan sitäkin tässä valossa, että lausekkeen p >>= k voi lukea myös suorita ensin laskenta p ja sovella sitten sen tulokseen funktiota k. Nyt sen koko laskenta etenee seuraavasti: 1. Laskenta p tuottaa tuloksenaan listan m eli [ 1, 2, 3,...]. 2. Sen jokaiseen alkioon i sovelletaan (map) funktiota k. 3. Jokainen sovellus k i tuottaa tuloksenaan jonkin listan [y 1 i, y 2 i, y 3 i,...]. 4. Yhdistetään (concat) nämä listat yhdeksi listaksi k 1 ++ k 2 ++ k Sitten ympäröivässä listamonadilausekkeessa >>=k sovelletaan seuraavaa funktiota k jokaiseen tämän listan alkioon y j i, jne. Vaihdetaan näkökulmaa kokonaisista listoista niiden yksittäisiin alkioihin: Ajatellaan, että alkio i edustaisi jotakin tilaa, jossa koko laskenta voisi olla tällä hetkellä. Silloin k i tuottaa sille monta eri mahdollista seuraavaa tilaa y j i. Tässtä näkökulmasta k on siis epädeterministinen seuraava laskenta-askel. Tässä valossa listamonadi ja sen do-syntaksi kuvaavat epädeterministisen laskennan kaikkien eri vaihtoehtojen seuraamiseen. Deklaratiivisesti yksi tällainen epädeterministinen askel hahmo <- kutsuttava voidaan lukea valitse mikä tahansa hahmo on sopiva vaihtoehto kutsuttava n laskennan antamista mahdollisuuksista. Tässä luennassa <- on. 107
5 Proseduraalisesti se luetaan kokeile jokaista näistä vaihtoehdoista siinä järjestyksessä jossa kutsuttava ne tuottaa. Tässä luennassa tuo deklaratiivisuus toteutetaan syvyyshakuna (depth-first search). Se on epätäydellistä koska se juuttuu ikuiseen silmukkaan, jos sellaiseen johtava väärä vaihtoehto tulee tässä kokeilujärjestyksessä ennen oikeaa vaihtoehtoa. tehokkaasti toteutettavissa erityisesti muistinkäytöltään. Haskellin laiskat listat hoitavat sen automaattisesti ohjelmoijatta. Aiemmassa esimerkissämme säännöllisistä lausekkeista käytimme juuri tätä samaa ideaa ja näimme erikseen vaivaa sen pysähtymisen takaamiseksi. Olemme nähneet aiemmin myös listakoosteet, joiden syntaksi oli hyvin lähellä dosyntaksia: 1. Listakoosteissa sallittiin vaihe ena Bool-tyyppinen lauseke, jota do-syntaksissa ei sallita. 2. Toisaalta do-syntaksissa sallittiin lause ena mielivaltainen kyseisen Monadityypin lauseke, kun taas listakoosteissa ei sallittu vaihe ena pelkkää listalauseketta ilman edeltävää osaa hahmo <-. Tämä ei tietenkään ole sattumaa vaan suunniteltua: Listakoosteet ovatkin yleisen Monadisen do-syntaksin erikoistapaus listamonadille. Eron 1 voi ymmärtää tästä näkökulmasta: Hylkää tämä nykyinen epädeterministinen laskentahaara ellei tuo ehto q ole totta on luonteva operaatio juuri listamonadissa mutta ei muissa. Tällaisen ehdontestausvaiheen q voi ilmaista listamonadin do-syntaksissa vaikkapa () <- if q then return () else fail Jos listakoosteen ehdontestausvaihe eet q on näin muunnettu tuottaviksi vaihe iksi, niin silloin se voidaan muuntaa suoraan do-syntaksiin: listakoosteesta [lauseke osa 1, osa 2, osa 3,. osa r ] do-syntaksiin do osa 1 osa 2 osa 3. osa r return lauseke 7.5 Tilaperustaisesta ohjelmoinnista monadina Lähdimme kurssin alussa siitä, että matemaattinen funktio on mahdollisimman tiukka rajapintakäsite ohjelmointiin: sisään menee informaatiota vain parametreissa 108
6 ulos ulos tulee informaatiota vain tuloksessa. Voidaan ajatella ja ohjelmoida sellaisia funktioita, jotka näiden funktionaalisten rajapintojensa sisällä ovatkin tilaperustaisia. Silloin vain täytyy pitää huoli, että tämä tila pysyy yksityisenä tällaisen funktion jokaisella käyttökerralla: Kun tällaisen funktion arvon laskenta alkaa, niin myös sen sisäinen tila alustetaan eikä jatketakaan siitä tilasta johon edellinen laskenta päättyi. Jos tällaisen funktion kahta eri arvoa laskettaisiin rinnakkain, niin kumpikin muokkaisi omaa yksityistä tilaansa. ghc tarjoaa tähän tarkoitukseen oman kirjastonsa Control.Monad.ST joka ei siis ole Haskell-standardikirjasto edes Haskell-kielistandardin mukainen, vaan käyttää ghc:n omia laajennuksia tyyppijärjestelmään. Näillä laajennuksilla Haskellin tyypitys pystyy takaamaan, että rajapinnat säilyvät funktionaalisina, vaikka sisäisesti laskenta onkin tilallista. Jos ohjelmoija yrittää vahingossa tai tahallaan rikkoa koodissaan näitä rajapintoja, niin hän siis saakin käännösaikaisen tyyppivirheilmoituksen. Valitettavasti nämä ilmoitukset voivat olla vaikeaselkoisia... Tämän Monadin nimenä onkin STate eli tila. Otetaan esimerkiksemme seuraava ohjelmointiongelmaa: Saamme listan pareja (,y) jotka tarkoittavat että alkioiden ja y pitää kuulua samaan joukkoon. Mitkä nämä joukot ovat, kun alkiot pidetään erillään, ellei mikään tällainen pari vaadi niiden kuuluvan yhteen? Se vaatii tietorakenteen, jossa on seuraavat operaatiot: initial joka alustaa sen siten, että jokainen alkio on aluksi yksin omassa joukossaan. union joka yhdistää ne joukot, joissa annetut alkiot ja y nyt ovat. Siitä käytetään myös nimeä merge. find palauttaa sen joukon edustajan, johon alkio z nyt kuuluu. Joukon edustaja on jokin sen alkioista. Sitä käytetään koko joukon nimenä. Kun esimerkiksi kysytään ovatko alkiot ja y jo nyt samassa joukossa niin re ratkaistaan kysymällä, onko alkion joukon edustaja sama kuin alkion y joukon edustaja. Tämä tietorakenne tunnetaankin nimellä union-find tai merge-find. 109
7 Tämä vaikuttaa sellaiselta ohjelmointiongelmalta, jonka imperatiivinen ratkaisu on funktionaalista nopeampi: Koska imperatiivisessa ratkaisussa voidaan käyttää uudelleensijoitusta, niin siellä voidaan tiivistää hakupolku alkiosta sen edustajaan, jotta seuraavat kyselyt sujuvat nopeammin. Tällöin saadaan ratkaisu, joka toimii askeleessa, jossa O((m + n) α(m + n)) m on alkioiden lukumäärä n on operaatioiden union ja find lukumäärä α on erittäin hitaasti kasvava funktio. Se on aiemmin näkemämme Ackermannin funktion (joka siis kasvoi erittäin nopeasti) käänteisfunktion sukulainen. Käytännössä α(m + n) > 5 vasta kun m + n > alkeishiukkasten arvioitu lukumäärä koko maailmankaikkeudessamme... Funktionaalisessa onjelmoinnissa tämä polun tiivistäminen ei ole mahdollista, ja ainakin yksinkertaiset tietorakenneratkaisut tarvitsevat funtion α tilalla logaritmin. Imperatiivisessa ratkaisussa on oleellisesti taulukko mem, jossa alkiosta z 0 alkava polku mem[z 0 ] mem[z 1 ] mem[z 2 ] z 0 z 1 z 2 mem[zp] z p+1 johtaa alkion joukon edustajaan z p+1. Kun kysytään operaatiolla find alkion joukon edustajaa, niin samalla tämä polku tiivistetään muotoon mem[z 0 ] = mem[z 1 ] = mem[z 2 ] = = mem[z p ] = z p+1 jolloin kaikkien näiden muidenkin alkioiden z 1, z 2, z 3,...,z p 1 operaatiolla find nopeutuvat. Operaatiota union voidaan taas tehostaa seuraavasti: Pidetään yllä jokaisessa edustajassa tietoa rank joka olisi näistä mem-linkeistä koostuvan puun korkeus ilman polkujen tiivistämistä. Tämän tiedon avulla yhdistetään aina matalamman puun juuri korkeamman puun juuren alipuuksi. Nämä juuret eli edustajat antaa siis operaatio find. Monadissa ST voimme luoda tällaisen taulukon mem ja käyttää sitä: Operaatio newarray_ :: (MArray a e m, I i) => (i, i) -> m (a i e) luo taulukon tälle indeksivälille sellaisessa monadissa m jossa sellaiset sallitaan. 110
8 ST on siis yksi sellainen m. IO on toinen sellainen m. Tyyppiluokka I on ne tyypit, joita voi käyttää taulukkoindekseinä. Operaatio readarray :: (MArray a e m, I i) => a i e -> i -> m e lukee tällaisen taulukon arvon annetusta indeksistä. Operaatio writearray :: (MArray a e m, I i) => a i e -> i -> e -> m () kirjoittaa tällaisen taulukon annnettuun indeksiin annetun arvon. module UnionFind where import Control.Monad import Control.Monad.ST import Data.Array.ST import Data.Word unions :: (I t) => [(t,t)] -> [[t]] unions ys = let lo = minimum sys hi = maimum sys sys = s ++ ys (s,ys) = unzip ys in runst $ do mem <- initial lo hi sequence_ $ map (uncurry $ union mem) ys sequence_ $ map (find mem) $ range (lo,hi) memz <- getassocs mem parties <- parts lo hi sequence $ map ( \ (i,it) -> let j = case it of Rank _ data Item t = Rank Word Ranked t memz groupies <- getelems parties return $ map reverse $ filter (not. null) groupies -> i Ranked k -> k in do jt <- readarray parties j writearray parties j $ i:jt) 111
9 deriving (Show) type UF s t = STArray s t (Item t) parts :: (I t) => t -> t -> ST s (STArray s t [t]) parts lo hi = newarray (lo,hi) [] initial :: (I t) => t -> t -> ST s (UF s t) initial lo hi = do mem <- newarray_ (lo,hi) sequence_ $ map (flip (writearray mem) (Rank 0)) $ range (lo,hi) return mem find :: (I t) => UF s t -> t -> ST s t find mem = let finder i = do here <- readarray mem i case here of Rank r -> return i Ranked j -> do k <- finder j writearray mem i $ Ranked k return k in finder union :: (I t) => UF s t -> t -> t -> ST s () union mem y = do i <- find mem j <- find mem y when (i/=j) $ do Rank p <- readarray mem i Rank q <- readarray mem j if p<q then writearray mem i (Ranked j) else do writearray mem j (Ranked i) when (p==q) $ writearray mem i (Rank (p+1)) Funktionaalinen Haskell-ohjelma voi laskea tilaperustaisena määritellyn funktion tuloksen käyttämällä funktiota runst :: (forall s. ST s a) -> a joka suorittaa parametrinsa saamansa ST-laskennan alkusta loppuun saakka 112
10 palauttaa arvonaan sen lopputuloksen, jonka tyyppi on a. Siten tyyppi ST s a on tilaperustainen laskenta, jonka lopputulos on tyyppiä a. Siten ST on sellainen kaksiparametrinen tyyppikonstruktori, joka saadessaan ensimmäisen parametrinsa s tuottaa Monadin ST s. Useimpiin Monadeihin liittyy tällainen suorita -funktio, jolla funktionaalisesta koodista käsin voi ajaa Monadisen laskennan. Kääntäen, Monadissa IO ei ole tällaista funktiota runio koska sehän juuri mahdollistaisi I/O-toiminnat keskellä funktionaalista koodia. Entä tuo tyyppiparametri s? Siinä on käytetty Haskellin laajennusta forall jota emme ole käsitelleet tarkemmin emmäekä tässäkään tutustu siihen syvällisesti. Sen intuitio on että s voi olla mikä tahansa tyyppi joten kääntäen siitä ei voi olettaa yhtään mitään, eli se on erittäin abstrakti. Tässä yhteydessä tämän tyypin voi lukea hiekkalaatikko (sandbo) jonka sisällä tämä tilaperustainen laskenta toimii. Haskellin laajennettu tyyppijärjestelmä takaa, ettei tilaperustainen laskenta voi palauttaa mitään sellaista vastausta, jonka tyypissä esiintyisi tämä s. Siten se ei voi palauttaa esimerkiksi taulukkoa mem koska sen tyyppi on UF s t. Näin se turvaa tilan säilymisen yksityisenä jokaisessa funktion runst kutsussa. 7.6 Monadisen ohjelmoinnin periaatteista Jos haluaa kirjoittaa funktion f, joka käyttää monadin M palveluita (kuten monadin IO syötteenluku- ja tulostuspalveluita) ja palauttaa arvon tyyppiä τ, niin sen tulostyyppinä on M τ viimeinen lauseke do-notaatiossa on return e jossa lausekkeen e tyyppi on τ. Tälläisen funktion arvon a voi sitten laskea do-notaatiossa <- f parametrit jossa myös hahmon tyyppi on τ ja se nimeää arvon a. Funktion f parametri en tyypit kirjoitetaan funktion f tyyppiin tavalliseen tapaan. Jos jonkin parametri n tyyppi on M µ niin se on monadinen vastine korkeamman kertaluvun parametrille: Esimerkiksi funktion when :: (Monad m) => Bool -> m () -> m () 1. parametri on tavallinen totuusarvotyyppinen testi 113
11 2. parametri on laskenta, joka suoritetaan jos tämä testi on tosi muuten ei tehdä mitään, siksi tulostyyppi on (). Vakiokirjastot Prelude ja Control.Monad käyttävät tällaisia korkeamman kertaluvun monadisia parametreja määritelläkseen tällaisia uusia kontrollirakenteita kuten tämä when. Näissä kontrollirakenteissa voidaan yhdistellä esimerkiksi funktionaalista listankäsittelyä ja monadisia operaatioita: Esimerkiksi sequence_ :: Monad m => [m a] -> m () sequence_ = foldr (>>) (return ()) tekee kokonaisen listan esimerkiksi tulostuosoperaatioita (joiden tyyppi on IO ()) alusta loppuun saakka, koska sequence_ [p 1,p 2,p 3,...,p k ] on suoraan funktion foldr määritelmän nojalla p 1 >> (p 2 >> (p 3 >> (... >> (p k >> return ())...))) joka taas on sokeroimaton muoto lausekkeelle do p 1 p 2 p 3. p k return () Muutkin ohjelmointitehtävät joissa on luonteva käsite ensin tämä ja sitten tuo voidaan mallintaa monadeilla. Sellainen on esimerkiksi jäsentäminen (parsing). Aiempaa säännöllisten lausekkeiden esimerkkiämme voikin kehittää edelleen kirjastoksi monadisia jäsenninkombinaattoreita (monadic parser combinator). Silloin do 1 <- q 1 2 <- q 2 3 <- q 3. k <- q k return $ f k 114
12 on yksi kielioppisääntö ensin tulee jotakin, jonka q 1 jäsentää, sitten jotakin jota q 2 jäsentää, sitten... ja nämä i ovat niitä vastaavat semanttiset semanttiset rakenteet kuten jäsennyspuut. Ne taas on luonteva esittää algebrallisilla tietotyypeillä. Näitä voi sitten yhdistellä (combine) sopivilla operaattoreilla kuten r < > s eli käytä joko jäsennintä r tai jäsennintä s. HackageDB sisältää kirjaston tällaisen Parsec. Yleisemmin monadisen ohjelmoinnin haittavaikutus on, että ne tartuttavat helposti koodia: Jos yksi osa koodista on Monadista, niin myös siinä kutsuttavien koodinosien pitää usein olla Monadisia, ja näin isosta osasta koodia tulee monadisesti monoliittista. Tämä johtuu siitä, että Monadinen tyyppi sanoo että tämä koodi voi tarvita tämän Monadin tarjoamia palveluita. Tästä näkökulmasta esimerkiksi ST s tarkoittaa tämä koodi voi tarvita uudelleensijoituslausetta. Koodin monoliittisuus tarkoittaakin usein sitä, että Monadia pidetään varmuuden vuoksi tarjolla ja sen voisi välttää miettimällä tarkemmin mitä palveluita oikeastaan milloinkin koodissaan tarvitsee. Monadikirjastot pyrkivät usein ehkäisemään tätä monoliittisyyttä tarjoamalla monadimuuntimia (monad transformers) joilla voi lisätä jonkin sisemmän monadin päälle kuorikerrokseksi toisen monadin. Tällöin jokaisessa kerroksessa on vain siinä tarpeelliset palvelut. 115
13 8 Laiskan laskennan teoriaa ja käytäntöä Tarkastellaan lopuksi hieman sitä, mistä Haskellin laiskassa laskennassa tarkemmin sanoen onkaan kyse. Tähän mennessä olemme puhuneet siitä käsiä heilutellen tyyliin Haskell laskee vain sen tiedon jota tarvitsee tms. Yleisesti ohjelmointikielen merkitysopissa eli semantiikassa on kaksi puolta: Tarkoitesemantiikka (denotational semantics) käsittelee sitä mitä kielen ilmaukset oikein laskevat ilmaisulla tarkoitettua tulosta. Kurssilla LAP käytettiin tätä lähestymistapaa silloin kun käsiteltiin sitä formaalikieltä, joka koostui niistä syötteistä, joilla automaatti(a vastaava tietokoneohjelma) vastaisi kyllä. Automaateilla tarkoitesemantiikka onkin formaalikielten joukko-oppia. Ohjelmointikielten tarkoitesemantiikassa käytetään sellaisia matemaattisia työvälineitä kuin kategoriateoriaa ja arvoalueteoriaa (joka on luennoijan oma kömpelö käännösehdotelma termille domain theory...). Ohjelmointikielillä tarkoitesemantiikka kuvaileekin niitä funktioita joita ohjelmat esittävät. Yksi osa tarkoitesemantiikkaa on ohjelmointikielen tyyppiteoria (type theory) jolla suljetaan pois sellaisia ilmauksia, jotka kyllä ovat syntaktisesti oikein, muitta joille ei voi antaa järkevää tarkoitettua tulosta. Esimerkiksi ohjelmalla, joka laskisi yhteen totuusarvon ja merkkiijonon, ei ole järkevää tulosta, joten tyypitys sulkeen sen pois. Haskellin tyyppiteoria lähtee siitä, että tyypitys tehdään kokonaan ennen laskentaa, jolloin laskennan aikana ei enää ilmene tyyppivirheitä vaan pelkästään sellaisia suoritusvirheitä kuten vaikkapa yritys jakaa nollalla tms. Emme kuitenkaan uppoudu tällä kurssilla tarkoitesemantiikkaan koska se vaatisi matemaattisen kalustonsa esittelyä ja kehittelyä. Suoritussemantiikka (operational semantics) taas käsittelee sitä miten kielen ilmausten tarkoittamat tulokset lasketaan. Tutustumme siis laiskan laskennan suoritussemantiikan pääpiirteisiin samoin kuin sen tavallisemman eli ahkerankin laskennan, jotta näemme missä niiden ero on. Lisäksi tutustumme siihen, miten Haskell-ohjelmoija voi koodissaan ilmoittaa että tämä koodinpätkä olisikin syytä suorittaa ahkerasti ja näin parantaa sen tehokkuutta. 8.1 Lambda-laskennen perusideat Alonzo Churchin 1936 jukaisema lambda- eli λ-laskenta on vakiintunut keskeiseksi formalismiksi ohjelmointikielten teoriassa, joten tutustutaan nyt lyhyesti sen perusideoihin. Church kehitti sen tarkastellakseen sellaista laskentaa, joka etenee sieventämällä monimutkaisempia lausekkeita yksinkertaisemmiksi. 116
14 Se onkin luonteva lähestymistapa erityisesti funktionaalisissa kielissä, joissa ohjelman suoritusta voidaan ajatella sen yksinkertaistamisena kohden tulostaan. Tilaperustaisissa kielissä oletetaan sievennyksen ohella myös jokin matemaattinen abstraktio muokattavan muistin käsitteelle, jota siis λ-laskennassa ei ole. Erityisesti λ-laskenta on otettu malliksi parametrinvälitykselle ohjelmointikielissä. Määritellään λ-lausekkeet seuraavasti: Muuttujan esiintymä on λ-lauseke. Sellaisenaan se on vapaa (free) esiintymä, koska sitä ei sido (bind) mikään λ. λ-termi λ.e on λ-lauseke, jossa on muuttuja ja e λ-lauseke. Tämä λ sitoo kaikki lausekkeessa e olevat tämän muuttujan vapaat esiintymät. Näin syntyy nykyaikaisista ohjelmointikielistä tuttu muuttujanesittelyiden näkyvyyssääntö: Tämä λ esittelee muuttujan ja siihen sidotut muuttujan esiintymät lausekkeessa e viittaavat tässä esiteltyyn muuttujaan. Jos lausekkeen s sisällä esiintyy jokin toinen (λ.f) niin se esittelee samannimisen mutta eri muuttujan, ja lausekkeen f sisällä muuttujan esiintymät viittaavatkin tähän sisempään esittelyyn. Tällainen λ-termi tarkoittaa ohjelmoijan näkökulmasta sellaista yksiparametrista funktiota, jonka parametrin nimi on tämä ja runko tämä e. Siis sitä, jonka Haskellissa ilmaisemme \ -> e joka onkin yritetty valita muistuttamaan merkkiä λ niin hyvin kuin se on ASCIIlla mahdollista... Kutsutermi muotoa (p q) on λ-lauseke jossa p ja q ovat λ-lausekkeita. Se tarkoittaa tämän funktion p kutsua tällä parametrilla q. Ennen suoritusta tehty tyypintarkastus takaa, että p voidaan sieventää muotoon (ellei se jo ole valmiiksi siinä muodossa) (λ.e). Ideana on että laskenta jatkuu lausekkeella e siten, että tämän vapaat esiintymät on siinä korvattu tällä q. Sovitaan sulkujen vähentämiseksi, että tarkoittaa samaa kuin (p q 1 q 2 q 3... q k ) (...(((p q 1 ) q 2 ) q 3 )... q k ) kuten Haskellissakin eli täälläkin käytämme kuritusta moniparametrisille funktioille, jotka ovat nyt muotoa λ 1.λ 2.λ 3....λ k.f. Muita λ-lausekkeita ei λ-laskennan perusmuodossa ole. 117
15 Nämä intuitiot parametrien nimien ja funktionkutsujen merkityksestä voidaan lausua seuraavina kahtena periaatteena: α-ekvivalenssi sanoo, että funktiot ovat samat, jossa λ.e ja λy.e[/y] muuttuja y ei esiinny vapaana lausekkeessa e ja e[/y] tarkoittaa sitä λ-lauseketta, joka saadaan korvaamalla jokainen muuttujan vapaa esiintymä lausekkeessa e λ-lausekkeella y. Siis paikallisen muuttujan määrittelyssä voi vaihtaa sen nimeä. Ohjelmointikielissä ajonaikainen järjestelmä huolehtii tästä automaattisesti, se täytyy sanoa ääneen vain λ-laskennan teoriaa esitellessä. β-reduktio taas sanoo, että kutsulauseke (λ.e) f sievenee muotoon e[/f] kunhan ei esiinny vapaana λ-lausekkeessa f mutta senhän voimme aina varmistaa käyttämällä ensin α-ekvivalenssia kutsulausekkeessa. Tämä on se keskeinen sääntö kun λ-laskentaa käytetään ohjelmointikielten ja niillä määritellyn laskennan kuvailussa......koska se ilmaisee mitä funktionkutsun suorittaminen merkitsee. Sen perusintuitio on, että kutsu suoritetaan suorittamalla funktion arvon määrittelevä lauseke e siten, että sen parametrin tilalla onkin sen argumentti f. Eri laskentamallien väliset erot palautuvat oleellisesti siihen, missä järjestyksessä näitä β-reduktioita sovelletaan, kun ohjelman suoritusta ajatellaan tähän tapaan sievennysaskeleina. Kun merkitsemme yhtä tällaista β-reduktioaskelta = β niin voimme esimerkiksi sieventää (λ.λy. y ) (λz.z z) = β λy.(λz.z z) y (λz.z z) = β λy.y y (λz.z z) jossa on alleviivattu se λ johon β-reduktiota nyt sovelletaan. Merkitään vastaavasti = β kokonaista ketjua tällaisia β-reduktioaskeleita. Siis (λ.λy. y ) (λz.z z) = β λy.y y (λz.z z) (14) edellisen kaksiaskelisen ketjun perusteella. Ohjelmointikieliä tarkasteltaessa tätä λ-laskennan perusmuotoa laajennetaan (tarpeen mukaan) esimerkiksi seuraavilla lisäpiirteillä: Perustyypeillä kuten Haskellin Bool, Integer,... Perusoperaatioilla jotka käsittelevät näitä perustyyppisiä arvoja, kuten vaikkapa (+) Integereille. 118
16 Algebrallisilla tietotyypeillä sekä vastaavilla case-lausekkeilla sentyyppisten arvojen käsittelyyn. Ne voidaan liittää mukaan määrittelemällä niillekin omat β-reduktiosääntönsä. Esimerkiksi Integereiden yhteenlasku (+) voidaan ajatella määritellyn äärettömän monena sääntönä tyyliin = β 4615 jne. Nämä perustyyppien perusoperaatiot käsittelevät sentyyppisiä arvoja (value) eli intuitiivisesti loppuun saakka laskettuja välituloksia formaalimmin sellaisia λ-lausekkeita, joihin ei enää voi soveltaa β-reduktioita. Siis esimerkiksi ((λ.(456 + )) 789) = β ( ) = β = β 1368 koska ulomman yhteenlaskun voi tehdä vain arvoilla, joten sen toinen operandi pitää ensin laskea omaan arvoonsa. 8.2 Normaalimuodoista Normaalimuodon (normal form, NF) käsite tarkoittaa yleisesti lausekkeen tms. sellaista muotoa, johon ei enää voi soveltaa mitään käytettävissä olevista muunnossäännöistä. Nyt λ-laskennan tapauksessa tämä ainoa sovellettava muunnossääntö on β-reduktio, ja siten λ-lausekkeen normaalimuoto tarkoittaa sen arvoa. Tämä normaalimuoto eli arvo voi toki olla funktiotyyppinenkin, kuten yhtälössä (14). Tyypittämätön λ-laskenta sisältää toki termejä joilla ei ole normaalimuotoa onhan päättymättömiä ohjelmiakin! Esimerkiksi jos termiin (λ.( )) (λ.( )) soveltaa β-reduktiota, niin saa vastauksenaan sen itsensä eli siihen voidaan loputtomiin soveltaa β-reduktiota. Mutta jos λ-laskennan termillä on normaalimuoto, niin se on oleellisesti yksikäsitteinen: Jos samalla termillä t on kaksi eri normaalimuotoa u ja v, niin ne ovat keskenään α-ekvivalentit, eli ne eroavat toisistaan vain paikallisten muuttujiensa nimissä. Tästä seuraa että kaikki termistä t alkavat ja loppuun asti lasketut β-reduktioketjut päätyvät oleellisesti samaan tulokseen. Siis tämä normaalimuoto eli arvo eli laskennan lopputulos on hyvin määritelty käsite joka ei riipu laskujärjestyksestä. 119
17 Tämä ei kuitenkaan tarkoita, että kaikki järjestykset olisivat yhtä hyviä: jotkut järjestykset päättyvät, ja saavuttavat lopputuloksen toiset taas jatkuvat ikuisesti, eivätkä saavuta sitä koskaan. Esimerkiksi λ-lausekkessa (λy.λz.z) ((λ.( )) (λ.( ))) = β λz.z (15) jos sovelletaan β-reduktiota alleviivatussa kohdassa, mutta muuten jäädään samaan ikuiseen silmukkaan kuin edellä. Onko olemassa jokin sellainen periaate valita aina oikea kohta λ-lausekkeessa, että kun juuri siihen sovelletaan β-reduktiota, niin aina lopulta päästään normaalimuotoon, jos sellainen lausekkeella on? Kyllä on, ja jopa yksinkertainen: Kohdista aina β-reduktio ensimmäiseen eli vasemmanpuoleisimpaan mahdolliseen kohtaan lausekkeessa. Tällaista kohtaa, johon voisi soveltaa β-reduktiota, kutsutaan nimellä rede (reducible epression eli redusoituva lauseke). Tämä vuoksi tätä periaatetta valitse aina vasemmanpuoleisin rede kutsutaankin normaaliksi sievennysjärjestykseksi (normal order reduction). Muista kuitenkin seuraava: Tällainen ohjelmointikielen formaali kuvaus ilmoittaa, että sen laskennan pitää näyttää ulkopuolisen tarkkailijan silmissä samalta kuin jos se oikeasti suoritettaisiinkin juuri näin. Kielen toteutus voi sisäisesti toimia miten tahansa, kunhan se säilyttää tämän illuusion. Suorituskelpoinen Haskell-ohjelmakaan ei manipuloi enää lausekkeita, vaan se suorittaakin niistä käännettyä konekoodia, jonka suoritus etenee sitä vastaavasti. Tätä periaatetta pitää tosin täsmentää silloin kun tämä vasemmanpuoleisin lauseke on case valinta of hahmo 1 -> tuloslauseke 1 hahmo 2 -> tuloslauseke 2 hahmo 3 -> tuloslauseke 3. hahmo k -> tuloslauseke k seuraavasti: 1. Valitse samalla periaatteella valinta lausekkeesta kohta, johon sovellat β-reduktiota. 120
18 2. Jos valinta lauseke muuttui sen tuloksena sellaiseen muotoon, johon jokin näistä hahmo ista sopii, niin korvaa koko tämä case-lauseke ensimmäisen sellaisen hahmon tuloslausekkeella johon olet tehnyt vastaavat nimennät. 3. Muuten palaa takaisin kohtaan 1 sieventämään valinta a edelleen; tai jos se onkin jo normaalimuodossaan, eikä se siltikään sopinut yhteenkään näistä hahmoista, niin lopeta koko laskenta suoritusaikaiseen virheeseen. Nyt voimme lausua ensimmäisen eron ohjelmointikielten erilaisten suoritusmekanismien välillä: Ahkera suoritusjärjestys (call-by-value/cbv, strict, eager) määritellään seuraavasti: Suorita kutsulauseke ((λ.e) f) siten, että 1. ensin sievennät sen parametrilausekkeen f arvoonsa a 2. sitten jatkat sieventämällä lauseketta e[/a]. Se ei siis noudatakaan normaalia sievennysjärjestystä......jolloin se voikin jäädä ikuiseen silmukkaan, kuten yhtälössä (15)... mutta silti useimmat ohjelmointikielet määrittelevät aliohjelmakutsunsa juuri näin... koska silloin ohjelmoija tietää suoritusjärjestyksestä ainakin sen, milloin arvon a laskenta on valmis... joten hän voi käyttää tilaperustaisia piirteitä. Laiska suoritusjärjestys (call-by-need, lazy, non-strict) taas noudattaakin normaalia sievennysjärjestystä. Silloin lauseke e pystyykin valitsemaan itse, mitä osia lausekkeen f arvosta a sen tarvitseekaan oikeasti laskea omaa vastaustaan varten eli silloin tietoriippuvuudet määräävät laskentajärjestyksen. Edellisen mukaan siis laiska suoritusjärjestys pystyy saavuttamaan lopputuloksen aina kun sellainen suinkin on olemassa eli se keksii itse aina oikean sievennystavan...vaikkei siinä ollutkaan paljon keksimistä: Riittää valita aina vasemmanpuoleisin rede. Huomaa kuitenkin, että case-lausekkeissa on kiinteä kokeilujärjestys... jos tämä hahmo sopii mutta mikään sitä edeltävistä ei sopinut. Siten myös laiskassa suoritusjärjestyksessä infinite = case of y -> infinite y [] -> 0 on ikuinen silmukka: sekään ei osaa itse valita jälkimmäistä haaraa, koska edellinenhän sopii aina. Haskell käyttää siis tätä laiskaa suoritusjärjestystä. Normaalilla suoritusjärjestyksellä on myös se etu, että koodia voi manipuloida algebrallisesti miettimättä mitä lisärajoituksia ohjelmointikielen laskentajärjestys aiheuttaa. Toisin sanoen, voi käyttää vapaasti lauseketta, joka määrittelee halutun tuloksen, miettimättä erikseen osaako tämä ohjelmointikieli laskea sen tuloksen kyllä se osaa! 121
19 Haskell ei myöskään laske vastauksiaan siihen oikeaan normaalimuotoon saakka, jossa kaikki mahdolliset β-reduktiot olisi tehty. Sen sijaan Haskell tyytyykin ns. heikkoon päänormaalimuotoon (weak head normal form, WHNF) jossa lopetetaankin heti, kun normaali sievennysjärjestys ehdottaisi soveltamaan β-reduktiota seuraavaksi sellaiseen kohtaan, joka on... jonkin λ:n sisällä, eli kun lauseke on sieventynyt muotoon λ.((λy.e) f) eli funktioksi, jonka laskenta etenee vasta kun se saa argumentin, koska vasta tämän argumenttinsa saatuaan tämä funktio tietäisi, mitä osia se siitä oikein tarvitseekaan, ja Haskell-laskennanhan määräävät tietoriippuvuudet. jonkin perusoperaation kutsu, mutta sillä on liian vähän argumentteja, joten sitä ei voi suorittaa tätä pidemmälle, kuten vaikkapa sektiota (5 +). jonkin datatyyppinä määritellyn algebrallisen tietotyypin konstruktori, jolla myös on liian vähän argumentteja, joten sitäkään ei voi suorittaa tätä pidemmälle. Muuten jatketaan tekemällä se ehdotettu β-reduktio, jne. Kun sievennettävä Haskell-lauseke h ei olekaan funktiotyyppiä vaan perustyyppiä kuten Integer niin silloin sen WHNF on vastaava arvo. algebrallista tyyppiä niin silloin sen WHNF saadaan sieventämällä sitä kunnes selviää, mikä tämän tyypin konstruktoreista on lausekkeen h alussa. Esimerkiksi jos h :: Maybe Integer niin silloin sen WHNF saadaan sieventämällä, kunnes selviää onko se Nothing vaiko jotakin muotoa Just n. Jos jälkimmäistä, niin sen kentän sisältö n on yhä sieventämättä ellei sisällön n sieventäminen ollut välttämätöntä koko lausekkeen h konstruktorin selvittämiseksi. Vastaavasti case-lausekkeen hahmo t ovatkin muotoa Konstruktori... koska ne kysyvät onko lausekkeen h Konstruktorina tämä? Myös tulkissa ghci käyttäjän kirjoittamaa lauseketta h sievennetään vain sen heikkoon päänormaalimuotoon mutta jos tämä h onkin sellaista tyyppiä, joka kuuluukin tyyppiluokkaan Show, niin silloin ghci alkaakin vielä muuntamaan sieventämäänsä tulosta lausekkeelle h vielä merkkijonoksi, jonka se tulostaa käyttäjälle. Tämä muunnos merkkijonoksi taas aiheuttaa sen, että lausekkeen h tulos sievennetäänkin loppuun saakka koska tämän tulostettavan merkkijonon WHNF puolestaan tarvitsee kaikki merkit siitä merkkijonosta, jonka tuottaa kutsu show h. 8.3 Ahkeruuden osoittaminen Haskell-ohjelmassa Haskell tarjoaa primitiivin, jolla ohjelmoija voi ilmoittaa haluavansa, että jokin ohjelman osa suoritetaankin ahkerasti. Kaikkein matalimman abstraktiotason primitiivi on seq :: a -> t -> t 122
20 jonka tulkinta on sievennä ensimmäinen argumentti tyyppiä a heikkoon päänormaalimuotoonsa w ja palauta tuloksena toinen argumentti tyyppiä b sellaisenaan. Siis Haskell suorittaa ensimmäisen argumenttinsa sivuvaikutuksenaan vaikkei se ole tarpeen tuloksen tekemiseksi. Onneksi Haskell tarjoaa myös tämän primitiivin päälle rakennettuja korkeamman abstraktiotason tapoja ilmaista ahkeruutta. Yksi tällainen tapa ovat Preluden operaattorit ($), ($!) :: (a -> b) -> a -> b f $ = f f $! = seq f joista $ onkin jo tuttu sehän vain kutsuu funktiota f parametrilla normaalisti eli laiskasti $! siis 1. ensin normalisoi parametrin heikkoon päänormaalimuotoonsa w 2. sitten kutsuu funktiota f tällä w. Näillä operaattoreilla on sitten lisätty datatyyppimäärittelyihin mahdollisuus sanoa, että käsittelekin tämä kenttä ahkerasti : Kentän tyypin voi aloittaa huutomerkillä!. Silloin lausekkeessa Konstruktori kenttään 1 kenttään 2 kenttään 3... kenttään k kunkin kentän eteen tulee operaattori ($!) jos sen tyyppi alkaa! ja muuten ($). Silloin esimerkiksi seuraava datatyyppimäärittely on sellain hakupuutyyppi, joka rakennetaankin ahkerasti eikä laiskasti: Funktiossa lisaa konstruktori Solmu normalisoi huutomerkkiensä! vuoksi rekursiokutsunsa lisaa y vasen (ja oikea) tuloksen. Siten se normalisoi jokaisen alipuunsa juuren. Siten rekursio tuottaa puun, jonka jokainen alipuu on kokonaan normalisoitu. data Puu k t = Tyhja Solmu!(Puu k t) k t!(puu k t) deriving (Show) lisaa :: (Ord k) => k -> t -> Puu k t -> Puu k t lisaa y Tyhja = Solmu Tyhja y Tyhja lisaa y haara@(solmu vasen avain tieto oikea) < avain = Solmu (lisaa y vasen) avain tieto oikea > avain = Solmu vasen avain tieto (lisaa y oikea) otherwise = haara 123
21 Nämä huutomerkit! datatyyppimäärittelyissä ovat jo nyt Haskell-standardissa. Seuraavaan Haskell-standardiin on ehdotettu tällaisten huutomerkkien! sallimista myös hahmoissa (bang patterns) ja ghc toteuttaakin sen yhtenä laajennuksistaan. Silloin voisi kirjoittaa hahmon muotoa!muuttuja tai!_ tarkoittamaan vaivatta, että normalisoi tämä osa heikkoon päänormaalimuotoonsa, vaikka se ei olekaan tarpeen. Haskell-kääntäjän kuten ghc koodin optimoinnin yhtenä työvaiheena on ahkeruusanalyysi (strictness analysis). Se lähtee liikkeelle näistä ohjelmoijan ilmoittamista huutomerkeistä! ja analysoi niiden perusteella, mitkä kaikki muutkin ohjelman osat pitää niiden seurauksena suorittaa ahkerasti Se siis sirottelee ohjelmaan omin päin lisää seq-kutsuja tehostaakseen sitä. Ahkerasta ohjelmakoodista voi nimittäin tuottaa tehokkaampaa konekoodia kuin laiskasta, koska siitä voi jättää pois laiskuuden vaatiman lisäkirjanpidon. Tämä on yksi sellainen optimointivaihe, jota ahkeran kielen kääntäjässä ei ole. Tätä huutomerkkiä! ei kuitenkaan voi käyttää tyyppimäärittelyssä newtype Nimi = Konstruktori Tyyppi vaan tämän uudennimi sen tyypin ahkeruuden tai laiskuuden määrää se olemassa oleva Tyyppi jonka se sisältää. Siten newtype tekee uudennimi sen tyypin joka käyttäytyy laskennassa samoin kuin tämä Tyyppi. Tämä pätee myös silloin kun tällä uudennimisellä tyypillä on tyyppiparametreja. Vastaavanlainen datatyypinmäärittely tekisikin uuden tyypin, joka olisikin laiska, ellei huutomerkillä! muuta ilmaista. Siis sen käyttäytyminen laskennassa määräytyykin datatyyppimäärittelyn perusteella eikä noudattamalla Tyyppiä. Tämä onkin newtype- ja datatyyppimäärittelyjen perustava ero. Tarkastellaan lopuksi esimerkkinä ahkeruuden tehostavasta vaikutuksesta lukulistan yhteenlaskua. Koska (+) on assosiatiivinen, niin voimme valita vapaasti kumpaa foldia käytämme. Valintamme on foldl koska sehän on intuitiivisesti pelkkä silmukka kun taas foldr olisi aitoa rekursiota : foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (:s) = foldl f (f z ) s foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z foldr f z (:s) = f (foldr f z s) 124
22 Lasketaan sitten käsin esimerkkiä normaalissa järjestyksessä: foldl (+) 0 [ 1, 2, 3,..., n ] = βfoldl (+) (0 + 1 ) [ 2, 3,..., n ] = βfoldl (+) ((0 + 1 ) + 2 ) [ 3,..., n ] (16) = βfoldl (+) ((...(((0 + 1 ) + 2 ) + 3 )...) + n ) [] = β((...(((0 + 1 ) + 2 ) + 3 )...) + n ). Eihän tämä laskenta ollutkaan lainkaan tehokasta sehän teki vain vasemmalle vinon kopion oikealle vinosta syötelistastaan! Miksi? Koska näitä välisummia ei tarvittu laskennan kuluessa! se + 2 ne + 3. Tämä summalauseke sievennetään arvoonsa vasta kun sitä kysytään ja silloin tehdään oleellisesti saman verran työtä kuin foldl-sievennyksissä. Kirjasto Data.List sisältää onneksi myös funktion foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (:s) = let z = z f in seq z $ foldl f z s joka siis sanoo, että sievennä lisäksi joka askeleessa tämänhetkinen välitulos z heikkoon päänormaalimuotoonsa. Koska nyt tämän välituloksen tyyppi on numero, niin sen sievennys laskee kyseisen välituloksen arvon. Sen laskenta eteneekin siis seuraavasti: jossa foldl (+) 0 [ 1, 2, 3,..., n ] = βfoldl (+) (0 + 1 ) [ 2, 3,..., n ] = β foldl (+) z 1 [ 2, 3,..., n ] = βfoldl (+) (z ) [ 3,..., n ] = β foldl (+) z 2 [ 3,..., n ] = βfoldl (+) z n [] = βz n z i = i on tämän välisumman arvo joka on siis nyt yksi luku. Nyt laskentamme etenee kuten haluamme: tehokkaana silmukkana. 125
23 8.4 Muistin käyttö ja välitulosten jakaminen Edellinen foldl -esimerkki osoitti myös, että laiskan laskennen muistinkäyttökin saattaa tuottaa yllätyksiä: foldl synnyttikin muistiin kokonaisen sieventämättömän summalausekkeen. Tutustutaan siksi lyhyesti myös siihen, miten laiska laskenta käsittelee muistia. Otetaan yksinkertaiseksi esimerkiksemme kahdennusfunktio double :: Int -> Int double = + ja lausekkeen double (double (double 1)) arvon laskenta. Normaalissa järjestyksessä β-reduktio kohdistuu aina vasemmanpuoleisimpaan mahdolliseen paikkaa eli uloimpaan doubleen: double (double (double 1)) = β (double (double 1)) +(double (double 1)). Mutta laskeeko Haskell tämän välituloslausekkeen (double (double 1))... kerran koska se on paikallisen muuttujan arvona vai kahdesti koska tämä esiintyykin kahdesti doublessa? Vain kerran koska tämän esiintymät jakavat saman kopion välituloslausekkeesta muistissa: + double (double 1) Itse asiassa laiskat ohjelmointikielet käyttävätkin toteutuksissaan verkkoreduktiota (graph reduction) joka on λ-laskentaa verkoilla eli niille määriteltyä β-reduktiota, jotta voidaan esittää rakenteiden jako muistissa, vaikka tekstimuotoisille lausekkeille ilman rakenteiden jakoa se onkin alun perin määritelty. Nyt seuraavaa β-reduktiota sovelletaan normaalin järjestyksen mukaan tämän verkon juureen. Siellä on (+) jolla on molemmat operandit, joten aletaan sieventää sitä heikkoon päänormaalimuotoonsa. 126
24 Mutta sen sievennys tarvitseekin molempien operandiensa arvot, joten ne pitää sieventää ensin omiin päänormaalimuotoihinsa. Sievennetään ensin vasemmanpuoleista operandia. Sieveneekö myös oikea operandi samalla nehän jakavat saman lausekkeen eli aliverkon? Kyllä sievenee, koska vasemman operandin sievennys kirjoittaa tuloksensa siihen samaan muistipaikkaan, jossa lauseke oli ja jonka myös oikea operandi jakaa: + + double 1 Siis vaikka Haskell-kielessä ei olekaan uudelleensijoituslausetta (Monadien ST ja IO ulkopuolella) niin sen ajonaikainen järjestelmä (run-time system, RTS) perustuu uudelleensijoitukseen konekooditasolla. Jatketaan samaan tapaan:
25 Nyt alimman solmun (+) molemmat operandit ovat lukuja 1 eli heikossa päänormaalimuodossaan, joten sekin voidaan vihdoin sieventää omaan heikkoon päänormaalimuotoonsa ja jälleen kirjoittaa tämä tulos siihen muistipaikkaan jossa solmu oli: Nyt voidaan laskea seuraava solmu (+) vastaavasti:
26 Ja lopuksi vielä juurisolmu (+): Kuviin on katkoviivoilla merkitty se, milloin muistin eri osia ei enää tarvita. Silloin ne palautuvat toteutuksen roskankerääjälle (garbage collector) uudelleenkäytettäviksi. Näin Haskell sieventää jokaisen lausekkeen vain kerran. Jos samaan lausekkeeseen on useita viittauksia, niin kaikki muutkin viittaukset hyötyvät yhden kautta tehdystä sievennystyöstä. Koska double-esimerkissämme oli solmun (+) sekä vasempana että oikeana operandina, ja siten niillä yhteinen lauseke, niin vasemman operandin sievennys tuotti samalla myös oikealle operandille arvon. Jos joutuu tarkastelemaan Haskell-ohjelman ajan- tai tilankäytön yksityiskohtia, niin silloin joutuu miettimään näitä jaettuja esityksiä muistissa. Toisaalta silloin Haskell joutuu käyttämään muistia kirjanpitoon siitä, mitkä rakenteet ovat vielä sieventämättä ja mitkä ovat jo valmiit. Lisäksi jokaiseen muistiviittaukseen liittyy lisätyönä kysymys onko sen kohde tämän kirjanpidon mukaan vielä kesken tai jo valmis? Tämän kirjanpidon sekä monimuotoisuuden vuoksi Haskell ylläpitää tietoalkioitaan muistissa laatikoituina (boed). Tällaisessa laatikossa on tilaa sekä kirjanpidolle että tulokselle. Tämä laatikointi on saman kaltaista kuin Javan kaksi kokonaislukulajia: raaka int joka ei ole olio vs. Integer joka on olio joka on laatikko jossa on int sisällä. Kääntäjälle ghci voi antaa valitsimen unbo-strict-fields jolloin se eliminoi tämän laatikoinnin niistä datatyyppien kentistä, joiden alussa on huutomerkki! niitä ei tarvitse laatikoida, koska nehän lasketaan ahkerasti. 129
TIEA341 Funktio-ohjelmointi 1, kevät 2008
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
Lisätiedot6 Algebralliset tietotyypit
Nyt voitaisiin kirjoittaa instance Functor Set where type Inv Set e = (Ord e) fmap = map jossa metodin tyyppi onkin nyt fmap :: (Ord a,ord b) => (a -> b) -> Set a -> Set b joka onkin nyt samaa tyyppiä
Lisätiedottään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla
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
LisätiedotAlgebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005
Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005 Tällä luennolla Algebralliset tietotyypit Hahmonsovitus (pattern matching) Primitiivirekursio Esimerkkinä binäärinen hakupuu Muistattehan...
LisätiedotTIEA341 Funktio-ohjelmointi 1, kevät 2008
TIEA341 Funktio-ohjelmointi 1, kevät 2008 Luento 14: Monadit Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 21. tammikuuta 2008 Tyyppien tyypit eli luonteet engl. kind tyyppinimet, kuten
Lisätiedot5.2.5 Konstruktoriluokat Edellisessä esimerkissä määrittelimme oman tyyppiluokan Isqrt jonka jäsenet olivat tyyppejä (kuten Int, Integer, Word,...).
5.2.5 Konstruktoriluokat Edellisessä esimerkissä määrittelimme oman tyyppiluokan Isqrt jonka jäsenet olivat tyyppejä (kuten Int, Integer, Word,...). Voimme määritellä tyyppiluokkia myös tyyppikonstruktoreille
LisätiedotTämän vuoksi kannattaa ottaa käytännöksi aina kirjoittaa uuden funktion tyyppi näkyviin, ennen kuin alkaa sen määritemää kirjoittamaan.
3.1. LISTAT 35 destaan pisteittäisesti: init :: [α] [α] init (x : []) = [] init (x : xs) = x : init xs Varuskirjastoon kuuluu myös funktiot take ja drop, jotka ottavat tai tiputtavat pois, funktiosta riippuen,
LisätiedotMonadeja siellä, monadeja täällä... monadeja kaikkialla? TIES341 Funktio ohjelmointi 2 Kevät 2006
Monadeja siellä, monadeja täällä... monadeja kaikkialla? TIES341 Funktio ohjelmointi 2 Kevät 2006 Materiaalia Paras verkkomatsku: http://www.nomaware.com/monads/html/ Komentoanalogiasta vielä Monadityypin
LisätiedotTIEA341 Funktio-ohjelmointi 1, kevät 2008
TIEA341 Funktio-ohjelmointi 1, kevät 2008 Luento 9 Kombinaattoreista Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 21. tammikuuta 2008 Currying Haskell-funktio ottaa aina vain yhden
LisätiedotTIEA341 Funktio-ohjelmointi 1, kevät 2008
TIEA341 Funktio-ohjelmointi 1, kevät 2008 Luento 4 Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 17. tammikuuta 2008 Modulin viimeistelyä module Shape ( Shape ( Rectangle, E l l i p
Lisätiedot5.5 Jäsenninkombinaattoreista
5.5. JÄSENNINKOMBINAATTOREISTA 67 type Env α = FiniteMap String α data EnvT m α = MkE (Env Integer m (Env Integer, α)) instance Transformer EnvT where promote mp = MkE $ λenv mp λr return $(env, r) instance
LisätiedotTIEA341 Funktio-ohjelmointi 1, kevät 2008
TIEA341 Funktio-ohjelmointi 1, kevät 2008 Luento 5 Ympärysmitta. Puut. Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 21. tammikuuta 2008 CASE: YMPÄRYSMITTA Lasketaan kuvioiden ympärysmittoja
LisätiedotLaajennetaan vielä Ydin-Haskellia ymmärtämään vakiomäärittelyt. Määrittely on muotoa
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:
LisätiedotLisää laskentoa. TIEA341 Funktio ohjelmointi 1 Syksy 2005
Lisää laskentoa TIEA341 Funktio ohjelmointi 1 Syksy 2005 Kertausta: Laajennettu aritmetiikka Lasketaan rationaaliluvuilla vakiot yhteen, vähennys, kerto ja jakolasku Laajennetaan sitä määrittelyillä: vakio
LisätiedotTyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet. TIES341 Funktio-ohjelmointi 2 Kevät 2006
Tyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet TIES341 Funktio-ohjelmointi 2 Kevät 2006 Alkuperäislähteitä Philip Wadler & Stephen Blott: How to make ad-hoc polymorphism less ad-hoc,
LisätiedotUusi näkökulma. TIEA341 Funktio ohjelmointi 1 Syksy 2005
Uusi näkökulma TIEA341 Funktio ohjelmointi 1 Syksy 2005 Aloitetaan alusta... Otetaan uusi näkökulma Haskelliin ohjelmointi laskentana kertausta toisaalta, uusia käsitteitä toisaalta helpottanee sitten
LisätiedotLuku 3. Listankäsittelyä. 3.1 Listat
Luku 3 Listankäsittelyä Funktio-ohjelmoinnin tärkein yksittäinen tietorakenne on lista. Listankäsittely on paitsi käytännöllisesti oleellinen aihe, se myös valaisee funktio-ohjelmoinnin ideaa. 3.1 Listat
LisätiedotTyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005
Tyyppejä ja vähän muutakin TIEA341 Funktio ohjelmointi 1 Syksy 2005 Viime luennolla... Haskellin alkeita pääasiassa Hello World!... ja muita tutunoloisia ohjelmia Haskellilla Haskellin voima on kuitenkin
LisätiedotDemo 7 ( ) Antti-Juhani Kaijanaho. 9. joulukuuta 2005
Demo 7 (14.12.2005) Antti-Juhani Kaijanaho 9. joulukuuta 2005 Liitteenä muutama esimerkki Ydin-Haskell-laskuista. Seuraavassa on enemmän kuin 12 nimellistä tehtävää; ylimääräiset ovat bonustehtäviä, joilla
LisätiedotSe mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.
Tehtävä. Tämä tehtävä on aineistotehtävä, jossa esitetään ensin tehtävän teoria. Sen jälkeen esitetään neljä kysymystä, joissa tätä teoriaa pitää soveltaa. Mitään aikaisempaa tehtävän aihepiirin tuntemusta
Lisätiedot815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 6 Vastaukset Harjoituksen aiheena on funktionaalinen ohjelmointi Scheme- ja Haskell-kielillä. Voit suorittaa ohjelmat osoitteessa https://ideone.com/
LisätiedotELM GROUP 04. Teemu Laakso Henrik Talarmo
ELM GROUP 04 Teemu Laakso Henrik Talarmo 23. marraskuuta 2017 Sisältö 1 Johdanto 1 2 Ominaisuuksia 2 2.1 Muuttujat ja tietorakenteet...................... 2 2.2 Funktiot................................
LisätiedotOhjelmoinnin perusteet Y Python
Ohjelmoinnin perusteet Y Python T-106.1208 2.3.2009 T-106.1208 Ohjelmoinnin perusteet Y 2.3.2009 1 / 28 Puhelinluettelo, koodi def lue_puhelinnumerot(): print "Anna lisattavat nimet ja numerot." print
Lisätiedot2.4 Normaalimuoto, pohja ja laskentajärjestys 2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13
2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13 Toisinaan voi olla syytä kirjoittaa α- tai β-kirjain yhtäsuuruusmerkin yläpuolelle kertomaan, mitä muunnosta käytetään. Esimerkki 4 1. (λx.x)y β = y 2.
LisätiedotAlkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)
Alkuarvot ja tyyppimuunnokset (1/5) Aiemmin olemme jo antaneet muuttujille alkuarvoja, esimerkiksi: int luku = 123; Alkuarvon on oltava muuttujan tietotyypin mukainen, esimerkiksi int-muuttujilla kokonaisluku,
LisätiedotTIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 30. marraskuuta 2015
TIEA241 Automaatit ja kieliopit, syksy 2015 Antti-Juhani Kaijanaho TIETOTEKNIIKAN LAITOS 30. marraskuuta 2015 Sisällys t Väitöstilaisuus 4.12.2015 kello 12 vanhassa juhlasalissa S212 saa tulla 2 demoruksia
LisätiedotLisää pysähtymisaiheisia ongelmia
Lisää pysähtymisaiheisia ongelmia Lause: Pysähtymättömyysongelma H missä H = { w111x w validi koodi, M w ei pysähdy syötteellä x } ei ole rekursiivisesti lueteltava. Todistus: Pysähtymisongelman komplementti
LisätiedotTietorakenteet ja algoritmit - syksy 2015 1
Tietorakenteet ja algoritmit - syksy 2015 1 Tietorakenteet ja algoritmit - syksy 2015 2 Tietorakenteet ja algoritmit Johdanto Ari Korhonen Tietorakenteet ja algoritmit - syksy 2015 1. JOHDANTO 1.1 Määritelmiä
LisätiedotAlgoritmit 2. Luento 2 Ke Timo Männikkö
Algoritmit 2 Luento 2 Ke 15.3.2017 Timo Männikkö Luento 2 Tietorakenteet Lineaarinen lista, binääripuu Prioriteettijono Kekorakenne Keko-operaatiot Keon toteutus taulukolla Algoritmit 2 Kevät 2017 Luento
Lisätiedot815338A Ohjelmointikielten periaatteet 2014-2015. Harjoitus 7 Vastaukset
815338A Ohjelmointikielten periaatteet 2014-2015. Harjoitus 7 Vastaukset Harjoituksen aiheena on funktionaalinen ohjelmointi Scheme- ja Haskell-kielillä. Voit suorittaa ohjelmat osoitteessa https://ideone.com/
LisätiedotAbstraktit tietotyypit. TIEA341 Funktio ohjelmointi 1 Syksy 2005
Abstraktit tietotyypit TIEA341 Funktio ohjelmointi 1 Syksy 2005 Data abstraktio Abstraktio on ohjelmoinnin tärkein väline Data abstraktio abstrahoi dataa Abstrakti tietotyyppi Koostuu kolmesta asiasta:
LisätiedotTIEA341 Funktio-ohjelmointi 1, kevät 2008
TIEA341 Funktio-ohjelmointi 1, kevät 2008 Luento 10 Todistamisesta Antti-Juhani Kaijanaho Jyväskylän yliopisto Tietotekniikan laitos 21. tammikuuta 2008 Samuuden todistaminen usein onnistuu ihan laskemalla
Lisätiedot3. Muuttujat ja operaatiot 3.1
3. Muuttujat ja operaatiot 3.1 Sisällys Imperatiivinen laskenta. Muuttujat. Nimi ja arvo. Muuttujan nimeäminen. Muuttujan tyyppi. Operaattorit. Operandit. Arvon sijoitus muuttujaan. Aritmeettiset operaattorit.
LisätiedotIDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit
IDL - proseduurit 25. huhtikuuta 2017 Viimeksi käsiteltiin IDL:n interaktiivista käyttöä, mutta tämä on hyvin kömpelöä monimutkaisempia asioita tehtäessä. IDL:llä on mahdollista tehdä ns. proseduuri-tiedostoja,
LisätiedotVasen johto S AB ab ab esittää jäsennyspuun kasvattamista vasemmalta alkaen:
Vasen johto S AB ab ab esittää jäsennyspuun kasvattamista vasemmalta alkaen: S A S B Samaan jäsennyspuuhun päästään myös johdolla S AB Ab ab: S A S B Yhteen jäsennyspuuhun liittyy aina tasan yksi vasen
LisätiedotAlgoritmit 1. Luento 3 Ti Timo Männikkö
Algoritmit 1 Luento 3 Ti 17.1.2017 Timo Männikkö Luento 3 Algoritmin analysointi Rekursio Lomituslajittelu Aikavaativuus Tietorakenteet Pino Algoritmit 1 Kevät 2017 Luento 3 Ti 17.1.2017 2/27 Algoritmien
LisätiedotYdin-Haskell Tiivismoniste
Ydin-Haskell Tiivismoniste Antti-Juhani Kaijanaho 8. joulukuuta 2005 1 Abstrakti syntaksi Päätesymbolit: Muuttujat a, b, c,..., x, y, z,... Tyyppimuuttujat α, β, γ,... Koostimet (data- ja tyyppi-) C, D,...,
LisätiedotATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014
18. syyskuuta 2014 IDL - proseduurit Viimeksi käsiteltiin IDL:n interaktiivista käyttöä, mutta tämä on hyvin kömpelöä monimutkaisempia asioita tehtäessä. IDL:llä on mahdollista tehdä ns. proseduuri-tiedostoja,
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 2: SICP kohdat 22.2.3 Riku Saikkonen 2. 11. 2010 Sisältö 1 Linkitetyt listat 2 Listaoperaatioita 3 Listarakenteet 4 Gambit-C:n Scheme-debuggeri Linkitetyt
LisätiedotLuku 4. Tietorakenteet funktio-ohjelmoinnissa. 4.1 Äärelliset kuvaukset
Luku 4 Tietorakenteet funktio-ohjelmoinnissa Koska funktio-ohjelmoinnissa ei käytetä tuhoavaa päivitystä (sijoituslausetta ja sen johdannaisia), eivät läheskään kaikki valtavirtaohjelmoinnista tutut tietorakenteet
LisätiedotSisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.
3. Muuttujat ja operaatiot Sisällys Imperatiivinen laskenta. Muuttujat. Nimi ja arvo. Muuttujan nimeäminen. Muuttujan tyyppi.. Operandit. Arvon sijoitus muuttujaan. Aritmeettiset operaattorit. Arvojen
LisätiedotLuku 5. Monadit. 5.1 Siirrännän ongelma
Luku 5 Monadit There are lots of books about functional programming in Haskell. They tend to concentrate on the beautiful core of functional programming: higher order functions, algebraic data types, polymorphic
LisätiedotTIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 26. kesäkuuta 2013
ja ja TIEA241 Automaatit ja kieliopit, kesä 2012 Antti-Juhani Kaijanaho TIETOTEKNIIKAN LAITOS 26. kesäkuuta 2013 Sisällys ja ja on yksi vanhimmista tavoista yrittää mallittaa mekaanista laskentaa. Kurt
LisätiedotTietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen
Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari 1 1. JOHDANTO 1.1 Määritelmiä 1.2 Tietorakenteen ja algoritmin valinta 1.3 Algoritmit ja tiedon määrä 1.4 Tietorakenteet ja toiminnot 1.5 Esimerkki:
LisätiedotAlgoritmit 2. Luento 2 To Timo Männikkö
Algoritmit 2 Luento 2 To 14.3.2019 Timo Männikkö Luento 2 Tietorakenteet Lineaarinen lista, binääripuu Prioriteettijono Kekorakenne Keko-operaatiot Keon toteutus taulukolla Algoritmit 2 Kevät 2019 Luento
LisätiedotLuku 2. Ohjelmointi laskentana. 2.1 Laskento
Luku 2 Ohjelmointi laskentana Funktio-ohjelmoinnin, olio-ohjelmoinnin ja käskyohjelmoinnin ero on löydettävissä niiden pohjalla olevista laskennan mallista. Automaattisen tietojenkäsittelyn yksi historiallinen
LisätiedotAlgoritmit 2. Luento 7 Ti Timo Männikkö
Algoritmit 2 Luento 7 Ti 4.4.2017 Timo Männikkö Luento 7 Joukot Joukko-operaatioita Joukkojen esitystapoja Alkiovieraat osajoukot Toteutus puurakenteena Algoritmit 2 Kevät 2017 Luento 7 Ti 4.4.2017 2/26
Lisätiedot8.5 Takarekursiosta. Sanoimme luvun 8.3 foldl -esimerkissämme että
85 Takarekursiosta Sanoimme luvun 83 foldl -esimerkissämme että foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs olisi pelkkä silmukka Tämä johtuu siitä, että
Lisätiedot14.1 Rekursio tyypitetyssä lambda-kielessä
Luku 14 Rekursiiviset tyypit Edellisessä luvussa esitetyt tietue- ja varianttityypit eivät yksinään riitä kovin mielenkiintoisten tietorakenteiden toteuttamiseen. Useimmissa ohjelmissa tarvitaan erilaisia
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 3: Funktionaalinen listankäsittely ja listankäsittelyoperaatiot (mm. SICP 22.2.3) Riku Saikkonen 31. 10. 2011 Sisältö 1 Linkitetyt listat 2 Listarakenteet
LisätiedotHahmon etsiminen syotteesta (johdatteleva esimerkki)
Hahmon etsiminen syotteesta (johdatteleva esimerkki) Unix-komennolla grep hahmo [ tiedosto ] voidaan etsia hahmon esiintymia tiedostosta (tai syotevirrasta): $ grep Kisaveikot SM-tulokset.txt $ ps aux
Lisätiedot5.3 Laskimen muunnelmia 5.3. LASKIMEN MUUNNELMIA 57
5.3. LASKIMEN MUUNNELMIA 57 Samaan sarjaan kuuluu seuraavakin funktio, jonka määritelmä esittelee muutenkin hyödyllisen tavan kirjoittaa ohjelmia: getline :: IO String getline = getchar λc case c of \n
LisätiedotT Syksy 2004 Logiikka tietotekniikassa: perusteet Laskuharjoitus 7 (opetusmoniste, kappaleet )
T-79144 Syksy 2004 Logiikka tietotekniikassa: perusteet Laskuharjoitus 7 (opetusmoniste, kappaleet 11-22) 26 29102004 1 Ilmaise seuraavat lauseet predikaattilogiikalla: a) Jokin porteista on viallinen
LisätiedotLaiska laskenta, korekursio ja äärettömyys. TIEA341 Funktio ohjelmointi Syksy 2005
Laiska laskenta, korekursio ja äärettömyys TIEA341 Funktio ohjelmointi Syksy 2005 Muistatko graafinsievennyksen? DAG esitys ja graafinsievennys DAG esitys Lausekkeen rakennepuu, jossa yhteiset alilausekkeet
Lisätiedot815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 3 vastaukset Harjoituksen aiheena ovat imperatiivisten kielten muuttujiin liittyvät kysymykset. Tehtävä 1. Määritä muuttujien max_num, lista,
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 7: Funktionaalista ohjelmointia (mm. SICP 3.5) Riku Saikkonen 13. 11. 2012 Sisältö 1 Laiskaa laskentaa: delay ja force 2 Funktionaalinen I/O 3 Funktionaalista
LisätiedotEsimerkki: Laskin (alkua) TIEA341 Funktio ohjelmointi 1 Syksy 2005
Esimerkki: Laskin (alkua) TIEA341 Funktio ohjelmointi 1 Syksy 2005 Esimerkki: Laskin Liukulukulaskentaa Yhteen, vähennys, kerto ja jakolaskut Syötteenä laskutehtävä, tulosteena tulos tai virheilmoitus
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 6: Rajoite-esimerkki, funktionaalista ohjelmointia (mm. SICP 3.3.5, 3.5) Riku Saikkonen 8. 11. 2012 Sisältö 1 SICP 3.3.5 esimerkki: rajoitteiden vyörytysjärjestelmä
LisätiedotTutoriaaliläsnäoloista
Tutoriaaliläsnäoloista Tutoriaaliläsnäolokierroksella voi nyt täyttää anomuksen läsnäolon merkitsemisestä Esim. tagi ei toiminut, korvavaltimon leikkaus, yms. Hyväksyn näitä omaa harkintaa käyttäen Tarkoitus
Lisätiedotja λ 2 = 2x 1r 0 x 2 + 2x 1r 0 x 2
Johdatus diskreettiin matematiikkaan Harjoitus 4, 7.10.2015 1. Olkoot c 0, c 1 R siten, että polynomilla r 2 c 1 r c 0 on kaksinkertainen juuri. Määritä rekursioyhtälön x n+2 = c 1 x n+1 + c 0 x n, n N,
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 5: Sijoituslause, SICP-oliot, todistamisesta (mm. SICP 33.1.3, 3.33.3.2) Riku Saikkonen 7. 11. 2011 Sisältö 1 Muuttujan arvon muuttaminen: set! 2 SICP-oliot
LisätiedotAlgoritmit 1. Luento 7 Ti Timo Männikkö
Algoritmit 1 Luento 7 Ti 31.1.2017 Timo Männikkö Luento 7 Järjestetty binääripuu Binääripuiden termejä Binääripuiden operaatiot Solmun haku, lisäys, poisto Algoritmit 1 Kevät 2017 Luento 7 Ti 31.1.2017
LisätiedotTIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 3. lokakuuta 2016
TIEA241 Automaatit ja kieliopit, syksy 2016 Antti-Juhani Kaijanaho TIETOTEKNIIKAN LAITOS 3. lokakuuta 2016 Sisällys n tunnistin Jay : An Efficient Context-Free Parsing Algorithm. Communications of the
LisätiedotHaskell ohjelmointikielen tyyppijärjestelmä
Haskell ohjelmointikielen tyyppijärjestelmä Sakari Jokinen Helsinki 19. huhtikuuta 2004 Ohjelmointikielten perusteet - seminaarityö HELSINGIN YLIOPISTO Tietojenkäsittelytieteen laitos 1 Johdanto 1 Tyyppien
LisätiedotRekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä
Rekursiolause Laskennan teorian opintopiiri Sebastian Björkqvist 23. helmikuuta 2014 Tiivistelmä Työssä käydään läpi itsereplikoituvien ohjelmien toimintaa sekä esitetään ja todistetaan rekursiolause,
Lisätiedot815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 2 vastaukset Harjoituksen aiheena on BNF-merkinnän käyttö ja yhteys rekursiivisesti etenevään jäsentäjään. Tehtävä 1. Mitkä ilmaukset seuraava
LisätiedotOhjelmoinnin perusteet Y Python
Ohjelmoinnin perusteet Y Python T-106.1208 28.2.2011 T-106.1208 Ohjelmoinnin perusteet Y 28.2.2011 1 / 46 Ohjelmointiprojektin vaiheet 1. Määrittely 2. Ohjelman suunnittelu (ohjelman rakenne ja ohjelman
LisätiedotChapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen
Chapel TIE-20306 Ryhmä 91 Joonas Eloranta Lari Valtonen Johdanto Chapel on Amerikkalaisen Cray Inc. yrityksen kehittämä avoimen lähdekoodin ohjelmointikieli. Chapel on rinnakkainen ohjelmointikieli, joka
LisätiedotPERL. TIE Principles of Programming Languages. Ryhmä 4: Joonas Lång & Jasmin Laitamäki
PERL TIE-20306 Principles of Programming Languages Ryhmä 4: Joonas Lång & Jasmin Laitamäki 1. Johdanto Perl on ohjelmointikielten perhe, johon kuuluu Perl 5 ja Perl 6. Kielet ovat kuitenkin erilliset ohjelmointikielet
Lisätiedot1 Mitä funktionaalinen ohjelmointi on?
1 Mitä funktionaalinen ohjelmointi on? On olemassa useita erilaisia ohjelmointiparadigmoja (programming paradigms) koska on useita erilaisia tapoja mallintaa ohjelmointiongelmia, esimerkiksi: Proseduraalinen
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 6: Funktionaalista ohjelmointia: todistamisesta, virrat ja I/O, hahmonsovitus (mm. SICP 3.5) Riku Saikkonen 8. 11. 2011 Sisältö 1 Vähän funktionaalisten
Lisätiedot11/20: Konepelti auki
Ohjelmointi 1 / syksy 2007 11/20: Konepelti auki Paavo Nieminen nieminen@jyu.fi Tietotekniikan laitos Informaatioteknologian tiedekunta Jyväskylän yliopisto Ohjelmointi 1 / syksy 2007 p.1/11 Tämän luennon
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 5: Sijoituslause, SICP-oliot, tietorakenteen muuttaminen (mm. SICP 33.1.3, 3.33.3.2) Riku Saikkonen 6. 11. 2012 Sisältö 1 Muuttujan arvon muuttaminen:
LisätiedotJäsennys. TIEA341 Funktio ohjelmointi 1 Syksy 2005
Jäsennys TIEA341 Funktio ohjelmointi 1 Syksy 2005 Muistutus: Laskutehtävä ja tulos data Laskutehtava = Luku Double Yhteen Laskutehtava Laskutehtava Vahennys Laskutehtava Laskutehtava Tulo Laskutehtava
LisätiedotPerusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti
C! Perusteet 19.1.2017 Palautteesta (1. kierros toistaiseksi) (Erittäin) helppoa Miksi vain puolet pisteistä? Vaikeinta oli ohjelmointiympäristön asennus ja käyttö Ei selvää että main funktion pitikin
LisätiedotPerusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti
C! Perusteet 19.1.2017 Palautteesta (1. kierros toistaiseksi) Toistaiseksi helppoa Miksi vain puolet pisteistä? Vaikeinta oli ohjelmointiympäristön asennus ja käyttö Vaikeaa eroavuudet Pythonin ja C:n
LisätiedotOhjelmoinnin peruskurssien laaja oppimäärä
Ohjelmoinnin peruskurssien laaja oppimäärä Luento 8: Pienen ohjelmointikielen tulkki (ohjelmoitava laskin) (mm. SICP 4-4.1.5 osin) Riku Saikkonen 15. 11. 2012 Sisältö 1 Nelilaskintulkki, globaalit muuttujat
LisätiedotOhjelmoinnin perusteet Y Python
Ohjelmoinnin perusteet Y Python T-106.1208 1.4.2009 T-106.1208 Ohjelmoinnin perusteet Y 1.4.2009 1 / 56 Tentti Ensimmäinen tenttimahdollisuus on pe 8.5. klo 13:00 17:00 päärakennuksessa. Tämän jälkeen
Lisätiedotetunimi, sukunimi ja opiskelijanumero ja näillä
Sisällys 1. Algoritmi Algoritmin määritelmä. Aiheen pariin johdatteleva esimerkki. ja operaatiot (sijoitus, aritmetiikka ja vertailu). Algoritmista ohjelmaksi. 1.1 1.2 Algoritmin määritelmä Ohjelmointi
LisätiedotHarjoitustyön testaus. Juha Taina
Harjoitustyön testaus Juha Taina 1. Johdanto Ohjelman teko on muutakin kuin koodausta. Oleellinen osa on selvittää, että ohjelma toimii oikein. Tätä sanotaan ohjelman validoinniksi. Eräs keino validoida
LisätiedotZeon PDF Driver Trial
Matlab-harjoitus 2: Kuvaajien piirto, skriptit ja funktiot. Matlabohjelmoinnin perusteita Numeerinen integrointi trapezoidaalimenetelmällä voidaan tehdä komennolla trapz. Esimerkki: Vaimenevan eksponentiaalin
LisätiedotITKP102 Ohjelmointi 1 (6 op)
ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 12. huhtikuuta 2019 Tee kukin tehtävä omalle konseptiarkille. Noudata ohjelmointitehtävissä kurssin koodauskäytänteitä. Yksi A4-kokoinen lunttilappu
LisätiedotITKP102 Ohjelmointi 1 (6 op)
ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 7. huhtikuuta 2017 Vastaa kaikkiin tehtäviin. Tee jokainen tehtävä erilliselle konseptiarkille. Kirjoittamasi luokat, funktiot ja aliohjelmat
Lisätiedot13. Loogiset operaatiot 13.1
13. Loogiset operaatiot 13.1 Sisällys Loogiset operaatiot AND, OR, XOR ja NOT. Operaatioiden ehdollisuus. Bittioperaatiot. Loogiset operaatiot ohjausrakenteissa. Loogiset operaatiot ja laskentajärjestys.
LisätiedotSystem.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);
Kysy Karilta tai Kimmolta, jos tehtävissä on jotain epäselvää. Kerro WETOon liittyvät ongelmat suoraan Jormalle sähköpostitse (jorma.laurikkala@uta.fi). Muista nimetä muuttujat hyvin sekä kommentoida ja
LisätiedotTIEA341 Funktio-ohjelmointi 1, kevät 2008
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
LisätiedotEhto- ja toistolauseet
Ehto- ja toistolauseet 1 Ehto- ja toistolauseet Uutena asiana opetellaan ohjelmointilauseet / rakenteet, jotka mahdollistavat: Päätösten tekemisen ohjelman suorituksen aikana (esim. kyllä/ei) Samoja lauseiden
LisätiedotTIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 19. tammikuuta 2012
TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho TIETOTEKNIIKAN LAITOS 19. tammikuuta 2012 Sisällys Sisällys Muistathan A B -konstruktion 0 k 1 i 2 s 3 s 4 a 5 0 k 1 o 2 i 3 r 4
Lisätiedot811120P Diskreetit rakenteet
811120P Diskreetit rakenteet 2018-2019 1. Algoritmeista 1.1 Algoritmin käsite Algoritmi keskeinen laskennassa Määrittelee prosessin, joka suorittaa annetun tehtävän Esimerkiksi Nimien järjestäminen aakkosjärjestykseen
LisätiedotPython-ohjelmointi Harjoitus 5
Python-ohjelmointi Harjoitus 5 TAVOITTEET Kerrataan silmukkarakenteen käyttäminen. Kerrataan jos-ehtorakenteen käyttäminen. Opitaan if else- ja if elif else-ehtorakenteet. Matematiikan sisällöt Tehtävät
LisätiedotDatatähti 2019 loppu
Datatähti 2019 loppu task type time limit memory limit A Summa standard 1.00 s 512 MB B Bittijono standard 1.00 s 512 MB C Auringonlasku standard 1.00 s 512 MB D Binääripuu standard 1.00 s 512 MB E Funktio
LisätiedotTIES542 kevät 2009 Tyyppijärjestelmän laajennoksia
TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia Antti-Juhani Kaijanaho 16. helmikuuta 2009 Tyypitetyt ohjelmointikielet sisältävät paljon muitakin konstruktioita kuin yksinkertaisesti tyypitetyn lambda-kielen,
LisätiedotSystem.out.printf("%d / %d = %.2f%n", ekaluku, tokaluku, osamaara);
Mikäli tehtävissä on jotain epäselvää, laita sähköpostia vastuuopettajalle (jorma.laurikkala@uta.fi). Muista nimetä muuttujat hyvin sekä kommentoida ja sisentää koodisi. Ohjelmointitehtävien osalta palautetaan
Lisätiedot815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 4 vastaukset Harjoituksen aiheena ovat imperatiivisten kielten lauseisiin, lausekkeisiin ja aliohjelmiin liittyvät kysymykset. Tehtävä 1. Mitä
LisätiedotOhjelmoinnin perusteet Y Python
Ohjelmoinnin perusteet Y Python T-106.1208 11.2.2009 T-106.1208 Ohjelmoinnin perusteet Y 11.2.2009 1 / 33 Kertausta: listat Tyhjä uusi lista luodaan kirjoittamalla esimerkiksi lampotilat = [] (jolloin
Lisätiedot11.4. Context-free kielet 1 / 17
11.4. Context-free kielet 1 / 17 Määritelmä Tyypin 2 kielioppi (lauseyhteysvapaa, context free): jos jokainenp :n sääntö on muotoa A w, missäa V \V T jaw V. Context-free kielet ja kieliopit ovat tärkeitä
LisätiedotImperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi
Imperatiivisen ohjelmoinnin peruskäsitteet muuttuja muuttujissa oleva data voi olla yksinkertaista eli primitiivistä (esim. luvut ja merkit) tai rakenteista jolloin puhutaan tietorakenteista. puhuttaessa
Lisätiedot1. Algoritmi 1.1 Sisällys Algoritmin määritelmä. Aiheen pariin johdatteleva esimerkki. Muuttujat ja operaatiot (sijoitus, aritmetiikka ja vertailu). Algoritmista ohjelmaksi. 1.2 Algoritmin määritelmä Ohjelmointi
LisätiedotTietotekniikan valintakoe
Jyväskylän yliopisto Tietotekniikan laitos Tietotekniikan valintakoe 2..22 Vastaa kahteen seuraavista kolmesta tehtävästä. Kukin tehtävä arvostellaan kokonaislukuasteikolla - 25. Jos vastaat useampaan
LisätiedotKoottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.
2. Ohjausrakenteet Ohjausrakenteiden avulla ohjataan ohjelman suoritusta. peräkkäisyys valinta toisto Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet
Lisätiedot