fix e e (fix e). fix = λf.(λx.f (x x)) (λx.f (x x)) (9)



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

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

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

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Luku 3. Listankäsittelyä. 3.1 Listat

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

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

JFO: Johdatus funktionaaliseen ohjelmointiin

Haskell ohjelmointikielen tyyppijärjestelmä

ELM GROUP 04. Teemu Laakso Henrik Talarmo

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

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

Java-kielen perusteet

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Ohjelmoinnin perusteet Y Python

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

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

Turingin koneet määritteli sen laitelähtöisesti: Laskenta etenee suorittamalla yksinkertaisia käskyjä siten kuin sen ohjelma

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssien laaja oppimäärä

ITKP102 Ohjelmointi 1 (6 op)

Tietorakenteet ja algoritmit - syksy

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Koka. Ryhmä 11. Juuso Tapaninen, Akseli Karvinen. 1. Taustoja 2. Kielen filosofia ja paradigmat 3. Kielen syntaksia ja vertailua JavaScriptiin Lähteet

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Ohjelmoinnin perusteet Y Python

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

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

Ohjelmoinnin perusteet Y Python

Ydin-Haskell Tiivismoniste

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

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

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

Ohjelmoinnin peruskurssi Y1

Lisää pysähtymisaiheisia ongelmia

Kielioppia: toisin kuin Javassa

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

Tutoriaaliläsnäoloista

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

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

Ohjelmointiharjoituksia Arduino-ympäristössä

815338A Ohjelmointikielten periaatteet

Ohjelmoinnin peruskurssi Y1

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

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä

Ohjelmoinnin peruskurssi Y1

Johdatus Ohjelmointiin

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

ITKP102 Ohjelmointi 1 (6 op)

Java-kielen perusteet

Tietueet. Tietueiden määrittely

Todistus: Aiemmin esitetyn mukaan jos A ja A ovat rekursiivisesti lueteltavia, niin A on rekursiivinen.

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet

Zeon PDF Driver Trial

Python-ohjelmointi Harjoitus 2

8.5 Takarekursiosta. Sanoimme luvun 8.3 foldl -esimerkissämme että

13. Loogiset operaatiot 13.1

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 26. kesäkuuta 2013

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Luku 2. Ohjelmointi laskentana. 2.1 Laskento

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin peruskurssi Y1

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Rekursiiviset palautukset [HMU 9.3.1]

Tämä tarina on Fibonaccin lukujen ongelman alkuperäinen muotoilu.

Tietotyypit ja operaattorit

Ohjelmoinnin perusteet Y Python

Kerta 2. Kerta 2 Kerta 3 Kerta 4 Kerta Toteuta Pythonilla seuraava ohjelma:

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

Osoitin ja viittaus C++:ssa

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 31. maaliskuuta 2011

Ohjelmoinnin peruskurssi Y1

Harjoitus 5. Esimerkki ohjelman toiminnasta: Lausekielinen ohjelmointi I Kesä 2018 Avoin yliopisto 1 / 5

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

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

Ohjelmoinnin peruskurssien laaja oppimäärä

ITKP102 Ohjelmointi 1 (6 op)

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

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

5. HelloWorld-ohjelma 5.1

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 4: Ohjelmointi, skriptaus ja Python

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

Transkriptio:

Käytännön funktionaaliset ohjelmointikielet esittävät rekursion tällä tavalla. Teorian näkökulmasta olisi kuitenkin eleganttia, jos oikean puolen Termissä ei tarvittaisi vasemman puolen Muuttujannimeä, koska silloin koko määritelmä olisi yksinkertainen kuin sanakirjassa. Tämä voidaan tehdä lisäämällä kieleen Y- eli kiintopistekombinaattori (fixpoint / fixed-point combinator) ja sille δ-reduktiosääntö. Esimerkiksi normaalin sievennysjärjestyksen kielessä se on fix e e (fix e). Se voitaisiin jopa määritellä ilman rekursiota fix = λf.(λx.f (x x)) (λx.f (x x)) (9) joskin tämän määritelmän muoto riippuu normalisointijärjestyksestä. Esimerkiksi kertomafunktio (8) saisi muodon 2.5.2 Tyypitys fact = fix (λf.λn. if (n > 1) (n f (n 1)) 1) jossa uusi parametri f saa arvokseen factin oikean puolen. Nämä δ-reduktiot tuovat kuitenkin mukanaan sen, että enää ei aina tiedetäkään, miten voitaisiin jatkaa normalisointia: Jos redeksinä löydetään vaikkapa 6 + Z True niin sille ei olekaan vastaavaa oikeaa puolta. Ohjelmoija sanoisi, että silloin ohjelmassa on tyyppivirhe koska siinä yritetään soveltaa alkeisoperaatiota vääränlaiseen argumenttiin. Alun perin tyyppiteoriaa (type theory) kehitettiin matemaattisessa logiikassa torjumaan sellaisia itseensä viittaavia ilmauksia kuten valehtelijan paradoksi Tämä lause ei ole totta! joille ei voi antaa loogista tulkintaa. Tietojenkäsittelijät puolestaan soveltavat tyyppiteoriaa tyyppivirheiden käsittelyyn ohjelmointikielissä, joissa itseensä viittaaminen eli rekursio on välttämätöntä. Ohjelmointikielessä voi olla dynaaminen tyypitys, jolloin ohjelman suoritus keskeytyy suoritusaikaiseen tyyppivirheeseen, jos se päätyy tilanteeseen, jota ei osatakaan normalisoida. staattinen tyypitys, jolloin kielen toteutus tutkii etukäteen ohjelman lähdekoodin, ja yrittää varmistua, ettei sen suoritus voi johtaa tyyppivirheisiin. Ohjelma hyväksytään suoritettavaksi vain jos tämä pystytään varmistamaan hylätään jos ei pystytä, ja ohjelmoijalle raportoidaan käännösaikainen tyypitysvirhe. Ohjelmoijan näkökulmasta dynaaminen tyypitys on joustavampaa kuin staattinen 17

