• Ei tuloksia

Funktionaalisen ohjelmoinnin hyödyt ja haasteet

N/A
N/A
Info
Lataa
Protected

Academic year: 2022

Jaa "Funktionaalisen ohjelmoinnin hyödyt ja haasteet"

Copied!
21
0
0

Kokoteksti

(1)

JAAKKO RINTA-FILPPULA

FUNKTIONAALISEN OHJELMOINNIN HYÖDYT JA HAAS- TEET

Kandidaatintyö

Tarkastaja: Tiina Schafeitel-Tähtinen Jätetty tarkastettavaksi 16.12.2017

(2)

I

TIIVISTELMÄ

JAAKKO RINTA-FILPPULA: Funktionaalisen ohjelmoinnin hyödyt ja haasteet Tampereen teknillinen yliopisto

Kandidaatintyö, 18 sivua joulukuu 2017

Tietotekniikan koulutusohjelma Pääaine: ohjelmistotekniikka

Tarkastajat: Tiina Schafeitel-Tähtinen Avainsanat: Funktionaalinen ohjelmointi

Funktionaalisissa ohjelmointikielissä on monia ominaisuuksia, jotka helpottavat oh- jelmien kirjoittamista sekä parantavat niiden toimintavarmuutta. Funktionaalinen ohjelmointi eroaa kuitenkin monin tavoin yleisemmin käytössä olevista proseduraa- lisista sekä olio-ohjelmointikielistä. Tämän kandidaatintyön tarkoituksena on tutkia ja esitellä funktionaalisen ohjelmoinnin tarjoamia hyötyjä sekä muutamia haasteita, joita ohjelmoija kohtaa siirtyessään funktionaaliseen ohjelmointiin.

Työssä esitellään aluksi lyhyesti yleisimpiä ohjelmointiparadigmoja: proseduraa- lista ja olio-ohjelmointia. Tämän jälkeen esitellään funktionaalista ohjelmointia, sen historiaa sekä Haskell-ohjelmointikieltä. Funktionaalisen ohjelmoinnin hyötyi- hin ja haasteisiin perehdytään kirjallisuustutkimuksen ja oman ohjelmointiprojektin kautta.

(3)

II

SISÄLLYS

1. Johdanto . . . 2

2. Ohjelmointiparadigmat . . . 3

2.1 Proseduraalinen ohjelmointi . . . 3

2.2 Olio-ohjelmointi . . . 4

3. Funktionaalinen ohjelmointi . . . 6

3.1 Historiaa . . . 6

3.2 Haskell . . . 7

3.3 Hyödyt . . . 8

3.4 Haasteet . . . 8

3.4.1 Rekursio . . . 9

3.4.2 Korkeamman asteen funktiot . . . 10

3.4.3 Sivuvaikutuksien esittäminen . . . 11

4. Ohjelmointiprojekti . . . 13

5. Yhteenveto . . . 16

Lähteet . . . 17

(4)

1

LYHENTEET JA MERKINNÄT

HTTP Hypertext Transfer Protocol, WWW-palvelinten ja -selainten käyt- tämä tiedonsiirtoprotokolla.

JSON JavaScript Object Notation, datan serialisointiin tarkoitettu yksin- kertainen ja laajasti käytössä oleva tekstiformaatti.

URL Uniform Resource Locator, web-resurssin tekstimuotoinen osoite.

(5)

2

1. JOHDANTO

Funktionaalinen ohjelmointi lupaa ohjelmoijille monia houkuttelevia ominaisuuk- sia, joiden avulla ohjelmista tulee muun muassa helpommin ymmärrettäviä ja toi- mintavarmempia. Tällaiset ominaisuudet ovatkin varmasti suurin syy opetella ja käyttää funktionaalisia ohjelmointikieliä. Funktionaalisia ohjelmointikieliä opete- taan ja käytetään kuitenkin vähemmän kuin proseduraalisia ja oliopohjaisia kieliä, ja siksi suurelle osalle ohjelmoijista funktionaalinen ohjelmointi onkin vieras aihe- alue. Tämän työn tarkoituksena on selvittää, millaisiin haasteisiin ohjelmoija tör- mää siirtyessään imperatiivisista ja oliopohjaisista ohjelmointikielistä funktionaali- siin. Mitkä asiat tulee toteuttaa funktionaalisessa ohjelmoinnissa eri tavalla? Miksi näiden uusien ajatusmallien omaksuminen saattaa olla haasteellista? Tutkimuskysy- myksiä pohditaan aikaisimpien tutkimusten sekä oman ohjelmointiprojektin kautta.

Aluksi työssä esitellään hieman proseduraalista ohjelmointiparadigmaa sekä olio- ohjelmointia. Luvussa 3 esitellään funktionaalinen ohjelmointi ja sen historiaa sekä perehdytään sen hyötyihin ja haasteisiin. Luvussa 4 tarkastellaan esiteltyjä hyötyjä ja haasteita oman ohjelmointiprojektin kautta. Lopuksi yhteenvetoluvussa kootaan yhteen tutkimuksen tulokset ja arvioidaan niiden yleisyyttä. Työn koodiesimerkit on kirjoitettu Haskell-kielellä, ellei toisin mainita.

(6)

3

2. OHJELMOINTIPARADIGMAT

