Funktionaalinen ohjelmointi



Samankaltaiset tiedostot
815338A Ohjelmointikielten periaatteet

Funktionaalinen ohjelmointi

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

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

Tietorakenteet ja algoritmit - syksy

ELM GROUP 04. Teemu Laakso Henrik Talarmo

Ohjelmoinnin peruskurssien laaja oppimäärä

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Ohjelmoinnin peruskurssien laaja oppimäärä

PERL. TIE Principles of Programming Languages. Ryhmä 4: Joonas Lång & Jasmin Laitamäki

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ehto- ja toistolauseet

815338A Ohjelmointikielten periaatteet

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

811120P Diskreetit rakenteet

Algoritmit 1. Luento 3 Ti Timo Männikkö

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

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin peruskurssien laaja oppimäärä

Osoitin ja viittaus C++:ssa

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

Ohjelmoinnin perusteet Y Python

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

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin peruskurssien laaja oppimäärä

Lisää pysähtymisaiheisia ongelmia

13. Loogiset operaatiot 13.1

TIE Principles of Programming Languages. Seminaariesityksen essee. Ryhmä 18: Heidi Vulli, Joni Heikkilä

811120P Diskreetit rakenteet

Matematiikan tukikurssi, kurssikerta 1

811120P Diskreetit rakenteet

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Matematiikan johdantokurssi, syksy 2016 Harjoitus 11, ratkaisuista

Prolog kielenä Periaatteet Yhteenveto. Prolog. Toni ja Laura Fadjukoff. 9. joulukuuta 2010

Chomskyn hierarkia. tyyppi 0 on juuri esitelty (ja esitellään kohta lisää) tyypit 2 ja 3 kurssilla Ohjelmoinnin ja laskennan perusmallit

Funktionaalinen ohjelmointi

Perinteiset tietokoneohjelmat alkavat pääohjelmasta, c:ssä main(), jossa edetään rivi riviltä ja käsky käskyltä.

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

Tutoriaaliläsnäoloista

Ohjelmoinnin peruskurssien laaja oppimäärä

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

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

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

Clojure, funktionaalinen Lisp murre

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

811120P Diskreetit rakenteet

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

1. Olio-ohjelmointi 1.1

ITKP102 Ohjelmointi 1 (6 op)

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

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

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

3. Muuttujat ja operaatiot 3.1

13. Loogiset operaatiot 13.1

Tietueet. Tietueiden määrittely

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

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

Java-kielen perusteet

Johdatus matematiikkaan

Python-ohjelmointi Harjoitus 2

Harjoitus 1 -- Ratkaisut

T Syksy 2004 Logiikka tietotekniikassa: perusteet Laskuharjoitus 7 (opetusmoniste, kappaleet )

Tietotyypit ja operaattorit

Luku 3. Listankäsittelyä. 3.1 Listat

Matematiikan tukikurssi

Haskell ohjelmointikielen tyyppijärjestelmä

etunimi, sukunimi ja opiskelijanumero ja näillä

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op)

MS-A010{3,4} (ELEC*) Differentiaali- ja integraalilaskenta 1 Luento 3: Jatkuvuus

Java-kielen perusteet

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

8. Kieliopit ja kielet

Zeon PDF Driver Trial

Vaihtoehtoinen tapa määritellä funktioita f : N R on

Ohjelmoinnin perusteet Y Python

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Java-kielen perusteita

815338A Ohjelmointikielten periaatteet

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

Rekursio. Funktio f : N R määritellään yleensä antamalla lauseke funktion arvolle f (n). Vaihtoehtoinen tapa määritellä funktioita f : N R on

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

Johdatus Ohjelmointiin

Ohjelmoinnin peruskurssien laaja oppimäärä

Transkriptio:

Funktionaalinen ohjelmointi Aiemmin on käsitelty nykyohjelmoinnin suosituimpia ohjelmointiparadigmoja: imperatiivista ohjelmointia ja olio-ohjelmointia. Tässä osassa perehdytään funktionaaliseen ohjelmointiin vaihtoehtona imperatiiviselle ohjelmoinnille; pääasiallisena lähteenä toimii Sebestan ([Seb]) luku 15. Maarit Harsu käsittelee vastaavia asioita teoksensa [Har] luvussa 13. Myöhemmin tarkastellaan logiikkaohjelmointia; näitä molempia kutsutaan joskus yhteisesti deklaratiiviseksi ohjelmoinniksi. Joissakin lähteissä deklaratiivinen ohjelmointi tarkoittaa pelkästään logiikkaohjelmointia (ks. esim [Seb] kappale 16.1). Vielä nykyäänkin yleisimpien, imperatiivisten ohjelmointikielten suunnittelu perustuu vallitsevan tietokonearkkitehtuurin eli von Neumannin arkkitehtuurin varaan. Tällaisten kielten tunnusomaisia piirteitä ovat fyysisiin muistipaikkoihin sidottujen muuttujien käyttö, sijoituslause ja iteratiivisen toiston tehokas soveltaminen. Tällöin ohjelmoinnin olennaisimmaksi piirteeksi muodostuu algoritmien ja tietorakenteiden suunnittelu ratkaisemaan ohjelmointiongelma. Funktionaalisen ohjelmoinnin perusidea on luopua komennoista ja suorittaa kaikki tarvittavat operaatiot lausekkeita käyttämällä, erityisesti funktioita toistuvasti soveltamalla. Funktionaalinen ohjelmointi pohjautuu vahvasti laskennan teoriaan. Samaan aikaan (vuonna 1936) Alan Turingin määritellessä Turingin koneen, jota käytetään tietojenkäsittelyn teoriassa tietokoneen abstraktina mallina, loi Stephen Kleene rekursiivisten funktioiden teorian. Voidaan osoittaa, että Turingin koneen ja Kleenen mallin laskennallinen voima on täsmälleen sama (ks. esimerkiksi [Dav], ss. 166-169). Funktionaalinen ohjelmointi perustuu matemaattisten funktioiden ja korkeamman asteen funktioiden (higher order functions) soveltamiseen. Matemaattinen funktio on kuvaus funktion määrittelyjoukolta funktion arvojoukolle. Kuvaus voidaan määritellä luettelemalla funktion arvot tai jonkin lausekkeen avulla, esimerkiksi määrittelemällä funktio f reaalilukujen joukolta reaalilukujen joukolle kaavalla f(x) = 3x-1. Korkeamman asteen funktioita kutsutaan myös funktionaalisiksi muodoiksi (functional forms) ja ne