staattinen tyypitys vähentää debuggauksen vaatimaa vaivaa, koska se karsii osan ohjelmointivirheistä jo ohjelmaa kirjoitettaessa. Staattinen tyypitys jakautuu vielä tyypintarkistukseen (typechecking) jossa ohjelmoija ilmoittaa koodissaan käyttämiensä muuttujien tyypit, ja kielen toteutus tarkastaa, että ne ovat oikein. Esimerkiksi kertomafunktiossa (8) ohjelmoija ilmoittaisi, että sen tyyppi on Z Z eli funktio, jonka parametri on tyyppiä Z ja tulos tyyppiä Z. Useimmat staattisesti tyypitetyt ohjelmointikelet käyttävät tyypintarkistusta. tyypinpäättelyyn (type inference) jossa ohjelmoijan ei tarvitse ilmoittaa muuttujiensa tyyppejä, vaan toteutus päättelee ne käyttöyhteyden perusteella. Esimerkiksi kertomafunktiossa (8) tämä päättely voisi edetä seuraavasti: 1 Koska fact alkaa λn niin sen pitää olla jotakin funktiotyyppiä t u. 2 Koska parametristä n vähennetään 1 Z niin myös sen tyypin t pitää olla Z. 3 Koska tuloksena voi olla 1 Z niin myös sen tyypin u pitää olla Z. Tällaiset menetelmät pohjautuvat logiikkaan. LAP-kurssin muistiinpanoissa mainittiin seuraava tulos: Lause 4 (Ricen lause). Jokainen Turingin koneiden epätriviaali semanttinen ongelma on ratkeamaton. Lauseen 2 perusteella sama pätee myös λ-laskennalla kirjoitetuille ohjelmille. Koska on epätriviaali semanttinen ongelma varmistaa voiko annettu ohjelmakoodi päätyä tyyppivirheeseen vaiko ei, niin tarkka tyypinpäättely on ratkeamatonta eli sellaisen ohjelmointikielen toteutus mahdotonta. Siksi tyypinpäättely on turvallisesti epätarkkaa: Ohjelmakoodi hyväksytään suoritettavaksi vain jos ohjelmointikielen toteutus on pystynyt varmistumaan että tyyppivirheet ovat mahdottomia. Tätä epätarkkuutta on vähennetty ohjelmointikielten tutkimuksessa ja kehityksessä vaihe vaiheelta määrittelemällä niille yhä ilmaisuvoimaisempia tyyppijärjestelmiä. Tällä kurssilla ja Haskell-kielessä sitoudutaan seuraavaan täsmällisen tyypityksen periaatteeseen: Jokaisella muuttujalla on täsmälleen yksi tarkka tyyppi, ja se määrätään staattisesti. Matematiikassa ei noudateta tätä periaatetta aivan orjallisesti: 1 Esimerkiksi jokainen luonnollinen luku m N kuuluu myös kokonaislukutyyppiin Z, ja 2 kaikki ne kuuluvat puolestaan rationaalilukutyyppiin Q, ja 3 kaikki ne kuuluvat puolestaan reaalilukutyyppiin R, ja 4 kaikki ne kuuluvat puolestaan kompleksilukutyyppiin C. Samaan tapaan olio-ohjelmoinnissa luokan L jäsenet ovat samalla myös sen jokaisen yliluokan jäseniä. 18

3 Funktionaalisen ohjelmoinnin historiaa 1934 Kurt Gödel aloitti rekursiivisten funktioiden ja relaatioiden teorian. Se oli osa hänen kuuluisan epätäydellisyyslauseensa todistusta. Se oli ensimmäinen ehdotus arkikielen käsitteen mekaanisesti laskettava tarkaksi määritelmäksi. Se oli kuitenkin puhtaan looginen fomalismi, jolloin sen mekaanisuus ei ollut aivan ilmiselvää muille kuin matemaatikoille. 1936 kehitettiin (toisistaan riippumatta) 2 sen selkeämmin mekaanista muotoilua: Alan Turing esitteli nimeään kantavat koneet. Alonzo Church esitteli λ-laskennan, jota käsittelimme luvussa 2. Siten funktionaalinen ohjelmointi oli läsnä jo silloin, kun teoreettinen tietojenkäsittelytiede otti ensiaskeleensa. 1956 määriteltiin 2 vanhinta yhä käytössä olevaa ohjelmointikieltä: Fortran (Formula translator) josta tuli pitkäksi aikaa tieteellisen laskennan eli numeronmurskauksen valtakieli. Lisp (List processing) josta tuli pitkäksi aikaa tekoälytutukimuksen valtakieli. Se lainasi λ-laskennan ideoita. Siinä on dynaaminen tyypitys. Siten funktionaalinen ohjelmointi oli läsnä jo modernien ohjelmointikielten syntyessä. 1958-78 välillä tyyppiteoriaa (jonka tutkimus oli alkanut jo 1900-luvun alussa) kehitettiin edelleen, aluksi loogikkojen ja myöhemmin myös tietojenkäsittelijöiden toimesta. Tämän tutkimuksen keskeinen tuotos oli Hindley-Milner- eli HM-tyyppiteoria, johon useimmat nykyiset tyypinpäättelevät ohjelmointikielet pohjautuvat. Tietojenkäsittelijä Robin Milner keksi itsenäisesti uudelleen saman teorian, jota loogikko J. Roger Hindley oli tutkinut aiemmin. Alkuperäisen idean olivat keksineet logiikassa jo Churchin oppilas Haskell B. Curry ja Robert Feys. Sekä ohjelmointikieli Haskell että kuritus (jonka tosin keksi toinen loogikko Moses Schönfinkel) on nimetty Curryn mukaan. 1990-luvun alussa funktionaalisten ohjelmointikielten kehitys oli edennyt standardointivaiheeseen saakka: Dynaamisesti tyypitetty Common Lisp -kieli sai lopullisen standardinsa (Steele, 1990). (Saman kieliperheen yhä kehittyviä kieliä ovat mm. Scheme ja Clojure.) HM-tyypinpäättelevän ahkeran kielen Standard ML (SML) ensimmäinen standardi ilmestyi. 19