Ohjelmointikieliä voidaan luokitella eri tavoilla: Jotkin kielet käännetään konekoo- diksi, kun taas toisten suoritus tapahtuu tulkkaamalla. Joissakin kielissä on käytössä staattinen tyypitys ja toisissa tyypit tarkistetaan dynaamisesti ohjelman suorituk- sen aikana. Edellä mainittujen lisäksi ohjelmointikieliä voidaan luokitella myös nii- den edustaman ohjelmointiparadigman mukaan. Ohjelmointiparadigmalla tarkoite- taan ajatusmallia, jonka mukaan ohjelmointi tapahtuu [16]. Se on lähestymistapa, joukko käytäntöjä sekä malleja, joiden kautta pyritään saavuttamaan haluttu lop- putulos [11]. Neljänä pääparadigmana voidaan pitää imperatiivista, funktionaalista, logiikkapohjaista sekä olio-ohjelmointia [16]. Termejä imperatiivinen ja proseduraa- linen ohjelmointi käytetään usein tarkoittamaan samaa asiaa. Myös tässä työssä termejä käytetään vaihdellen. Yksi ohjelmointikieli voi edustaa useampaa paradig- maa samanaikaisesti. Tässä työssä keskitytään proseduraalisen ja funktionaalisen ohjelmoinnin eroihin.

2.1 Proseduraalinen ohjelmointi

Proseduraalinen ohjelmointi on kenties monille tutuin paradigma, sillä monet suo- situt ohjelmointikielet kuten C, Java, Python ja Ruby ovat proseduraalisia kieliä.

Näistä Java, Python ja Ruby ovat myös oliopohjaisia kieliä. Proseduraalisessa ohjel- moinnissa ohjelma suoritetaan rivi riviltä siinä järjestyksessä, jossa rivit ovat. Suori- tusjärjestystä voidaan muuttaa komennoilla, kutenfor,while,gotojaif [11]. Im- peratiiviselle ohjelmoinnille on myös tyypillistä, että ohjelman globaali tila muuttuu suorituksen aikana [16]. Seuraavassa koodiesimerkissä on esitetty lukujen 1–20 sum- maaminen C-kielellä.

1 i n t sum = 0;

2

3 f o r (i n t i = 1; i <= 20; ++ i ) {

4 sum = sum + i ;

5 }

6

7 p r i n t f (" % d ", sum ); // 210

(7)

2.2. Olio-ohjelmointi 4 Rivillä 1 on alustettu muuttuja sum, johon laskennan tulos talletetaan. Tulokseen päädytään käymällä luvut 1–20 läpi yksi kerrallaan for-silmukassa ja lisäämällä ne edellisten summaan. Ohjelman tila siis muuttuu joka kierroksella. Muuttujan arvon muuttaminen on esimerkki sivuvaikutuksesta. Sivuvaikutus tarkoittaa, että arvon tuottamisen lisäksi lausekkeen suoritus muuttaa ohjelman tai järjestelmän tilaa. Seuraavassa koodiesimerkissä on toteutettu Ruby-kielellä funktio, joka ker- too annetun luvun kahdella ja palauttaa tuloksen. Funktiolla on kuitenkin myös sivuvaikutus: se ylikirjoittaa tärkeän tiedoston sisällön.

1 d e f d o u b l e _ n u m b e r ( num )

2 F i l e . o p e n (’ i m p o r t a n t - f i l e ’, ’ w ’) { | f | f . w r i t e (’ l o r e m i p s u m ’) }

3 num * 2

4 end

Funktion käyttäjän tulee olla tietoinen sen mahdollisista sivuvaikutuksista. Tämä vaikeuttaa ohjelman ymmärtämistä varsinkin, jos sivuvaikutus riippuu saman tai muiden funktioiden aiemmista kutsukerroista. Pienissä ohjelmissa tämä ei ole on- gelma, mutta mitä isommaksi koodipohja kasvaa, sitä isompi ongelma yllättävistä sivuvaikutuksista tulee. Moniin tavallisiin operaatioihin, kuten tiedoston lukemiseen, tulostamiseen ja satunnaislukujen generointiin liittyy sivuvaikutuksia, joten niitä ei voida koskaan täysin välttää.

2.2 Olio-ohjelmointi

Olio-ohjelmointi on nykyisin eniten käytetty ohjelmointiparadigma. Sen kehitti Alan Kay työstäessään Smalltalk-ohjelmointikieltä, joka julkaistiin vuonna 1970. Kaksi vuotta myöhemmin julkaistiin C-kieleen perustuva C++, joka on edelleen laajassa käytössä. Muita suosittuja olio-ohjelmointikieliä ovat muun muassa Java, Python ja C. [11]

Olio-ohjelmoinnissa keskeisessä asemassa ovatoliot, joiden avulla voidaan ryhmitellä dataa ja siihen liittyviä operaatioita. Oliot kapseloivat sisäänsä dataa, johon pääsee käsiksi ulkopuolelta vain niiden tarjoamien metodien avulla. [11] Jokainen olio on jonkinluokan instanssi. Saman luokan instansseilla on kaikilla samat luokan määrit- telemät toiminnot. Luokat voivat periytyä toisista luokista, jolloin jälkeläisellä on käytössä vanhemman data ja toiminnot. [2] Seuraavassa koodiesimerkissä on yksin- kertainen luokka, joka kuvaa autoa.

(8)

2.2. Olio-ohjelmointi 5

1 c l a s s Car {

2 p u b l i c:

3 i n t g e t S p e e d () const;

4 void s e t S p e e d (i n t n e w S p e e d );

5

6 p r i v a t e:

7 i n t s p e e d _ ;

8 };

Luokasta Car luotujen olioiden sisäistä, private-osassa määriteltyä dataa ei voi muokata suoraan. Siihen on mahdollista päästä käsiksi vain käyttämällä luokan julkisen rajapinnan (public-osa) määrittelemiä metodeja.

(9)

6

3. FUNKTIONAALINEN OHJELMOINTI