voivat ottaa parametreikseen funktioita sekä myös palauttaa funktioita paluuarvonaan. Matemaattinen funktio eroaa imperatiivisen ohjelmoinnin funktiosta pääasiassa siinä, että matemaattisella funktiolla ei voi olla sivuvaikutuksia; sen ainoa toimenpide on palauttaa parametrejaan vastaava arvo, joka on aina samoilla parametrien arvoilla sama. Funktiot ovat funktionaalisen ohjelmoinnin peruselementtejä samaan tapaan kuin muuttujat imperatiivisessa ohjelmoinnissa. Tyypillinen funktionaalinen muoto on esimerkiksi funktioiden yhdistäminen (kompositio): Funktioiden f ja g yhdistetyllä funktiolla h = f g tarkoitetaan funktiota, joka saadaan, kun argumenttiin sovelletaan ensin funktiota g ja sitten tulokseen funktiota f, ts. h(x) = f(g(x)). Esimerkiksi, jos f(x) = x2 ja g(x) = 2x+1, h(x) = f g(x) = f(g(x)) = f(2x+1) = (2x+1)2 = 4x2 + 4x +1. Konstruktioksi kutsutaan funktionaalista muotoa, joka saadaan soveltamalla annetun funktiolistan jokaista funktiota yhteen argumenttiin: tuloksena on siis arvojen lista, jossa on yhtä monta alkioita kuin funktioita oli listassa. Yleensä konstruktio merkitään kirjoittamalla funktiot hakasulkeisiin, esimerkiksi funktioiden f(x) = 3x-1 ja g(x) = x 4 +2 tapauksessa [f,g](3) tuottaa tulokseksi listan (8,83). Yksittäistä funktiota voidaan myös soveltaa argumenttilistan kaikkiin arvoihin, tätä merkitään yleisesti symbolilla α. Esimerkiksi, jos f(x) = x/3, on operaation α(f,(1,2,3)) tuloksena (1/3,2/3,1). Funktionaalisen ohjelmoinnin perusta, matemaattisten funktioiden mallintaminen, johtaa tyystin erilaiseen ohjelmointityyliin kuin imperatiivisissa kielissä. Näissä kielissä lausekkeet evaluoidaan ja tulos sijoitetaan muistipaikkaan, (puhtaasti) funktionaalisissa kielissä ei käytetä muuttujia tai sijoituslauseita. Tällöin ohjelmoija ei voi käyttää imperatiivisesta ohjelmoinnista tuttuja tekniikoita, kuten iteratiivista toistoa: toisto on tehtävä rekursion avulla, on siis sovellettava funktiota toistuvasti lausekkeeseen. Ohjelmien koodista muodostuu funktioiden määrittelyjä; nämä koostuvat funktiokutsuista ja kutsujärjestystä ohjaavista ehtolauseista. Itse ohjelman toiminta koostuu funktiokutsuista. Funktionaalisen kielen funktio, kuten matemaattinenkin,