HM-tyypinpäättelevän kielemme Haskell ensimmäinen standardi ilmestyi. Standardi määrittelee, että Haskell-kielen suoritustapa on rento. Haskell-kielen toteutukset ovat laiskoja, vaikka standardi ei sitä tarkkaan ottaen vaadikaan. Siksi Haskell-kieltäkin on tapana kutsua laiskaksi. 1990-luvun lopussa myös näiden HM-tyypinpäättelevien kielten standardointi oli edistynyt: SML 97 (Milner et al., 1997) on SML-kielen lopulliseksi tarkoitettu standardi. (Samaan kieliperheeseen kuuluvat mm. OCaml ja F#.) Haskell 98 (Peyton Jones, 2003) on Haskell-kielen merkittävä standardi. 2000-luvulla kiinnostus erityisesti Haskell-kieleen on kasvanut. Sillä on rentona kielenä monia kauniita teoreettisia ominaisuuksia. Onko niistä hyötyä käytännön ohjelmoinnissa vai haittaa? Sen laajennettu HM-tyypinpäättely on yhdistänyt parametrisen ja ad hoc - monimuotoisuuden. Tälle yhdistelmälle on löytynyt monia yllättäviä käyttökohteita ohjelmoinnissa. Tällä hetkellä uusin standardi on Haskell 2010 (Marlow, 2010). 4 Haskell-kieliset ohjelmat Tällä kurssilla tutustutaan funktionaaliseen ohjelmointikieleen Haskell. Sen kotisivu on http://www.haskell.org/ josta löytyy standardi- ja muuta materiaalia, toteutuksia, kirjastoja,... Haskell-oppikirjoja on sekä painettuna (Hutton, 2007; Thompson, 2011) että verkossa (Lipovačka, 2011; O Sullivan et al., 2008). Haskell on puhdas (pure) funktionaalinen kieli, eli siinä ei ole lainkaan sivuvaikutuksia. Jos Haskell hyväksyy lähdekoodisi ilman virheilmoituksia, niin koodisi on varmasti funktionaalista. Puhtaus ja laiskuus ovat yhteydessä toisiinsa: epäpuhtaassa laiskassa kielessä ei tiedettäisi milloin eri sivuvaikutukset tapahtuisivat. Haskell onkin varsin uskollinen (rennolle laiskalle) λ-laskennalle. Keskitymme aluksi Haskellin suorituspuoleen ja syvennämme myöhemmin sen tyypityspuolen kuvausta. Käytämme Haskell Platform -pakettia, jonka voi asentaa kielen kotisivulta, ja jossa on kääntäjä ghc (eli Glasgow Haskell Compiler). 20

lauseke tuottaa omaa arvo omaa tyyppi on osa sopii omaa hahmo Kuva 1: Jokainen arvo, lauseke, ja hahmo omaa tietyn tyypin. tulkki ghci (eli ghc interactive) jota käytämme pääosan kurssista. Sen keskeisiä komentoja ovat :load tiedosto joka lataa Haskell-lähdekoodia. :type lauseke joka ilmoittaa päätellyn tyypin. kirjastot jotka laajentavat Haskell-standardin peruskirjastoja merkittävästi uusilla tietotyypeillä ja funktioilla. Paketin lisäksi kannattaa hankkia jokin editori, joka ymmärtää Haskell-syntaksia. Haskell-koodin merkitys voi riippua siitä, kuinka se on sisennetty. Yliopiston Windows-koneissa sellainen on Notepad++. Luennoija suosii itse Emacs-editorin Haskell-moodia. Esittelemme rinnakkain 4 käsitettä: Arvot jotka ovat laskennan tuloksia. Tyypit koska jokaisella arvolla on yksi tietty tyyppi. Lausekkeet jotka tuottavat arvoja. Lausekkeen tyyppi kertoo, minkä tyyppisiä arvoja se voi tuottaa. Hahmot (patterns) joita käytetään lausekkeissa sopimaan (match) arvoihin. Hahmon tyyppi kertoo, minkä tyyppisiin arvoihin se sopii. Kuva 1 esittää niiden väliset suhteen. 4.1 Syntaksista Muuttujien nimet alkavat pienellä kirjaimella, ja jatkuvat kirjaimilla, numeroilla, alaviivoilla _ ja heittomerkeillä. Muut nimet (kuten tyyppien, konstruktorien ja modulien nimet) ovat muuten samanlaisia, mutta ne alkavatkin SUURELLA kirjaimella. Haskellissa saa käyttää samaa nimeä eri tarkoituksiin, kun ne eivät voi mennä sekaisin: Esimerkiksi tyypin nimi voi olla sama kuin konstruktorin nimi, koska tyypit ja konstruktorit eivät voi esiintyä samassa osassa koodia. 21

x y tarkoittaa == x on sama kuin y /= x ei ole sama kuin y < x on pienempi kuin y > x on pienempi kuin y <= x on pienempi tai yhtäsuuri kuin y >= x on suurempi tai yhtäsuuri kuin y + lukujen yhteenlasku, - vähennuslasku, * kertolasku, / jakolasku ja ˆ potenssiinkorotus && x ja y x tai y ++ listojen x ja y yhdistäminen peräkkäin. yhdistetty funktio x y $ Oikealle assosioiva heikommin kuin sitova funktionkutsuoperaattori. Siis x $ y $ z u on x (y (z u)). Taulukko 1: Haskell-kielen valmiiksi määriteltyjä operaattoreita. Lisäksi muuttujan nimi voi koostua pelkästään erikoismerkeistä, jolloin se tulkitaan binääriseksi operaattoriksi, eli 2-parametriseksi funktioksi, joka kirjoitetaankin niiden väliin. Taulukossa 1 on yleisimmät valmiiksi määritellyt operaattorit. Jos on operaattori, niin (x ) on funktio λy.x y eli jolle on annettu vain sen 1. argumentti x ( y) on funktio λx.x y eli jolle on annettu vain sen 2. argumentti y ( ) on funktio λx.λy.x y eli ilman kumpaakaan sen argumenttia, eli tavallisena niiden eteen kirjoitettavana funktiona. Siis x y on sama kuin ( ) x y. Kääntäen, tavallisesta nimestä saa operaattorin ympäröimällä se takaperoisilla lainausmerkeillä.... Siis x f y on sama kuin f x y. Operaattoreille voi määritellä assosiaatiosuunnan ja sidontavoiman infixp q jossa p on l(eft, vasemmalle) tai r(ight, oikealle) tai puuttuu (ei assosioidu) oletusarvo on l q on sidontavahvuus 0 q 9 heikoimmasta vahvimpaan oletusarvo on 9. Kommentteja on kolmenlaisia: Merkkiyhdistelmä -- aloittaa kommentin, joka jatkuu tämän rivin loppuun saakka. 22

Merkkiyhdistelmien {- -} välinen teksti on kommenttia. Nämä kommentit voivat olla sisäkkäin, joten niillä voi kommentoida lähdekoodia pois. Merkkiyhdistelmien {-# #-} välinen teksti on erikoiskommentti, johon voi laittaa Haskell-toteutukselle ohjeita (pragmas). Tällä kurssilla tärkein niistä on lähdekooditiedoston alkuun sijoitettava {-# LANGUAGE laajennus_1,laajennus_2,... #-} jolla voi kääntää päälle haluamansa laajennukset standardikieleen ja käyttää niitä tässä lähdekooditiedostossa. Jos yrittää käyttää jotakin laajennusta kääntämättä sitä ensin päälle, niin ghci ilmoittaa asiasta. 4.2 Atomiset perustietotyypit Haskell δ-määrittelee joitakin atomisia perustietotyyppejä operaatioineen. Tyypin rakenteisuus tarkoittaa, että tyypin arvot sisältävät pienempiä osia, joilla on omat arvonsa esimerkiksi tietue sisältää kenttiä, joilla on omat sisältönsä atomisuus tarkoittaa, että tyypin arvoja ei voi jakaa tällä tavoin pienempiin rakenneosiinsa. Näiden atomisten tyyppien vakiot ovat samalla lausekkeita jotka tuottavat vastaavan arvon siis lukuvakio 123 tuottaa lukuarvon 123 ja niin edelleen hahmoja jotka sopivat siihen arvoon. 4.2.1 Totuusarvotyyppi Totuusarvotyypin nimi on Bool. Sen vakiot ovat False and True. Lausekkeessa if ehto then tulos_t else tulos_f ehto on tyyppiä Bool sen kumpikin tuloshaara tulos_t ja tulos_f on keskenään samaa tyyppiä t tämä tyyppi t on samalla koko if-lausekkeen tyyppi koska kaikki sen tuottamat arvot ovat sitä tyyppiä. else-haaraa ei voi jättää pois mikä arvo silloin tuotettaisiin, jos ehto on False? 23

4.2.2 Merkkityyppi Haskell käyttää Unicode-merkistöä. Merkkityypin nimi on Char. Merkkivakio kirjoitetaan yksinkertaisten lainausmerkkkien... väliin. Merkkivakioiden kirjoitusasu (Marlow, 2010, luku 2.6) on ohjelmointikielen C käyttämän kirjoitusasun laajennus. Haskell käsittelee merkkijonoja merkkilistoina. 4.2.3 Kokonaislukutyypit Haskell sisältää valmiina tyypit Integer matemaatikon mielivaltaisen kokoisille kokonaisluvuille Z. Niissä bittien lukumäärää rajoittaa vain vapaa muisti. Aritmetiikka on hidasta, koska se on toteutettu ohjelmallisesti. Int ohjelmoijan kokonaisluvuille, joiden bittien lukumäärä on toteutuksen luonnollinen sananpituus. Se on tyypillisesti 32 tai 64 bittiä, Haskell-toteutuksesta riippuen. Haskell-standardi sallii sen, että toteutus voi varata muutaman biteistä omaan sisäiseen kirjanpitoonsa, joten bittien määrä voikin olla esimerkiksi vain 30. Aritmetiikka on nopeaa, koska se on toteutettu konekäskyillä. Aritmetiikka pyörähtää ympäri hiljaisesti kun luku ei enää mahdu tähän maksimipituuteen. Kirjastossa Data.Int on tyypit Intb joilla voi antaa etumerkillisen kokonaisluvun bittien määrän b = 8, 16, 32, 64 tarkasti. Kirjastossa Data.Word on vastaavat tyypit Wordb etumerkittömille kokonaisluvuille tyyppi Word joka on etumerkitön vastine tyypille Int. Kirjastossa Data.Ratio on tyyppi Rational matemaatikon murtoluvuille Q. välineet joilla voi määritellä tyyppien Intb perusteella sellaiset ohjelmoijan murtoluvut, joissa on haluttu määrä bittejä. 4.2.4 Liukulukutyypit Haskell sisältää valmiina IEEE-standardin mukaiset tyypit Real yksin- ja Double kaksinkertaisen tarkkuuden liukuluvuille. Epätarkkuutensa vuoksi yksinkertaista tarkkuutta kannattaa käyttää vain silloin jos kaksinkertainen kuluttaisi liikaa muistia. Kirjastossa Data.Complex on välineet, joilla voi määritellä kompleksiluvut C näiden tyyppien perusteella. 24

4.3 Funktiot Tyyppi funktio, jonka argumentti on tyyppiä t ja tulos on tyyppiä u kirjoitetaan t -> u. Tämä -> on tyyppikonstruktori: Funktio, joka ottaa parametreinaan tyypit t ja u, ja tuottaa niistä tuloksenaan tyypin t -> u. Tämä tyyppikonstruktori -> assosioi oikealle, joten 2-parametrisen kuritetun funktion tyyppi t -> (u -> v) eli funktio, joka ottaa argumentin tyyppiä t ja palauttaa funktion, joka ottaa argumentin tyyppiä u ja palauttaa tuloksen tyyppiä v voidaan kirjoittaa ilman sulkuja t -> u -> v ja niin edelleen. Funktiotyypit ovat atomisia. Funktiotyypeillä ei ole omia hahmoja. λ-abstraktio λx.e kirjoitetaan lausekkeena \ x -> e. Lauseke \ x y -> e on lyhenne lausekkeelle \ x -> \ y -> e ja niin edelleen. Ohjelmoija voi esitellä uuden muuttujan haluamaansa Tyyppiä ja määritellä sille arvon lauseke seuraavasti: muuttuja :: Tyyppi muuttuja = lauseke Tavallisesti ohjelmoija on määrittelemässä uutta funktiota, jonka nimeksi annetaan muuttuja, eli muuttuja :: ParametrinTyyppi -> TuloksenTyyppi muuttuja = \ parametri -> runko joten sen voi kirjoittaa siistimmin muodossa muuttuja :: ParametrinTyyppi -> TuloksenTyyppi muuttuja parametri = runko joissa parametreja voi myös olla useita. λ-merkintää \ käytetäänkin tavallisesti vain silloin kun määritellään uusi nimetön funktio. Niitä käytetään paljon funktionaalisessa ohjelmoinnissa, jossa on tavallista lähettää funktioita parametreina toisiin funktioihin ja saada tuloksena uusia funktioita laskea funktioilla. 25

Esimerkkinä olkoon funktioiden yhdistäminen joka määritellään matematiikassa (f g)(x) = f(g(x)) eli funktioista f ja g muodostettu f g on sellainen funktio, joka soveltaa ensin parametriin x funktiota g ja sitten tulokseen funktiota f. Haskellilla sen määritelmän voi (mutta ei ole pakko, koska Haskell tekee sen itse) kirjoittaa jossa f. g = \ x -> f (g x) 1. parametri on funktio f :: u -> v 2. parametri on funktio g :: t -> u tulos on uusi nimetön funktio tyyppiä t -> v joka yhdistää nämä 2 parametriaan. Itse asiassa merkintää...:: Tyyppi eli...on tätä Tyyppiä voi käyttää näiden määritelmien lisäksi myös Haskellin lausekkeissa ja hahmoissa. Ohjelmoija ei voi huijata näillä merkinnöillä Haskellia: 1 Haskell päättelee joka tapauksessa ohjelmoijan kirjoittamien lausekkeiden ja hahmojen tyypit itsenäisesti. 2 Sitten se vertaa saamiaan tuloksia ohjelmoijan kirjoittamiin merkintöihin. Ohjelmoija voi jättää nämä merkinnät pois koodistaan, koska Haskell päättelee tyypit itse. Hyvä periaate on jos funktio kaipaa kommenttia, niin sille kannattaa antaa myös tyyppi koska tyyppi on samalla funktion dokumentaatiota jonka kone vieläpä tarkistaa. Nyt voimme määritellä funktioita, jotka operoivat atomisten perustietotyyppien arvoilla. Käytetään esimerkkinä kertomafunktiota (8). Koska tämä funktio tuottaa pienestäkin kokonaisluvusta hyvin suuren kokonaisluvun, valitaan sen tyypiksi Int -> Integer. Nyt kohtaamme Haskellin täsmällisen tyypityksen: Int on eri tyyppi kuin Integer, joten niitä ei voi kertoa yhteen, vaikka ne molemmat ovatkin kokonaislukutyyppejä. Siten parametri n on ensin muunnettava tyypistään Int tyyppiin Integer. Täsmällisessä tyypityksessä sama arvo ei vaihda tyyppiiään, vaan siitä tuoteteaan toinen arvo, jolla on haluttu tyyppi. Tässä sen tekee Haskell-kirjastofunktio fromintegral. Myöhemmin näemme, miten Haskell on laajentanut HM-tyypinpäättelyä sallimaan tällaiset funktiot. Tämä fact on varsin konventionaalista ohjelmointia. 26

fact :: Int -> Integer fact n = if n < 2 then 1 else fromintegral n * fact (n-1) fact2 = fact. (2 *) fact2 on sen sijaan funktio joka on määritelty laskutoimituksella olemassa olevista funktioista eikä suoraan. Suoran määritelmän voi laskea koodiyhtälöillämme: joten suora määritelmä on fact2 δ fact. (2 *) δ fact. (\ y -> 2 * y) δ \ x -> fact ((\ y -> 2 * y) x) β \ x -> fact (2 * x) fact2 x = fact (2 * x) josta näkyy mikä funktio niin määriteltiin. 4.3.1 Paikalliset määritelmät Ohjelmoinnissa tarvitaan usein paikallisia määritelmiä, jotka ovat voimassa vain siellä, missä ohjelmoija niin määrää. Tämä voidaan ilmaista lausekkeella let { määritelmä ; määritelmä ; määritelmä... ; määritelmä } in lauseke eli nämä määritelmät ovat käytössä täsmälleen silloin kun tätä lauseketta lasketaan. Jatketaan edellistä fact-esimerkkiä. Nyt muunnetaankin Int n aluksi yhden kerran Integeriksi. Siinä määritellään kertomafunktio fact :: Integer -> Integer paikallisesti. Annetaan Haskellin päätellä sen tyyppi itse. 27

fact :: Int -> Integer fact = let fact n = if n < 2 then 1 else n * fact (n-1) in fact. fromintegral Nämä paikalliset määritelmät voi kirjoittaa myös muodossa lauseke where { määritelmä ; määritelmä ; määritelmä... ; määritelmä } Tässä vaihtoehtoisessa tavassa näkyvyysalue voi olla myös Hahmo eikä lauseke. Se yksinkertaistaa usein ohjelmien kirjoitusasua. 4.3.2 Suluton syntaksi Esimerkissämme ei kuitenkaan näkynyt aaltosulkeita {...} vaikka let-syntaksissa määritelmät ympäröitiin niillä ja erotettiin toisistaan puolipisteellä ;. Haskell sallii aaltosulkeiden ja puolipisteiden jättämisen pois, jos ohjelmakoodi sisennetään niin, että siitä näkyy sama informaatio. Sisennysperiaate (Marlow, 2010, luku 2.7) on seuraava: Jos aloittavan let-, where-, of- tai do-sanan jälkeen ei tulekaan avaavaa aaltosulkua {, niin avattu rakenne tulkitaan sisennetyksi. Jos näin avatun rakenteen sisällä kahden eri osan välillä pitäisi olla ; niin kumpikin niistä pitää sen sijaan sisentää alkamaan samalta sarakkeelta, ja tämän yhteisen aloitussarakkeen pitää olla jossakin rakenteen aloittaneen sanan oikealla puolella. Siis esimerkiksi jokaisen määritelmän pitää alkaa samalta sarakkeelta letin tai wheren oikealta puolelta. Jos edellisessä esimerkissämme olisi ollut useita paikallisia määritelmiä, niin jokasisen niistä olisi pitänyt alkaa samalta sarakkeelta kuin fact. Jos avatun rakenteen sisällä jokin osa koostuu monesta eri rivistä, niin sen muut rivit pitää sisentää alkamaan tämän yhteisen aloitussarakkeen sarakkeen oikealta puolelta. Siten edellisessä esimerkissämme if-lauseke on sisennetty fact -määritelmän alkukohdan oikealle puolelle. Avattu rakenne sulkeutuu kun ohjelmakoodissa kohdataan rivi, joka onkin sisennetty tämän yhteisen aloitussarakkeen vasemmalle puolelle. Edellisessä esimerkissämme se on in-rivi. 28

4.4 Rakenteiset tietotyypit Rakenteisten tyyppien arvot sisältävät osia, jotka ovat myöskin arvoja, ja joilla siis on omat tyyppinsä. Näiden kokonaisten arvojen sisältämiä osia pääsee tutkimaan hahmonsovituksella (pattern matching). Eräs Haskell-syntaksin suunnitteluperiaatteista on, että hahmot muistuttavat mahdollisimman paljon niitä arvoja, joihin ne sopivat. Tavoitteena on siis koodinlukutapa jos syötearvo näyttää tältä, niin sitä vastaava tulosarvo näyttää tuolta. 4.4.1 Monikot Funktion tulos koostuu usein monesta eri osasta, jotka yhdessä muodostavat vastauksen. Esimerkiksi kirjastofunktio quotrem on kokonaislukujen tarkka jakolasku jonka vastaus koostuu siis 1 osamäärästä ja 2 jakojäännöksestä eli se on kokonaislukupari. Haskell tarjoaakin valmiina tyypit kaikille pareille, kolmikoille, nelikoille,... koska muuten jokainen ohjelmoija kuitenkin määrittelisi itselleen omat sellaiset jolloin heidän koodiensa yhdistäminen olisi turhan hankalaa. Tarkemmin sanoen, Haskell tarjoaa valmiina tyyppikonstruktorit pareille, kolmikoille, nelikoille,... Tyyppi (Tyyppi_1,Tyyppi_2, Tyyppi_3,...,Tyyppi_k ) on niiden monikkojen tyyppi, jossa on k osaa, joista 1 osan arvojen tyyppi on Tyyppi_1 2 osan arvojen tyyppi on Tyyppi_2 3 osan arvojen tyyppi on Tyyppi_3 ja niin edelleen. Tämän tyypin lausekkeet ovat muotoa (lauseke_1,lauseke_2,lauseke_3,...,lauseke_k) ja hahmot muotoa (Hahmo_1,Hahmo_2,Hahmo_3,...,Hahmo_k ) josta näkyy arvojen ja lausekkeiden syntaktinen samankaltaisuusperiaate. Niiden osat vastaavat toisiaan siten, että jokainen lauseke_i :: Tyyppi_i ja Hahmo_i :: Tyyppi_i. 29

Haskell ei tarjoa 1-osaisia monikkotyyppejä (Tyyppi) koska sulutettu (lauseke) tai (hahmo) on sama asia kuin pelkkä suluton lauseke tai Hahmo. Haskell tarjoaa 0-osaisen monikkotyypin (). Sitä voidaan käyttää, kun funktio ei palauta kutsujalleen mitään kiinnostavaa informaatiota koska sen pitää palauttaa jotakin. Tällaisia funktioita on esimerkiksi I/O-operaatioissa, joita kutsutaan sivuvaikustensa eikä vastauksensa vuoksi esimerkiksi putstr joka tulostaa merkkijonon stdout-oletustulostustiedostoon. Haskell siis erottaa toisistaan funktiokutsut f(x,y) jossa funktiota f kutsutaan yhdellä parametrilla, joka on lausekkeiden x ja y arvoista rakennettu pari f x y jossa funktiota f kutsutaan kahdella eri parametrilla, jotka ovat lausekkeiden x ja y arvot sellaisinaan. 4.4.2 Hahmonsovitus Hahmonsovituslauseke on muotoa case lauseke of { Hahmo_1 -> tulos_1 ; Hahmo_2 -> tulos_2 ; Hahmo_3 -> tulos_3... ; Hahmo_n -> tulos_n } jonka jokainen Hahmo_i :: t on samaa tyyppiä t kuin lauseke :: t jotta niitä voidaan sovittaa sen arvoihin tulos_j :: u on keskenään samaa tyyppiä u jotta koko case-lausekkeen kaikilla mahdollisilla eri tuloksilla on sama tyyppi u. Sen intuitiivinen toiminta on: 1 Olkoon a se arvo, jonka lauseke tuotti. 2 Valitaan kirjoitusjärjestuksessa ensimmäinen Hahmo_k johon sen arvo a sopii. Koko case-lausekkeen tulos on sen tulos_k. 3 Jos yksikään Hahmo_k ei sovi arvoon a niin koko ohjelman suoritus keskeytyy ajonaikaiseen virheeseen laskennan tulosta ei ole määritelty. Koska arvo a lasketaankin laiskasti (eikä ahkerasti kuten tässä oletamme) niin tarkennamme myöhemmin, miten se tapahtuu. Esimerkiksi if ehto then tulos_t else tulos_f 30

on sama kuin case ehto of True -> tulos_t False -> tulos_f Hahmoina sallitaan myös muuttujat. Tavoitteena on, että tulos_k voisi käsitellä niitä osia arvosta a jotka sen Hahmo_k paljasti. Tämä tavoite saavutetaan siten, että tällainen muuttujahahmo antaa paikallisen nimen sitä vastaavalle arvon a osalle, ja tulos_k (mutta vain se) voi käyttää tätä näin annettua nimeä. Koska kyse on nimeämisestä, sama muuttuja saa esiintyä samassa Hahmossa vain kerran. Siis esimerkiksi (z,z) ei ole sallittu Hahmo vaikka sillä olisikin luonteva tulkinta: ne parit, joiden osat ovat keskenään samat. Hahmona sallitaan myös alaviiva _. Se tarkoittaa, että tulos_k ei tarvitse tätä osaa arvosta a joten sille osalle ei anneta omaa nimeä. Hahmona sallitaan myös muuttuja@hahmo joka on kuin sisältämänsä Hahmo ja sen lisäksi nimeää koko siihen sopineen arvon tällä muuttujalla. Esimerkiksi x@(5,y) sopii sellaisiin pareihin, joiden ensimmäinen osa on 5, ja se antaa parin toiselle osalle nimeksi y ja koko parille nimeksi x. Esimerkiksi case x quotrem y of (q,r) -> tulos antaa tuloslausekkeessa nimen q osamäärälle (quotient) ja nimen r jakojäännökselle (remainder) kun kokonaisluku x jaetaan kokonaisluvulla y. Tällainen nimeäminen voikaan kirjoittaa helpommin let (q,r) = x quotrem y in tulos sallimalla myös tällaiset määritelmät joiden vasen puoli (ennen yhtäsuuruusmerkkiä = ) onkin kokonainen Hahmo eikä pelkkä nimi. Hahmoon voi liittää vartijan (guard). Sen syntaksi on Hahmo vartija jossa vartija :: Bool on lauseke. Arvo a sopii tällaiseen vartioituun Hahmoon vain jos myös sen vartija on True. Tällä vartijalla voi siten ilmaista semanttisen lisäehdon, jonka pitää myös olla voimassa syntaktisen Hahmonsovituksen lisäksi. Esimerksi ne parit, joiden osat ovat keskenään samat voi ilmaista vartioidulla Hahmolla (z,z ) z==z. 31

Sen vuoksi vartijassa saa käyttää (myös) niitä muuttujia jotka tämä Hahmo esitteli, kuten z ja z tässä esimerkissä. Sama Hahmo voi toistua monta kertaa peräkkäin koska siinä Hahmo vartija_1 = tulos_1 Hahmo vartija_2 = tulos_2 Hahmo vartija_3 = tulos_3... Hahmo vartija_m = tulos_m 1 ensin arvo a sovitetaan tähän yhteiseen Hahmoon, ja jos se onnistuu niin 2 sitten valitaan ensimmäinen vartija_i joka on True ja sitä vastaava tulos_i. Silloin tätä yhteistä Hahmoa ei tarvitse toistaa, vaan riittää. Hahmo vartija_1 = tulos_1 vartija_2 = tulos_2 vartija_3 = tulos_3... vartija_m = tulos_m Saman Hahmon eri vartijat voivat tarvita yhteisiä paikallisia määrittelyjä. Ne voidaan kirjoittaa Hahmo vartija_1 = tulos_1 vartija_2 = tulos_2 vartija_3 = tulos_3... vartija_m = tulos_m where määrittely määrittely määrittely... määrittely paljon helpommin kuin letillä. Matematiikassa on yleistä määritellä funktio tapauksittain. Esimerkiksi Fibonaccin luvut voidaan määritellä 0 kun n = 0 fib(n) = 1 kun n = 1 fib(n 1) + fib(n 2) kun n > 1 joka voidaan kirjoittaa jopa muodossa fib(0) = 0 fib(1) = 1 fib(n) = fib(n 1) + fib(n 2) jossa ideana on lukea tapauksia niiden kirjoitusjärjestyksessä ja valita niistä ensimmäinen joka sopii. 32

Myös Haskellissa on tavallista määritellä funktio niin, että se valitsee tuloksensa suoraan parametrinsa perusteella: funktio parametri = case parametri of Hahmo_1 -> tulos_1 Hahmo_2 -> tulos_2 Hahmo_3 -> tulos_3... Hahmo_n -> tulos_n Niinpä tämä voidaankin kirjoittaa vastaavassa muodossa tapauksittain funktio Hahmo_1 = tulos_1 funktio Hahmo_2 = tulos_2 funktio Hahmo_3 = tulos_3... funktio Hahmo_n = tulos_n jonka jokaisella tapauksella on luonteva luenta: Jos funktion parametri sopii tähän Hahmoon niin tässä on vastaava tulos. fib :: Integer -> Integer fib 0 = 0 fib 1 = 1 fib n = fib (n-1) + fib (n-2) foo = (\ (q,r) -> q * r). ( quotrem 2) bar = uncurry (*). ( quotrem 2) Koska nimetyn funktion parametri saa olla Hahmo niin sallitaan sama myös nimettömissä funktioissa. Tämä Haskell-funktion määrittely tapauksittain on luonteva työväline käsiteltäessä Haskell-kielen rakenteisia tietotyyppejä, jotka myöskin määritellään tapauksittain. 4.4.3 Listat Haskell tarjoaa valmiina tyyppikonstruktorin listoille, samasta syystä kuin se tarjoaa tyyppikonstruktorit eri kokoisille monikoille. Funktionaalisessa ohjelmoinnissa käytetään paljon listoja. Laiskuuden ja listojen yhdistelmä on erityisen monikäyttöinen. 33

Kaikkien Haskell-listassa olevien alkioiden pitää olla keskenään samaa tyyppiä, jotta listan sisältöä käsiteltäessä tiedetään minkä tyyppisiä arvoja siellä kohdataan. Tyyppi listat, joiden alkiot ovat tyyppiä t kirjoitetaan [t]. Tyyppiä [t] olevan listan perussyntaksi on tyhjä lista jonka lauseke ja Hahmo on [] epätyhjä lista jonka lauseke tai Hahmo on eka:muut jossa eka :: t on listan ensimmäisen alkion ja muut :: [t] loppulistan lauseke tai Hahmo. Listat ovat ensimmäinen itseensä viittaavasti eli rekursiivisesti määritelty tietotyyppimme: Epätyhjä lista koostuu ekasta alkiosta ja loppulistasta jossa ovat muut sen alkiot tällä samalla tavalla. Listoilla [] on parametriton eli vakio- (:) on 2-parametrinen (arvo)konstruktori: Nimi, joka voi esiintyä lausekkeessa jolloin se merkitsee funktiota, joka luo parametreistaan uuden tämän tyyppisen arvon esimerkiksi (:) :: t -> [t] -> [t] hahmossa jolloin se sopii täsmälleen niihin arvoihin, jotka on luotu juuri tällä funktiolla esimerkiksi a :cs sopii täsmälleen niihin listoihin tyyppiä [Char] jotka alkavat merkillä a. Haskell-syntaksissa on sovittu, että operaattoreista konstruktoreita ovat ne, joiden nimi alkaa kaksoispisteellä : kuten tässä. Konstruktorioperaattori (:) assosoi oikealle joten eka:(toka:(kolmas:loput)) voidaan kirjoittaa ilman sulkuja eka:toka:kolmas:loput. Jos listan pituus tiedetään ennalta, niin sen alkiot voidaan kirjoittaa hakasulkeiden [...] sisään pilkuin, eroteltuina. Esimerkiksi 3-alkioinen lauseke tai Hahmo kuten eka:toka:kolmas:[] voidaan kirjoittaa [eka,toka,kolmas]. Jos kyseessä on merkkivakioiden lista jonka pituus tiedetään ennalta eli merkkijonovakio, niin merkit voidaan kirjoittaa tuttuun tapaan peräkkäin kaksinkertaisten lainausmerkkien "..." väliin ilman erottelevia pilkkuja,. Esimerkiksi [ a, b, c ] voidaan kirjoittaa "abc". TRA-kurssien käsittein kyseessä on yhteen suuntaan linkitetty lista, jota esittää osoitin sen ensimmäiseen alkioon (mutta ei viimeiseen) mutta Haskell-ohjelmoija ei näe näitä osoittimia, jotka ovat kielen toteutustason väline. 34

Listojen käsittelystä Koska Haskell-listat on määritelty rekursiivisesti, niin niitä on luontevaa myös käsitellä rekursiivisesti määritelmänsä suhteen. Toisin sanoen, jos Haskell-funktio saa parametrinaan listan, niin se on luontevaa määritellä tapauksittain: Mitä funktion pitäisi palauttaa... tyhjälle listalle []? epätyhjälle listalle eka:muut jossa muut voidaan käsitellä rekursiivisesti tällä samalla funktiolla? Myös Haskell-syntaksi tukee tällaista ohjelmointia: funktio [] = z funktio (eka:muut) = f eka (funktio muut) jossa lausekkeet z ja f riippuvat siitä, mitä funktion halutaan laskevan. Haskell lataa käynnistyessään automaattisesti vakiokirjaston nimeltä Prelude joka sisältää mm. funktion foldr jonka 1. parametri on tämä funktio f :: t -> u -> u 2. parametri on tämä vakio z :: u tulos on uusi funktio tyyppiä [t] -> u joka toteuttaa tämän listarekursion. Haskell-koodina se on: foldr _ z [] = z foldr f z (x:xs) = f x (foldr f z xs) Usein käytetty nimentätapa: one x, many other xs. Funktionaalisessa ohjelmoinnissa kehitetään ja hyödynnetään usein tällaisia funktioita, jotka ilmaisevat abstraktisti jonkin yleisperiaatteen tässä tätä on rekursio listan rakenteen suhteen soveltuvat moniin eri käyttötarkoituksiin, koska niitten toimintaa voi varioida funktiotyyppisillä parametreilla tässä f. Esimerkiksi funktiot totuusarvolistan alkioiden konjunktio ja... disjunktio voidaan nyt määritellä and = foldr (&&) True or = foldr ( ) False Voimme laskea vaikkapa 35

and [x_1,x_2,x_3,...,x_n] = foldr (&&) True [x_1,x_2,x_3,...,x_n] = (&&) x_1 (foldr (&&) True [x_1,x_2,x_3,...,x_n]) = x_1 && (foldr (&&) True [x_2,x_3,...,x_n])... = x_1 && x_2 && x_3 &&... && x_n && True = x_1 && x_2 && x_3 &&... && x_n joten näin todellakin on. Osaako näin määritelty and pysähtyä heti jos se kohtaa syötelistassaan Falsen, vai jatkaako se silloinkin rekursiolla koko listan loppuun? Operaattori (&&) on määritelty Lasketaanpa: True && x = x False && _ = False and [True,False,True,...] = foldr (&&) True [True,False,True,...] = (&&) True (foldr (&&) True [False,True,...]) = foldr (&&) True [False,True,...] = (&&) False (foldr (&&) True [True,...]) = False eli kyllä laiska suoritus osaa! Tämä osaaminen johtuu siitä, että foldr-rekursiohaara vie parametrin f siihen kohtaan, josta laiska suoritus etsii seuraavan redeksinsä eli f suoritetaankin seuraavaksi ennen rekursiivista foldr-rekursiokutsua. Preludessa on myös listan tuttu läpikäynti järjestyksessä silmukalla 1 while lista ei ole vielä lopussa 2 z = f z listan nykyinen alkio; 3 siirry listan sitä seuraavaan alkioon; 4 return z funktiona foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs eli fold left. foldl ei kuitenkaan osaakaan pysähtyä heti, koska siinä seuraavan redeksinä onkin joka kierroksella foldl itse: foldl (&&) True [True,False,True,...] = foldl (&&) (True && True) [False,True,...] = foldl (&&) ((True && True) && False) [True,...] = foldl (&&) (((True && True) && False) && True) [...] =... 36