Funktionaalisessa ohjelmoinnissa ohjelman suoritus perustuu funktioiden evaluoin- tiin. Toisin kuin imperatiivisessa ohjelmoinnissa, funktionaalisessa ohjelmoinnissa ohjelmalla ei ole implisiittistä tilaa, jota funktiot voisivat muuttaa. Funktiot voivat- kin siis käsitellä vain niille parametrina annettuja arvoja. Kun imperatiivisessa ohjel- moinnissa usein määritellään miten ohjelman suoritus tapahtuu, funktionaalisessa ohjelmoinnissa puolestaan kuvataan mitä halutaan saada tulokseksi. Moderneille funktionaalisille ohjelmointikielille tyypillisiä ominaisuuksia ovat muun muassa kor- keamman asteen funktiot (engl. higher-order function), laiska evaluointi (engl. lazy evaluation) ja hahmonsovitus (engl. pattern matching). [6] Laiska evaluointi tarkoit- taa, että lausekkeiden arvoja ei lasketa ennen kuin se on täysin välttämätöntä. Tämä mahdollistaa esimerkiksi äärettömien listojen käsittelyn. Hahmonsovituksessa funk- tion määritelmä valitaan annettujen parametrien perusteella.

Funktionaalisessa ohjelmoinnissa datan käsittely on tehokasta, sillä funktioita voi- daan yhdistellä helposti. Tämän takia se soveltuukin hyvin dataintensiivisten on- gelmien ratkaisemiseen. Datan käsittely ei kuitenkaan ole ainoa käyttötarkoitus funktionaalisille ohjelmointikielille, vaan niitä voidaan käyttää kaikenlaisten oh- jelmien toteuttamiseen. Esimerkiksi Haskellille löytyy paljon valmiita kirjastoja aina web-palvelinsovellusten toteuttamisesta graafisten käyttöliittymien luomiseen, ja Clojure-kielessä voidaan käyttää olemassa olevia Java-kirjastoja.

3.1 Historiaa

Ensimmäisenä funktionaalisena ohjelmointikielenä voidaan pitää Alonzo Churchin vuonna 1932 kehittämää lambdakalkyylia. Vaikka lambdakalkyylia ei suunnitel- tukaan suoritettavaksi tietokoneella, toimi se pohjana moderneille funktionaalisille ohjelmointikielille. [6] Lambdakalkyyli on matemaattinen malli, joka formalisoi las- kettavissa olevat funktiot, ja sen avulla voidaankin esittää mikä tahansa laskettavissa oleva funktio yksinkertaisia primitiivejä ja laskusääntöjä käyttäen [18].

Ensimmäinen varsinainen funktionaalinen ohjelmointikieli oli 1950-luvulla John Mc- Carthyn kehittämä Lisp. Se kehitettiin alunperin listojen käsittelyä varten teko-

(10)

3.2. Haskell 7 älytutkimuksen tarpeisiin. Lisp ei ollut puhtaasti funktionaalinen, sillä se sisälsi muun muassa sijoitusoperaation sekä muita sivuvaikutuksen sisältäviä operaatioita.

1970-luvulla kehitetty ML-kieli toi funktionaaliseen ohjelmointiin Hindley–Milner- tyyppijärjestelmän, joka on myöhemmin otettu käyttöön kaikissa staattisesti tyypi- tetyissä funktionaalisissa ohjelmointikielissä, kuten Haskellissa. ML oli staattisesti tyypitetty kieli, jossa tyypit pääteltiin automaattisesti ilman eksplisiittisiä tyyp- pimäärittelyitä (engl. type inference). Tyyppijärjestelmä mahdollisti ohjelmoijan itse määrittelemät tietotyypit sekä polymorfiset funktiot. [6]

3.2 Haskell

Haskell on staattisesti tyypitetty, puhtaasti funktionaalinen ja laiskasti evaluoitu ohjelmointikieli [12]. Haskellin kehitys alkoi vuonna 1987 ja ensimmäinen versio julkaistiin vuonna 1990 [7]. Haskellissa funktiot ovat puhtaita (engl. pure funtion) eli niillä ei voi olla sivuvaikutuksia. Haskell-kääntäjä osaa päätellä lausekkeiden tyypit käännösaikana, joten ohjelmoijan ei tarvitse kirjoittaa niitä itse. Koska Haskellissa on käytössä paljon korkean tason abstraktioita, ovat ohjelmat usein imperatiivisia vastineitaan lyhyempiä. [12] Esimerkiksi quicksort-algoritmin toteutus on vain kuusi riviä pitkä:

1 q u i c k s o r t :: (Ord a ) => [ a ] - > [ a ]

2 q u i c k s o r t [] = []

3 q u i c k s o r t ( p : xs ) = ( q u i c k s o r t s m a l l e r ) ++ [ p ] ++ ( q u i c k s o r t g r e a t e r )

4 where

5 s m a l l e r = f i l t e r ( < p ) xs

6 g r e a t e r = f i l t e r ( >= p ) xs

Kyseinen toteutus ei ole yhtä tehokas, kuin hyvin optimoitu C-kielinen vastine. Se on kuitenkin yleisesti käytetty esimerkki Haskellin eleganttiudesta [12].

Haskellin syntaksi saattaa tuntua aluksi oudolta, joten seuraavaksi on selitetty pää- piirteissään edellisen koodiesimerkin rakenne. Ensimmäisellä rivillä on määritelty quicksort-funktion tyyppi; sen parametrit ja paluuarvo. Määrittely [a] -> [a]

tarkoittaa, että funktio ottaa parametrinaan listan arvoja ja palauttaa listan saman- tyyppisiä arvoja. Funktion paluuarvon tyyppi on nuolilla erotetun listan viimeinen osa. Rajoitus (Ord a) => rajoittaa lista-alkioiden tyypin olemaan ainoastaan jär- jestettävissä olevaa tyyppiä (Ord-tyyppiluokan instanssi). Riveillä 2–6 on määritelty funktion toteutus käyttäen hyväksi hahmonsovitusta. Hahmonsovitus tarkoittaa funktion toteutuksen valitsemista annettujen parametrien perusteella. Rivillä 2 on määritelty järjestetyn listan olevan tyhjä lista ([]), jos parametrina on annettu tyhjä lista. Rivillä 3 parametrina annettu lista on purettu listan ensimmäiseen