palauttaa samoilla parametreilla aina saman arvon. Tätä kutsutaan viittauksen läpinäkyvyydeksi (referential transparency). Puhtaan funktionaalisen kielen semantiikka on näistä syistä huomattavasti yksinkertaisempi kuin imperatiivisten kielten tai imperatiivisia piirteitä sisältävien funktionaalisten kielten. Funktionaalisessa kielessä on valmiiksi määriteltynä joukko primitiivisiä funktioita, joukko korkeamman asteen funktioita, joiden avulla ohjelmoija voi muodostaa uusia funktioita entisistä, funktion soveltamisoperaatio sekä rakenteita datan esittämiseksi. Yleensä primitiivisten funktioiden joukko pyritään pitämään pienenä. Vaikka funktionaalisia kieliä voidaankin kääntää, ne ovat usein tulkattavia. 1. LISP- ja Scheme-kielet Aluksi kuvataan funktionaalista ohjelmointia pääasiassa ensimmäiseksi syntyneen ja vieläkin yleisimmän funktionaalisen ohjelmointikielen, LISPin ja sen erään murteen, Schemen, avulla. John McCarthy esitteli LISPin jo vuonna 1958 MIT:n tekoälyprojektin yhteydessä. McCarthy halusi luoda kielen, jolla olisi vahva matemaattinen pohja; hän oli sitä mieltä, että rekursiivisten funktioiden teoria soveltui perustaksi paremmin kuin Turingin koneeseen perustuvat mallit, jotka olivat silloin suositumpia. Erityisesti kielen tulisi tarjota mahdollisuus listojen käsittelyyn ja soveltaa funktion käsitettä mahdollisimman laajasti; lisäksi kieleen olisi sisällytettävä korkeamman kertaluvun funktiot. Ensimmäinen LISP -tulkki syntyi puolittain vahingossa. Kun McCarthy kehitti julkaisussaan LISPin yleisfunktion eval, joka laskee minkä tahansa LISP-funktion arvon, kaksi hänen projektissaan työskennellyttä henkilöä huomasi, että implementoituna tätä funktiota voidaan käyttää LISP-tulkkina. Pian funktio kirjoitettiinkin ja ensimmäinen LISP-tulkki syntyi. Kohta tämän jälkeen kirjoitettiin myös kääntäjä, joka toteutettiin LISP-kielellä. Tämä on tiettävästi ensimmäinen kerta, kun kielen kääntäjä kirjoitetaan samalla kielellä. LISPistä on kehittynyt monia murteita, joista COMMON LISP ja Scheme

lienevät yleisimmin käytössä. Tässä esityksessä keskitytään Sebestaa ([Seb], kappaleet 15.4 ja 15.5) mukaillen Schemen ominaisuuksiin. 1.1 LISP LISPin symbolit koostuvat kirjaimista, numeroista ja eräistä sallituista erikoismerkeistä. Symbolien ohella käsitellään myös lukuja, jotka eivät ole symboleja, koska ne aina edustavat ainoastaan numeerista arvoaan. Symboleilla T ja NIL on erikoismerkitys: ensimmäinen on totuusarvo tosi ja toinen epätosi. Loogisissa lausekkeissa NIL on epätosi ja mikä tahansa siitä poikkeava arvo katsotaan todeksi. Symboleita ja lukuja kutsutaan atomeiksi (atoms); LISPin perustietotyypit ovat atomit ja listat (lists). LISPin lista on järjestetty joukko, jonka alkiot ovat atomeja tai toisia listoja. Listan rajoittimina toimivat kaarisulut ja alkioiden erottimina sanavälit. Lista voi olla myös tyhjä, jolloin se merkitään ( ) tai NIL. Atomeja ja listoja kutsutaan yhteisnimellä symboliset lausekkeet tai s-lausekkeet (s-expression). Listan ensimmäinen alkio on sen pää (head) ja kaikki loput sen häntä (tail). Myös funktiot kirjoitetaan LISPissä listamuodossa (funktio parametri1 parametri2...) Funktion nimi kirjoitetaan aina ensin, esimerkiksi operaatio 2+3 kirjoitetaan LISPissä (+ 2 3). Kaikki lausekkeet on kirjoitettava sulkujen sisään; sulkulausekkeet voivat limittyä määräämättömän pitkälle. (Jotkut ovatkin pilkallisesti väittäneet, että LISP on lyhenne sanoista "Lots of Idiotic Silly Parenthesis"). LISP sisältää joitakin ennalta määriteltyjä funktioita aritmetiikkaa ja listojen käsittelyä varten. Kun lauseke annetaan LISP -tulkille, se yrittää selvittää lausekkeen arvon laskemalla uloimman funktion kutsun argumenttien arvot vasemmalta oikealle. Joskus on tarpeen kuitenkin käsitellä lauseketta sellaisenaan, jolloin sen arvoa ei haluta laskettavan. Esimerkiksi voidaan haluta tarkastella lauseketta (- 5 4) listana eikä laskutoimituksena 5-4. Tällöin laskenta voidaan estää merkitsemällä se lainaukseksi kirjoittamalla lainausmerkki ' (tai sana QUOTE) lausekkeen eteen.