(11)

3.3. Hyödyt 8 alkioon p ja listan loppuosaan xs, jonka jälkeen tulos on määritelty rekursiivisesti käyttäenwhere-osassa määriteltyjä apufunktioita. Apufunktiot eivät ole näkyvissä quicksort-funktion ulkopuolella.

3.3 Hyödyt

Nimensä mukaisesti funktionaalisessa ohjelmoinnissa funktiot ovat keskeisessä ase- massa. Funktiot ovat aivan kuten mitkä tahansa muutkin arvot (engl. first-class citizen), joten niitä voidaan välittää parametreina toisille funktioille ja käyttää funk- tioiden paluuarvoina. Funktioiden käsitteleminen arvoina mahdollistaa ohjelman pilkkomisen hyvin pieniin geneerisiin osiin, joita yhdistelemällä päästään haluttuun lopputulokseen. Esimerkiksi listan järjestämiseen käytettävälle funktiolle voidaan antaa parametrina funktio, jota käytetään alkioiden vertailemiseen. Järjestämis- funktiota voidaan siten helposti käyttää erilaisten listojen järjestämiseen vain vaih- tamalla käytettävää vertailufunktiota. [8, 14]

Ohjelman logiikan seuraaminen on helpompaa kuin proseduraalisessa ohjelmoin- nissa, sillä kaikki funktiot ovat puhtaita. Haskellissa millään funktiolla ei ole koskaan sivuvaikutuksia, joten funktion arvo riippuu vain ja ainoastaan annetuista para- metreista. Tätä ominaisuutta kutsutaan viiteläpinäkyvyydeksi (engl. referential transparency). Viiteläpinäkyvyys takaa, että jos funktiota kutsutaan useamman kerran samoilla parametreilla, on tulos joka kerralla sama. Tämä tekee ohjel- man toiminnan ymmärtämisestä helppoa sekä ohjelmoijalle että kääntäjälle. [12]

Viiteläpinäkyvyys mahdollistaa myös koodin optimointeja. Esimerkiksi Haskell- kääntäjä voi jättää funktiokutsun lopputuloksesta pois, jos täysin samanlainen kutsu on jo tehty aikaisemmin, sillä se tietää varmaksi, että toisen kutsun lopputulos on sama kuin ensimmäisen [10].

Viiteläpinäkyvyyden ansiosta ohjelman jakaminen pienempiin, uudelleenkäytettäviin osiin on helppoa. Tämä vähentää toistoa ja edelleen koodin määrää. Lyhyemmät ohjelmat ovat helpommin hallittavia ja niissä on vähemmän bugeja. [12] Kun funk- tion arvo riippuu vain sille annetuista parametreista, voidaan se suorittaa milloin vain. Ohjelman suorituskykyä voidaan siis parantaa suorittamalla peräkkäisiä funk- tiokutsuja rinnakkain. [8]

3.4 Haasteet

Funktionaalisessa ohjelmoinnissa on monia ominaisuuksia, jotka tekevät ohjelmista toimintavarmempia ja helpommin ymmärrettäviä. Funktionaalinen ohjelmointi saat-

(12)

3.4. Haasteet 9 taa kuitenkin tuntua hankalalta proseduraaliseen ohjelmointiin tottuneen ohjelmoi- jan näkökulmasta. Uuden ohjelmointikielen opetteluun liittyy lähes aina uuden syntaksin opettelu. Syntaksin lisäksi uuden ohjelmointiparadigman opettelu vaatii, että ohjelmoija omaksuu uuden tavan lähestyä ongelmia. Tässä luvussa käsitellään muutamia funktionaaliselle ohjelmoinnille tyypillisiä käsitteitä, jotka saattavat olla aloittelevalle ohjelmoijalle haastavia.

Tähän työhön on valittu tarkasteltaviksi rekursio, korkeamman asteen funktiot sekä monadit. Valinta perustuu kirjoittajan omiin kokemuksiin funktionaalisen ohjel- moinnin opettelusta. Nämä konseptit tulevat kuitenkin usein esille myös muiden kokemuksissa [17, 3, 13], ja erityisesti rekursion haastavuutta on tutkittu aikaisem- min [5, 4].

3.4.1 Rekursio

Koska funktionaalisessa ohjelmoinnissa ei ole mahdollista muuttaa muuttujien ar- voja, ei proseduraalisesta ohjelmoinnista tuttuja silmukkarakenteita voida käyttää.

Funktionaalisissa ohjelmointikielissä toisto tulee toteuttaa rekursion avulla. Monissa kielissä on tarjolla funktioita, kuten map, foldl ja zip, listojen käsittelyyn, mutta nekin on toteutettu rekursion avulla. Yksinkertainen esimerkki rekursiosta on ko- konaislukulistan summan laskeminen, joka voidaan toteuttaa seuraavasti:

1 sum :: [I n t] - > I n t

2 sum [] = 0

3 sum ( n : ns ) = n + sum ns

Summausoperaatio on määritelty rekursiivisesti. Rivit 2 ja 3 määrittelevät funktion paluuarvon eri parametreilla. Rivillä 2 on määritelty rekursion lopetusehto: tyhjän listan summa on 0. Jos lista ei ole tyhjä, siirrytään riville 3, jossa listan summa on määritelty rekursiivisesti: summa on ensimmäisen alkion n ja listan loppuosan ns summa.

Rekursion ymmärtäminen on kuitenkin vaikeaa proseduraalista ohjelmointia opiskel- leille. Rekursiota opetetaan ohjelmoinnin peruskursseilla yleensä melko myöhäisessä vaiheessa. Opetus on konkreettista ja perustuu rekursion suorituksen ymmärtämi- seen, jolloin abstraktimpi ongelman rekursiivinen hahmottaminen jää vähemmälle huomiolle. [4] Rekursiivisia funktioita määriteltäessä on tärkeää määritellä rekursi- olle lopetusehto, jotta suoritus ei jatku loputtomiin. Tällaisen lopetusehdon tun- nistaminen on monelle rekursioon tottumattomalle haastavaa. Huono lopetusehdon valinta voi myös johtaa tarpeettoman monimutkaiseen toteutukseen. [5]

(13)

3.4. Haasteet 10

3.4.2 Korkeamman asteen funktiot

Kuten luvussa 3.3 todettiin, funktiot ovat tyypillisesti samanarvoisia kuin muutkin datatyypit, mikä mahdollistaa tehokkaiden abstraktioiden luomisen. Funktioiden parametrit sekä paluuarvot voivat siis olla myös toisia funktioita. Uusien funk- tioiden määritteleminen soveltamalla osittain (engl. partial application) olemassa olevaa funktiota on tärkeä työkalu. Esimerkiksi Haskell-kielessä funktio voi ottaa maksimissaan yhden parametrin. Useita parametreja vastaanottavat funktiot otta- vatkin todellisuudessa vastaan yhden parametrin ja palauttavat funktion, joka ottaa parametrinaan yhden parametrin ja palauttaa funktion, joka ottaa parametrinaan yhden parametrin ja niin edelleen. [12] Esimerkiksimin-funktion tyyppi

1 min :: (Ord a ) => a - > a - > a

voidaan lukea: “min ottaa parametrinaan kaksi järjestettävän tyyppistä arvoa ja palauttaa yhden samantyyppisen arvon”. Sama tyyppimäärittely voidaan kirjoittaa ekvivalentisti muodossa

1 min :: (Ord a ) => a - > ( a - > a )

jolloin siitä nähdään helpommin, että todellisuudessaminottaa vastaan vain yhden parametrin ja palauttaa funktion. Kun funktiota kutsutaan seuraavasti

1 min 2 4

todellisuudessa kutsu tapahtuukin seuraavasti

1 (min 2) 4

Ensimmäiseksi siis kutsutaan min-funktiota vain parametrilla 2. Tämä palaut- taa funktion, joka vertailee parametrina annettua lukua lukuun 2 ja palauttaa pienemmän. Tätä funktiota kutsutaan parametrilla 4 ja palautetaan tulos. Funk- tioiden osittainen soveltaminen saattaa tuntua aluksi vaikealta konseptilta, mutta se kuitenkin auttaa jakamaan ohjelmaa pienempiin uudelleenkäytettäviin osiin. Esi- merkiksi funktio, joka kertoo listan kaikki luvut kahdella, voidaan määritellä seu- raavasti:

1 d o u b l e L i s t :: (Num a ) => [ a ] - > [ a ]

2 d o u b l e L i s t = map ( ( * ) 2)

Määrittelyssä on hyödynnetty funktion osittaista kutsumista kahteen kertaan. Kut- sumalla kertolaskufunktiota vain parametrilla 2 luodaan funktio, joka kertoo paramet- rina annetun luvun kahdella. Funktiota map kutsutaan antaen parametrina tämä

(14)

3.4. Haasteet 11 funktio, jolloin se palauttaa funktion, joka kertoo kaikki annetun listan luvut kahdella.

(Lausekkeen (*) 2 ympärillä olevat sulut voidaan jättää pois käyttämällä funk- tionsoveltamisoperaattoria $, joka olisi kenties Haskellille tyypillisempi tapa. Sulut on jätetty esimerkkiin selvyyden vuoksi.) Ilman funktion osittaista soveltamista doubleList-funktion määrittely voisi näyttää seuraavalta:

1 d o u b l e L i s t xs = map (\ x - > 2 * x ) xs

Funktion tyyppimäärittely on jätetty toistamatta. Määrittely on hieman pidempi kuin osittaista soveltamista käytettäessä. Parametrina annettu listaxs on tarpeet- tomasti kirjoitettu auki, ja myös kertolaskuoperaatio on avattu lambdafunktioksi.

Esimerkin mukainen toteutus saattaa tuntua luontevammalta funktionaalista ohjel- mointia aloittelevalle.

3.4.3 Sivuvaikutuksien esittäminen

Kuten aikaisemmin on todettu, puhtailla funktioilla ei voi olla sivuvaikutuksia. Sivu- vaikutuksia tarvitaan kuitenkin moniin tavallisiin operaatioihin, jotka käsittelevät jotain ohjelman ulkopuolisia resursseja. Tällaisia operaatioita ovat esimerkiksi teks- tin tulostus tai levyllä olevan tiedoston lukeminen. Tarvitaan siis jokin tapa esittää sivuvaikutuksia, jotta funktionaalisella ohjelmointikielellä voidaan toteuttaa käytän- nöllisiä ohjelmia. Seuraavassa perehdytään hieman Haskellin tapaan esittää sivu- vaikutuksia.

Haskellissa sivuvaikutusten esittäminen on toteutettu monadien jaIO a-tietotyypin avulla. IO aon tietotyyppi, joka kuvaa jotakin toimintoa, joka suoritettaessa aiheut- taa sivuvaikutuksen ja tuottaaa-tyyppisen tuloksen [9]. Esimerkiksi tiedostonluku- funktion tietotyyppi

1 r e a d F i l e :: FilePath - > IO S t r i n g