1.2 Scheme Scheme on 1970-luvun puolivälissä MIT:ssä syntynyt LISPin murre. Kieli on pieni, yksinkertainen, käyttää staattista näkyvyysalueen määräytymistä ja käsittelee funktioita ensimmäisen kertaluokan olioina (ts. funktioihin voidaan soveltaa samoja operaatioita kuin kielen muihinkin entiteetteihin). Schemen syntaksi ja semantiikka on yksinkertainen, minkä vuoksi sitä on käytetty funktionaalisen ohjelmoinnin opetuksessa. Tässä esitettävät ohjelmaesimerkit ovat pienin muutoksin myös kelvollista LISP-koodia. Schemeen on toteutettu primitiivifunktiot aritmeettisia perusoperaatioita varten. Schemessä kirjoitetaan aritmeettiset operaatiot aina prefix-notaatiolla, ts. operaattori ensin ja sitten operandit, esimerkiksi laskutoimitus 1/6 + 2/3 kirjoitetaan (+ (/ 1 6) (/2 3)) ja tuloksena saadaan 5/6 (huomaa, että tulos saadaan murtolukuna). Kielessä on myös laaja joukko funktioita numeerista laskentaa varten, esimerkiksi modulo, sin ja sqrt jakojäännöksen, sinin ja neliöjuuren laskemiseksi. Jotta ohjelmoija voisi kirjoittaa toimivia ohjelmia, kielen on annettava mahdollisuus määritellä omia funktioita. Schemessä (kuten myös LISPissä) käytetään ns. lambdalausekkeita funktioiden määrittelyyn. Idea perustuu Alonso Churchin kehittämään lambda-kalkyyliin (lambda calculus), joka tarjoaa yksinkertaisen formalismin funktioiden määrittelyyn ja niillä laskemiseen. Churchin esitysmuoto argumenttinsa neliöivälle funktiolle on λx.x 2

Schemessä vastaava kirjoitetaan (lambda (x) (* x x)) Tässä symboli lambda ilmaisee kyseessä olevan funktion määritelmän. Luonnollisesti muodollisia parametreja voi olla useita; parametrien listaa kutsutaan lambda-listaksi. Yleinen laskenta esitetään lambda-lauseen rungossa (yllä olevassa esimerkissä (* x x) ). Runko voi olla mikä tahansa Scheme-tulkin laskettavissa oleva muoto. Lambda-lause on abstrakti mekanismi funktion määrittelyä ja laskentaa varten; sen avulla saadaan täsmällinen kuvaus funktiosta, sen parametreista ja laskennasta. Tällainen lause on nimetön funktio, joka katoaa bittitaivaaseen heti kun muoto on laskettu. Näin ollen kielessä pitää vielä olla jokin mekanismi, jolla funktioon voidaan sitoa tunniste, jotta funktiota voidaan kutsua ohjelmassa. Uusien funktioiden määrittely tapahtuu funktiolla define (LISPissä vastaava funktio on defun), esimerkiksi ylläolevan esimerkin funktio voidaan määritellä (define (nelio x) (* x x) ) jolloin sitä voidaan ohjelmassa kutsua nimellä: (nelio 9) ja lauseke palauttaa arvon 81. Funktion laskenta noudattaa seuraavaa järjestystä: 1. Evaluoidaan todellisten parametrien arvot 2. Sidotaan muodolliset parametrit näihin arvoihin 3. Lasketaan lambda-lauseen runko, jonka tuloksena saatu arvo palautetaan lambda-kutsun arvona. Funktiota ei siis evaluoida ennen kuin sen parametrit on täysin evaluoitu. Tällaisen kielen sanotaan noudattavan tiukkaa semantiikkaa (strict semantics). Mikäli tällaista vaatimusta ei kielessä ole, sanotaan että kielessä on käytössä joustava semantiikka