tarkoittaa, että kun sitä kutsutaan, se palauttaa toiminnon, joka suoritettaessa tuot- taa tuloksenaan merkkijonon, tässä tapauksessa tiedoston sisällön. Itse funktion kutsuminen ei siis vielä aiheuta sivuvaikutuksia, sillä se vain palauttaa toimin- non, jolla on sivuvaikutuksia. IO-toimintoja voi Haskellissa suorittaa vain main- funktio. [9]

IO-tietotyypin taustalla on monadi. Monadit ovat tapa liittää arvoon jokin kon- teksti sekä soveltaa funktioita näille arvoille konteksti huomioon ottaen [12]. Puh- taasti funktionaalisissa ohjelmointikielissä kaiken datan välityksen täytyy tapah- tua eksplisiittisesti, sillä sivuvaikutuksia ei sallita. Tällä on, kuten luvussa 3.3

(15)

3.4. Haasteet 12 todettiin, hyviä puolia, mutta se saattaa myös toisinaan vaikeuttaa ohjelman toteu- tusta. Esimerkiksi, jos haluttaisiin pitää kirjaa jonkin operaation suorituskertojen lukumäärästä, tulisi tämän arvo välittää ja palauttaa aina eksplisiittisesti funktiosta toiseen. Proseduraalisessa ohjelmointikielessä laskuri voisi olla globaali muuttuja, jonka arvoa muutetaan tarvittaessa. Monadit mahdollistavat muun muassa arvo- jen kuljettamisen funktiosta toiseen. [20] HaskellissaIO a on monadi, jonka avulla ohjelman ulkopuolista tilaa kuljetetaan ohjelman suorituksen mukana. Ohjelma saa tilan main-funktiota kutsuttaessa, jonka jälkeen se kulkee IO-toimintojen mukana läpi ohjelman. [10]

Monadien avulla voidaan ketjuttaa operaatioita, jotka saattavat tuottaa virheitä il- man, että edellisen operaation virheellisyyttä tarvitsee erikseen tarkistaa [20]. Mona- dit ovat hyödyllisiä työkaluja funktionaalisessa ohjelmoinnissa, ja niiden ymmärtämi- nen helpottaa muun muassa io-operaatioiden käyttämistä. Erilaisia oppaita, jotka pyrkivät selittämään monadien käsitettä ja käyttöä, on internetissä paljon [15], joten se vaikuttaisi olevan monille haastavaa. Tämä johtuu todennäköisesti siitä, että vastaavaa rakennetta ei tavallisesti ole olemassa proseduraalisessa tai olio- ohjelmoinnissa, sillä sille ei ole tarvetta.

(16)

13

4. OHJELMOINTIPROJEKTI

Ohjelmointiprojektina toteutettiin Haskellilla web-palvelinsovellus, jolta voidaan ky- syä TTY:n ruokaloiden ruokalistat valitulle päivälle. Ohjelmalla on yksinkertainen HTTP-rajapinta, jonka kautta ruokalistat palautetaan JSON-muodossa. Ruokalis- tan hakeminen tapahtuu lähettämällä HTTP GET -pyyntö palvelimelle polkuun /YYYY-MM-DD, jossa YYYY-MM-DD on haluttu päivämäärä ISO-8601 -muodossa. Esi- merkiksi marraskuun 21. päivän 2017 ruokalistat saataisiin pyynnöllä

1 GET h t t p :// < osoite > / 2 0 1 7 - 1 1 - 2 1 /

Ruokalistojen tiedot haetaan ravintoloiden omista rajapinnoista ja muokataan yhte- näiseen muotoon. Ohjelman toteutus voidaan jakaa kahteen osaan: HTTP-pyyntöjen käsittelyyn sekä ruokalistadatan hakemiseen ja muokkaamiseen. HTTP-pyyntöjen käsittely hoitaa halutun päivämäärän parsimisen annetusta URL:stä. Kun päivä- määrä on tiedossa, voidaan kutsua funktioita, joilla ruokalistat haetaan. Jokaista ravintoloitsijaa varten on oma moduulinsa, jossa on toteutettu ruokalistojen hakemi- nen kyseisen ravintoloitsijan rajapinnasta. Näiden moduulien rajapintoina toimivat uudet tietotyypit, jotka toteuttavat tyyppiluokan

1 c l a s s F o o d S e r v i c e a where

2 g e t R e s t a u r a n t s D a t a :: a - > C a m p u s -> U T C T i m e - > IO [ R e s t a u r a n t ]

HTTP-rajapinnan palauttaman datan rakenne on määritelty luomalla uudet tieto- tyypit jokaiselle JSON-objektille. Esimerkiksi ruokalaji voidaan esittää seuraavien tietotyyppien avulla:

1 data M e a l C o n t e n t = M e a l C o n t e n t { n a m e :: S t r i n g

2 , d i e t s :: [S t r i n g]

3 } d e r i v i n g ( Generic , Show)

4

5 data M e a l = M e a l { n a m e :: S t r i n g

6 , p r i c e s :: [S t r i n g]

7 , c o n t e n t s :: [ M e a l C o n t e n t ]

8 } d e r i v i n g ( Generic , Show)

Vastaavalla tavalla on määritelty eri rajapinnoista saatavien ruokalistatietojen for- maatit. Näiden parsiminen JSON-formaatista sekä vastauksen enkoodaminen JSON-

(17)

4. Ohjelmointiprojekti 14 muotoon on yksinkertaista aeson-kirjaston [1] avulla. Geneerisyyden ansiostaFromJSON- jaToJSON-tyyppiluokkien oletustoteutukset ovat riittävät parsimisen ja enkoodauk- sen toteuttamiseen, jolloin edellämainituista rakenteista saadaan JSON-muotoon muutettavia lisäämällä rivit

1 i n s t a n c e T o J S O N M e a l C o n t e n t

2 i n s t a n c e T o J S O N M ea l

Kun data on muutettu JSON-muodosta itsemääriteltyihin tietotyyppeihin, on sitä helppo käsitellä. Funktioiden käsittely arvoina tekee listojen käsittelystä miellyt- tävää. Listojen suodattaminen ja erilaisten datamuunnosten tekeminen listan al- kioille on map- ja filter-funktioiden avulla helppoa ja ilmaisuvoimaisempaa kuin vastaavilla silmukkarakenteella. Proseduraalisessa ohjelmoinnissa nämä muunnokset jouduttaisiin tekemään esimerkiksi for-silmukoiden avulla. Jos funktioita ei voitaisi välittää parametreina toisille funktioille, tulisi koodiin paljon toistoa, sillä lähes samanlaisia for-silmukoita jouduttaisiin toistamaan useassa kohdassa.

HTTP-pyyntöjen käsittely tapahtuu ohjelman main-funktiossa. Pyyntöjen käsit- telyyn valittiin käytettäväksi scotty-kirjasto [19], sillä se vaikutti todella yksinker- taiselta käyttää. Seuraavassa koodiesimerkissä on ohjelman main-funktio kokonai- suudessaan

1 m a i n = s c o t t y 3 0 0 0 $

2 get " /: d a t e " $ do

3 d a t e S t r i n g < - p a r a m " d a t e "

4 l e t d a t e = i s o T o U T C T i m e d a t e S t r i n g

5

6 s o d e x o D a t a < - l i f t I O $ g e t R e s t a u r a n t s D a t a S o d e x o TUT d a t e

7 f a z e r D a t a < - l i f t I O $ g e t R e s t a u r a n t s D a t a F a z e r TUT d a t e

8 j u v e n e s D a t a < - l i f t I O $ g e t R e s t a u r a n t s D a t a J u v e n e s TUT d at e

9

10 l e t a l l D a t a = concat [ s o d e x o D a t a , f a z e r D a t a , j u v e n e s D a t a ]

11 r e s p o n s e = A P I R e s p o n s e { r e s t a u r a n t s = a l l D a t a }

12

13 j s o n $ r e s p o n s e

Ohjelma 4.1 Toteutetun ohjelman main-funktio

Projektissa ei ollut tarvetta kirjoittaa itse rekursiivisia funktioita. Suurimmat vai- keudet toteutuksessa olivat IO-tyyppien käsittelyssä. Työssä onnistuttiin kuitenkin melko hyvin eristämään niiden käsittely vain pieneen osaan funktioista. Loput funk- tioista ovat täysin puhtaita. Funktioiden osittaisen soveltamisen mahdollisuus unoh- tui useaan kertaan, ja joistakin apufunktioista tuli aluksi tarpeettoman monimutkai- sia. Projektin edetessä tämä väheni kuitenkin huomattavasti. Kokonaisuudessaan

(18)

4. Ohjelmointiprojekti 15 projektin toteutus funktionaalisella ohjelmointikiellä tuntui mielekkäältä. Haskellin vahva tyypitys tuo varmuutta, sillä jos ohjelma kääntyy, se todennäköisesti myös toimii oikein, eikä kaadu.

(19)

16

5. YHTEENVETO

Funktionaalinen ohjelmointi tarjoaa paljon hyödyllisiä ominaisuuksia. Prosedu- raalisesta ohjelmoinnista funktionaaliseen ohjelmointiin siirtyminen ei kuitenkaan ole täysin vaivatonta. Funktionaaliseen ohjelmointiin kuuluu olennaisena osana ratkaisumalleja ja rakenteita, joita ei proseduraalisessa ja olio-ohjelmoinnissa käytetä yhtä usein. Tähän työhön valittiin käsiteltäväksi kolme funktionaalisessa ohjelmoin- nissa haastavaa aihetta: rekursio, korkeamman asteen funktiot sekä sivuvaikutusten esittäminen.

Rekursio on monille varsinkin aluksi hankala käsite, sillä sen osuus monilla ohjel- mointikursseilla jää vähäiseksi. Opetus usein myös keskittyy enemmän rekursion suorituksen ymmärtämiseen kuin ongelman rekursiivisen ratkaisun hahmottamiseen.

Funktioiden välittäminen parametreina toisille funktioille ei ole mahdollista kaikissa proseduraalisissa ohjelmointikielissä, joten korkeamman asteen funktiot saattavat tuntua vaikeilta funktionaalista ohjelmointia aloittelevalle. Samoin funktion osit- tainen soveltaminen on tyypillisesti vain funktionaalisten ohjelmointikielten omi- naisuus. Funktioiden puhtaudesta johtuen sivuvaikutusten esittämistä varten tulee funktionaalisessa ohjelmoinnissa olla mekanismeja, kuten monadit, joita ei ole prose- duraalisissa ohjelmointikielissä. Tällaisten täysin uusien konseptien opetteleminen ja sisäistäminen on usein vaativaa.

Työhön valitut kolme aihetta eivät varmastikaan ole ainoat haasteet funktionaa- lisessa ohjelmoinnissa. Kandidaatintyön laajuuden takia rajaus oli kuitenkin pi- dettävä kohtalaisen suppeana, ja valitut aiheet ovat kirjoittajan mielestä tärkeitä.

Samoja aiheita on käsitelty myös muiden kirjoittamissa teksteissä, joten työn tulok- set ovat ainakin jossain määrin yleistettävissä.

(20)

17

LÄHTEET

[1] aeson: Fast JSON parsing and encoding Saatavissa (viitattu 27.11.2017):

https://hackage.haskell.org/package/aeson.