(non-strict semantics). Funktiota define voidaan käyttää myös nimen sitomiseksi johonkin lausekkeeseen, esimerkiksi (define pii 3.14) Seuraavaksi käsitellään Schemen kontrollirakenteita eli laskennan ohjausta. Imperatiivisissa ohjelmointikielissä tarvitaan erilaisia kontrollirakenteita (haarautumisia ja toistoja) laskennan ohjausta varten. Myös funktionaalisissa kielissä on laskentaa ohjattava jonkinlaisilla rakenteilla. Schemen kontrollirakenteet muistuttavat ulkoisesti funktiokutsuja. Ohjausrakenteet esitetään sulkulausekkeina, joissa ensimmäinen termi on ohjausrakenteen nimi ja seuraavat termit ikään kuin funktion argumentteja, joihin rakennetta sovelletaan. Rakenteen laskennan tuloksena on jokin arvo kuten funktioillakin. Tässä tarkastellaan joitakin tapoja haaroittaa laskentaa. Toisto toteutetaan funktionaalisessa ohjelmoinnissa yleensä rekursion avulla, vaikka joihinkin funktionaalisiin kieliin onkin toteutettu iteratiivisia kontrollirakenteita. Predikaatiksi sanotaan funktiota, joka palauttaa totuusarvon (#t tai #f). Ehtolauseita kontrolloidaan yleensä predikaattien avulla. Kahden vaihtoehdon tapauksessa on yleensä kätevintä käyttää if -lausetta, jonka muoto on (if ehto tosi_lauseke epätosi_lauseke) Muodon arvoksi tulee lausekkeen tosi_lauseke arvo, mikäli ehto on tosi ja muussa tapauksessa lausekkeen epätosi_lauseke arvo. Esimerkiksi (if (= 1 1) 4 5) saa arvon 4 ja (if (= 1 2) 4 5)

Lauseen avulla voitaisiin määritellä esimerkiksi kertomafunktio seuraavasti: (define (kertoma n) (if (<= n 1) 1 (* n (kertoma (- n 1))))) Huomaa, että määrittely on rekursiivinen: kun funktion parametri n on suurempi kuin 1, kutsutaan funktiota parametrilla (n-1). Kun n on korkeintaan 1, palautetaan arvo 1. Monivalinta voidaan Schemessä saada aikaan cond-lauseeella, joka haarauttaa laskentaa predikaattien määrittelemien ehtojen nojalla. Lauseen muoto on seuraava: (cond (p1 a1) (p2 a2) (pn an) (else a)) missä predikaatit pi ja arvolausekkeet ai voivat olla mielivaltaisia lausekkeita. Lauseen arvo määräytyy seuraavasti: Predikaattiosia lasketaan järjestyksessä, kunnes kohdataan ensimmäinen, jonka looginen arvo on tosi, ts. #t. Tätä predikaattia vastaavan arvolausekkeen arvo palautetaan lauseen arvona. Ellei mikään predikaateista ole tosi, lauseen arvo on else-osan arvo. Ellei else-osaa ole ja kaikki ehdot ovat epätosia, lauseen arvo on määräämätön. Esimerkiksi Fibonaccin luvut laskeva funktio (fibo(0)=0, fibo(1)=1, ja fibo(n) = fibo(n-1)+fibo(n-2)) voidaan määritellä cond-lauseen avulla seuraavasti: (define (fibo n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fibo (- n 1)) (fibo (- n 2)))))) Edellä olevasta funktiosta näkyy myös, miten LISP-tyyppisissä kielissä sulkuja kertyy usein lauseiden loppuun. Symboleilla ja listoilla operoiminen ovat tyypillisiä LISP-pohjaisten kielten sovelluskohteita. Esitellään vielä Schemen listankäsittelyominaisuuksia. Schemen

perustietotyypit ovat, kuten LISPinkin, atomit ja listat; listojen perusominaisuudet ovat myös samat kuin LISPissä. Listojen konstruoimista, purkamista ja tutkimista varten Schemeen on toteutettu seuraavat primitiivifunktiot: car, cdr, cons, ja list. Näistä car ja cdr ovat listan purkufunktioita; car palauttaa argumenttina saadun listan pään ja cdr listan hännän. Funktion car paluuarvo on toisin sanoen s-lauseke ja funktion cdr lista. Näin ollen (car '(a b c d) ) palauttaa s-lausekkeen a ja (cdr '(a b c d) ) palauttaa listan (b c d) Yhden alkion listaan sovellettuna cdr palauttaa tyhjän listan. Huomaa, että listat on annettava lainauksina (lainausmerkki listan edessä), jotta tulkki ei yritä evaluoida listojen alkioita lausekkeina. Samannimisiä funktioita käytetään myös LISP-kielessä; funktioiden omalaatuiset nimet ovat peräisin historian kätköistä: Nimet ovat lyhennyksiä IBM -tietokoneen tiettyjen rekisterien nimistä (CAR = Contents of Address Register ja CDR = Contents of Decrement Register). John McCarthy käytti näitä rekistereitä listan pään ja hännän tallettamiseen. Funktio cons muodostaa uuden listan argumenttina saatuina päästä ja hännästä. Funktion ensimmäinen argumentti on siten s-lauseke ja toinen lista, jonka alkuun funktio lisää ensimmäisen lausekkeen. Paluuarvo on aina lista. Esimerkiksi (cons 'C '(A B))

palauttaa listan (C A B). Sen sijaan funktio list muodostaa listan parametreistansa, esimerkiksi (list 'C '(A B) 'D) palauttaa listan (C (A B) D). Kuten aiemmin todettiin, predikaatiksi sanotaan funktiota, joka palauttaa totuusarvon (#t tai #f). Funktiot eq?, null? ja list? ovat Schemen listojen käsittelyn alkeispredikaatit. Funktio list? palauttaa tiedon, onko sen argumentti lista vai ei, esimerkiksi (list? 'a) palauttaa arvon #f ja (list? '(a b)) arvon #t. Predikaatti eq? vertaa argumenttina saamaansa kahta symbolia ja palauttaa arvon #t mikäli ne ovat identtisiä. Muussa tapauksessa palautetaan #f. Funktio eq? rajoittaa argumenttinsa symboleiksi - funktio ei testaa olioiden yhtäläisyyttä loogisesti vaan ovatko oliot esitetty fyysisesti samoilla rakenteilla. Yleensä funktiota voidaan soveltaa myös lukuihin ja listoihin, mutta tulokset saattavat olla yllättäviä, koska esimerkiksi listat saattavat olla ulkoisesti samanlaisia mutta kuitenkin koostua fyysisesti eri listasoluista, esimerkiksi (eq? '(a b) '(a b)) palauttaa arvon #f. Sen sijaan (eq? 'a 'a) palauttaa arvon #t. Predikaatti null? kertoo, onko parametrilista tyhjä vai ei. Schemeen on toteutettu alkeisfunktioita käyttämällä useita varusfunktioita listojen käsittelyyn. Seuraavaksi esitetään kaksi esimerkkiä listoja käsittelevästä Scheme-ohjelmoinnista.

Esimerkki 1. Kirjoita funktio, joka päättelee, onko parametrina saatu alkio toisena parametrina saadun listan alkio. Paluuarvo on #t tai #f sen mukaan esiintyykö alkio listassa vai ei. Listan syvärakennetta ei tarvitse tarkastaa, ts. ei tutkita, esiintyykö alkio jossakin listan alilistassa. Ratkaisu. Ongelma voidaan ratkaista yksinkertaisella rekursiolla. Mikäli parametrina saatu lista on tyhjä, voidaan palauttaa arvo #f. Ellei lista ole tyhjä, voidaan tarkastelu jakaa kahteen osaan: tutkitaan, onko parametrina saatu alkio listan ensimmäinen jäsen (eli listan pää), jolloin paluuarvo on #t. Ellei näin ole, tutkitaan sisältyykö alkio listan häntään. Näin saadaan kirjoitettua rekursiivinen ratkaisufunktio: (define (onko_jasen x lista) (cond ((null? lista) #f) ((eq? (car lista) x) #t) (else (onko_jasen x (cdr lista))) )) Esimerkki 2. Kirjoita funktio litista, joka litistää parametrina saadun listan, ts. poistaa kaikki muut paitsi uloimmat sulut. Apuna voi käyttää Schemen varusfunktiota append, joka yhdistää parametreinaan saadut kaksi listaa. Ratkaisu. Ratkaisuideana on litistää listan pää ja häntä sekä yhdistää näin saadut listat. Täten ratkaisufunktio toteutetaan luonnollisimmin rinnakkaisella rekursiolla, jossa funktiota itseään kutsutaan kaksi kertaa. Erikoistapauksina pitää käsitellä tyhjä lista (palautetaan tyhjä lista) ja atomi (palautetaan atomista muodostettu lista): Ratkaisufunktio on seuraava: (define (litista lista) (cond ((null? lista) '() ) ((not (list? lista)) (cons lista '())) (else (append (litista (car lista)) (litista (cdr lista)))))) Ensimmäinen ehtolause siis vertaa listaa tyhjään listaan ja palauttaa tällöin tyhjän listan. Toinen lause tutkii, onko parametri atomi (ts. ei ole lista) ja muodostaa tässä tapauksessa atomin sisältävän listan. Mikäli kumpikaan näistä ehdoista ei ole voimassa, litistetään listan pää ja häntä ja yhdistetään tulokset.

2. Haskell Seuraavaksi käsiteltävä Haskell on puhtaasti funktionaalisista kielistä todennäköisesti nykyään yleisin. Nimensä kieli on saanut Haskell Brooks Curryn mukaan; Curryn tulokset matemaattisen logiikan alalla toimivat teoreettisena pohjana funktionaaliselle ohjelmoinnille. Haskellin syntaksi muistuttaa varhaisemman funktionaalisen ML-kielen syntaksia, mutta monilta osin se poikkeaa ML-kielestä merkittävästi. Yhteisiä piirteitä ovat vahva tyypitys, joka mahdollistaa staattisen tyypin tarkistuksen sekä moduulirakenne. Haskellin toteutuksia on vapaasti saatavissa, ks. esimerkiksi www.haskell.org (Haskellin kotisivu). Haskell on siis puhdas funktionaalinen kieli eikä sisällä lainkaan imperatiivisia piirteitä. Erityisesti kielessä ei ole muuttujia eikä sijoituslauseita. Haskell noudattaa joustavaa semantiikkaa, mikä mahdollistaa ns. laiskan evaluoinnin, jota. käytetään joissakin funktionaalisissa kielissä lausekkeiden arvojen määräämisessä erotuksena kaikissa imperatiivisissa ja monissa funktionaalisissakin kielissä käytettävästä innokkaasta evaluoinnista (eager evaluation). Innokkaassa evaluoinnissa funktion ja lausekkeen arvo lasketaan välittömästi sen parametrien kulloisillakin arvoilla. Laiskassa evaluoinnissa sen sijaan lausekkeen arvo evaluoidaan vasta kun sitä tarvitaan ja evaluointi tapahtuu ainoastaan kerran. Tämä mahdollistaa näennäisesti äärettömien rakenteiden kirjoittamisen. Haskellissa funktio voidaan määritellä pelkästään sen nimellä (ilman erillistä avainsanaa) käyttäen hahmontunnistusta seuraavasti. Määrittelyssä voidaan käyttää rekursiota. Alla määritellään kertoman laskeva funktio fact 0 = 1 fact n = n * fact(n-1) Funktiota voidaan kutsua ohjelmassa tulostamaan luvun 9 kertoma esimerkiksi seuraavasti

main = do print(fact 9) Funktion määrittelyssä voidaan käyttää myös ehtolauseita: kertoma(n) = if n == 0 then 1 else n*kertoma(n-1) määrittelee myös kertomafunktion. Funktio voidaan myös määritellä käyttäen eräänlaista monivalintarakennetta. Seuraava funktio laskee rekursiivisesti Fibonaccin lukuja, joista esitettiin esimerkki myös Schemen yhteydessä fibo n n == 0 = 0 n == 1 = 1 n > 1 = fibo(n-1)+fibo(n-2) Perehdytään vielä Haskellin listojenkäsittelyn ominaisuuksiin. Listat esitetään hakasulkujen sisällä alkiot pilkulla toisistaan erotettuna, esimerkiksi suunnat = [ pohjoinen, ete1ä, itä, länsi ] listojen yhdistämisoperaattori on ++ ja kahta pistettä voidaan käyttää esittämään aritmeettista jonoa listassa, esimerkiksi operaation [1, 4, 5] ++ [2, 6, 8] tuloksena on lista [1,4,5,2,6,8] ja operaation [1, 4..16] tuloksena on lista [1,4,7,10,13,16]. Kaksoispistettä (:) voidaan käyttää merkitsemään osaa listasta operaation, joten listan pituuden palauttava funktio voitaisiin kirjoittaa rekursiivisesti hahmontunnistusta käyttäen Haskellissa seuraavalla tavalla len [] = 0 len(x:xs) = 1 + len(xs)

Jos siis parametri lista on tyhjä, palautetaan arvo nolla. Muuten jaetaan lista päähän ja häntään ja palautetaan 1+hännän pituus. Haskellissa voidaan käyttää ns. listakehitelmiä (list comprehension) listojen määrittelyyn. Listakehitelmän muoto on seuraava [runko määreet]. Esimerkiksi [n*n n <- [1..20]] tuottaisi listan jossa on lukujen 1,2,, 20 neliöt. Lista olisi siis [1,4,9,16,,400]. Laiskan laskennan avulla voidaan muodostaa myös periaatteessa äärettömiä tietorakenteita. Esimerkiksi lista, joka sisältää kaikkien parillisten kokonaislukujen neliöt voitaisiin määritellä seuraavasti: parilliset = [2, 4..] neliot = [n*n n <- parilliset] Luonnollisestikaan tietokoneessa ei voi generoida todellisuudessa ääretöntä listaa. Haskellissa lukujen neliöitä generoidaan aina listaa käytettäessä siihen saakka kuin lukuja tarvitaan. Näin esimerkiksi ohjelma main = do print(144 `elem` neliot) voidaan suorittaa ja se tulostaa arvon True, koska luku 144 on listassa (funktio `elem` tutkii onko alkio listassa). Sen sijaan main = do print(121 `elem` neliot) jäisi generoimaan listaa ikuisesti, koska 121 ei ole parillisen luvun neliö. Näin ollen tällaista listaa käsitellessä tulisi kirjoittaa itse funktio tarkistamaan onko alkio listassa. Sellainenhan on helppo tehdä, koska luvut ovat listassa suuruusjärjestyksessä. Laiskaa evaluointia voidaan hyödyntää monin tavoin, mutta voimakkaana ja joustavana

työkaluna se myös vaatii resursseja ja saattaa johtaa koodin hitaampaan suorittamiseen. 3. Muita funktionaalisia kieliä Aiemmin käsiteltiin LISPiä ja Schemeä; nykyisistä LISPin murteista yleisin on ehkä kuitenkin COMMON LISP, joka syntyi 1980 -luvun alkupuolella yhdistämään siihen asti kehittyneiden LISP-versioiden piirteitä. Täten COMMON LISP, päinvastoin kuin Scheme, sisältää varsin laajan kirjon piirteitä, myös imperatiivisia ominaisuuksia. COMMON LISP sallii sekä staattisen että dynaamisen määräytymisen, kun Scheme käyttää pelkästään staattista näkyvyysalueen määräytymistä. ML (MetaLanguage) on myös tunnettuja funktionaalisia kieliä, joka sisältää imperatiivisia piirteitä. Syntaksiltaan ML muistuttaa enemmän Pascalia kuin LISPin murteita. Lisäksi ML:ssä voidaan esitellä muuttujan tyyppejä, muuttujia ei kuitenkaan tarvitse esitellä vaan tyypit määräytyvät myös implisiittisesti. Kieli on vahvasti tyypitetty - jokaisen muuttujan ja lausekkeen tyyppi voidaan määrittää jo käännösaikana. Jotkut funktionaalisen ohjelmoinnin kannattajat vastustavat tätä menettelyä, mutta sillä saavutetaan myös monia etuja, esimerkiksi luotettavuus ja tehokkuus lisääntyvät. ML sisältää myös sijoituslauseen, joskin sen toiminta poikkeaa imperatiivisten kielten sijoituslauseesta. Kieli ei salli automaattisia tyyppimuunnoksia, operandien tyyppien on sovittava operaatioihin. ML:n moduuliominaisuutta voidaan käyttää abstraktien tietotyyppien toteuttamiseen; kieleen on myös toteutettu valmiita tietorakenteita. Microsoftin.NET-perheeseen kuuluu myös funktionaalinen kieli, F#. Kielen pohjautuu perustaltaan O Caml-kieleen, joka puolestaan on ML- ja Haskell-kielien jälkeläinen. F# ei ole puhtaasti funktionaalinen kieli, vaan se sisältää imperatiivisia piirteitä ja tukee olio-ohjelmointia. F#-kielessä voidaan määritellä jonoja (sequences) ja listoja (lists), jotka eroavat toisistaan siten, että jonot muodostetaan laiskasti ja listat innokkaasti. Jonot voivat siis olla äärettömiä, kun taas listat muodostetaan aina muistiin kokonaisuudessaan. Kielen funktiot muistuttavat Haskellin funktioita.

4. Imperatiivisten ja funktionaalisten kielten vertailua Funktionaalisten kielten syntaksi on yleensä hyvin yksinkertainen imperatiivisiin kieliin verrattuna, esimerkiksi LISP-kielen listarakennetta käytetään sekä ohjelman että tietorakenteiden esittämiseen. Funktionaalisten kielten semantiikkakin on yksinkertaisempaa, esimerkiksi funktioilla ei ole sivuvaikutuksia, rekursiivisten toistorakenteiden semantiikka on helpommin kuvattavissa jne. On epäselvää, onko funktionaalisilla kielillä ohjelmoiminen tuottavampaa vai tuottamattomampaa kuin imperatiivisilla kielillä. Erityisesti funktionaalisten kielten kannattajat väittävät, että niillä laaditut ohjelmat ovat huomattavasti pienempiä kuin imperatiivisten kielten ohjelmat. Tästä seuraisi tuottavuuden kasvu. Tosiasiassa sovelluskohde lienee olennainen seikka, joka vaikuttaa sovelluksen kokoon. Mikäli sovellus sopii funktionaaliseen paradigmaan, väite saattaa päteä. Joissakin tapauksissa funktionaalisesti laadittu ohjelma voi olla imperatiivista ohjelmaa laajempi (ks. [Seb], kappale 15.11). Funktionaalisten ohjelmien väitetään olevan suoritusajaltaan heikompia kuin imperatiivisten ohjelmien. Mikäli funktionaalista ohjelmaa tulkataan, se on luonnollisesti verrattain hidas. Lähes kaikille funktionaalisille kielille on kuitenkin kehitetty kääntäjät. Tämä tietenkin tasoittaa asetelmia. Funktionaaliset ohjelmat sisältävät usein ominaisuuksia, jotka ovat omiaan hidastamaan ohjelman suoritusta. Esimerkiksi laiska evaluointi on tällainen ominaisuus. Lisäksi imperatiiviset on nimenomaan suunniteltu ohjelmointiin nykytietokoneiden von Neumann-ympäristössä. On arvioitu, että funktionaalisena ohjelma kuluttaisi keskimäärin kaksinkertaisen ajan imperatiiviseen versioon verrattuna. Useimmissa ohjelmissa tämä ei ole kovin merkittävä ero. Luettavuus on ainakin funktionaalisiin kieliin tottuneelle yleisesti funktionaalisten kielten puolella. Monesti funktionaalinen ohjelmakoodi on varsin helposti tulkittavissa jopa käytettyä ohjelmointikieltä tuntemattomalle. Imperatiivinen koodi on muuttujien

esittelyjen ja monimutkaisten kontrollirakenteiden vuoksi vaikeaselkoisempaa. Samion rinnakkaisuus on funktionaalisessa ohjelmoinnissa helpompaa funktioiden sivuvaikutusten puuttumisen takia. Näin arvioituna on vaikea päättää, miksi funktionaalisten kielten suosio on niin paljon vaatimattomampi kuin imperatiivisten kielten. Ehkä merkittävin seikka on, että suurin osa ohjelmoijista oppii ohjelmoinnin alkeet imperatiivisella kielellä. Tällöin funktionaalinen ohjelmointityyli saattaa vaikuttaa vieraalta ja vaikeaselkoiselta. Kuitenkin moniin perustaltaan imperatiivisiin kieliin on viime aikoina toteutettu funktionaalisen ohjelmoinnin ominaisuuksia. Esimerkiksi anonyymit lambda-funktiot kuuluvat nykyisin C++-, C#- ja Java-kieliin, joissa niistä käytetään yleensä nimeä lambdalausekkeet (lambda expressions). Kaupallisen ohjelmistotuotannon ulkopuolella funktionaalista ohjelmointia on sovellettu laajasti tekoälysovellusten ohjelmointiin. Tämä on edelleenkin yksi funktionaalisten kielten pääsovellusalueita; erityisesti kielet sopivat symboliseen laskentaan ja listojen käsittelyyn. Funktionaalisilla kielillä on toteutettu muun muassa matematiikkaohjelmia, luonnollista kieltä käsitteleviä ohjelmia sekä puheen ja kuvan mallinnusohjelmia. Muillakin alueilla on sovellettu funktionaalisia kieliä, esimerkiksi tekstieditori EMACS on kirjoitettu LISPillä. Myös oliotietokantojen käsittelyohjelmia sekä CAD -ohjelmia on toteutettu funktionaalisilla kielillä. Haskellilla on kirjoitettu pelejäkin. Akateemisessa maailmassa funktionaalisia kieliä on sovellettu laajasti erilaisissa tutkimusprojekteissa. Lisäksi ainakin Schemeä on käytetty funktionaalisen ohjelmoinnin opetuskielenä ja joissakin issa ohjelmoinnin ensimmäisenä opetuskielenäkin.

Lähteet [Dav] Davis, M. Tietokoneen esihistoria Leibnizista Turingiin, Art House, 2003 [Har] Harsu, Maarit. Ohjelmointikielet, Periaatteet, käsitteet, valintaperusteet, Talentum 2005. [Seb] Sebesta, Robert W. Concepts of Programming Languages 10th edition, Pearson 2013.