[2] T. Budd, An introduction to object-oriented programming, 3rd, Addison- Wesley, Boston, 2002.

[3] D. Fayram, Functional Programming Is Hard, That’s Why It’s Good, Oct.

2011 Saatavissa (viitattu 4.12.2017): http://dave.fayr.am/posts/2011-08- 19-lets-go-shopping.html.

[4] D. Ginat, E. Shifroni, Teaching Recursion in a Procedural Environment — How Much Should We Emphasize the Computing Model?, The Proceedings of the Thirtieth SIGCSE Technical Symposium on Computer Science Education, SIGCSE ’99, ACM, New Orleans, Louisiana, USA, 1999, pp. 127–131.

[5] B. Haberman, H. Averbuch, The Case of Base Cases: Why Are They So Diffi- cult to Recognize? Student Difficulties with Recursion, SIGCSE Bull., Vol. 34, Iss. 3, June 2002, pp. 84–88.

[6] P. Hudak, Conception, evolution, and application of functional programming languages, ACM Computing Surveys (CSUR), Vol. 21, Iss. 3, 1989, pp. 359–

411.

[7] P. Hudak et al., A History of Haskell: Being Lazy with Class, Proceedings of the Third ACM SIGPLAN Conference on History of Programming Languages, HOPL III, ACM, San Diego, California, 2007, pp. 12-1–12-55.

[8] J. Hughes, Why Functional Programming Matters, The Computer Journal, Vol. 32, Iss. 2, 1989, pp. 98–107.

[9] Introduction to IO - HaskellWiki, July 2017 Saatavissa (viitattu 8.11.2017):

https://wiki.haskell.org/Introduction_to_IO.

[10] IO inside - HaskellWiki, May 2015 Saatavissa (viitattu 8.11.2017): https:

//wiki.haskell.org/IO_inside.

[11] B. Jan, Programming Language Paradigms & The Main Principles of Object- Oriented Programming, CRIS: Bulletin of the Centre for Research and Inter- disciplinary Study, Vol. 2014, Iss. 1, 2014, pp. 93–99.

[12] M. Lipovaca, I. Books24x7, Learn You a Haskell for Great Good! : A Beginner’s Guide, 1st ed., No Starch Press,US, San Francisco, 2011 Saatavissa: http : //learnyouahaskell.com/chapters.

(21)

LÄHTEET 18 [13] M. Mahto, Does Functional Programming scare you?, Mar. 2017 Saatavissa (viitattu 4.12.2017): https://hackernoon.com/does-functional-programming- scare-you-fb95552ff354.

[14] A. S. Mena, I. Books24x7, Beginning Haskell : A Project-Based Approach, 1st ed., Apress, Berkeley, CA, 2014.

[15] Monad tutorials timeline - HaskellWiki, Nov. 2015 Saatavissa (viitattu 25.11.2017): https://wiki.haskell.org/Monad_tutorials_timeline.

[16] K. Nørmark, Paradigms, July 2013 Saatavissa (viitattu 30.9.2017): http:

//people.cs.aau.dk/~normark/prog3- 03/html/notes/paradigms_themes- paradigms.html.

[17] J. Reem, Functional Programming is Black Magic, Nov. 2013 Saatavissa (vi- itattu 4.12.2017): https://medium.com/@jreem/functional-programming-is- black-magic-310084308678.

[18] R. Rojas, A Tutorial Introduction to the Lambda Calculus, CoRR, 2015.

[19] scotty: Haskell web framework inspired by Ruby’s Sinatra, using WAI and Warp Saatavissa (viitattu 28.11.2017): http://hackage.haskell.org/package/

scotty.

[20] P. Wadler, Monads for functional programming, in: J. Jeuring, E. Meijer (eds.) Advanced Functional Programming: First International Spring School on Advanced Functional Programming Techniques, Båstad, Sweden, May 24–

30, 1995 Tutorial Text, Springer Berlin Heidelberg, Berlin, Heidelberg, 1995, pp. 24–52.

Viittaukset

Outline

LIITTYVÄT TIEDOSTOT

juontavat siit¨a, ett¨a hyperboliset funktiot k¨aytt¨aytyv¨at monessa suhteessa kuten trigonometriset funktiot. Hy- perboliset funktiot

Jos seuraava suoritettava logiikka riip- puu ohjelman tilasta, voidaan tehdä korkeamman asteen funktio, joka palauttaa tarvit- tavan toiminnallisuuden toteuttavan

Funktionaalinen paradigma edellyttää heidän mukaansa sitä, että funktiot ovat ensiarvoisia elementtejä (first-class objects / citizens), joita voidaan luoda, käsitellä,

Yrityksen liiketoiminnan kehittäminen tuotteistamisen avulla on todella tärkeää, koska tässäkin tapauksessa palvelun tuotteistaminen on iso osa palvelun kokonaisuutta..

Funktiot ovat ohjelmalohkoja ilman datamuistia, johon arvot lohkoparametreista voitaisiin tallentaa. Funktiolohkot voivat käyttää globaaleja datalohkoja tietojen pysyvään

Jos sijoittajan marginaalive- roaste on 60 prosenttia, niin taulukon 3 (s. 37) mukaan jaetun voiton kokonaisveroaste oli en- tisessä järjestelmässä 64 prosenttia olettaen,

Koska EU:n itä-laajentumisen ajankohtana nykyisen EU:n ja sen uusien jäsenmaiden bila- teraalinen kauppa on käytännössä vapautettu kaupan esteistä ja EU:n

Nämä haasteet ovat aivan keskeisessä asemassa myös Suomen metsäluonnon monimuotoisuuden suojelussa, joten tarkastelen näitä kysymyksiä tästä näkökulmasta.... Tieteen