JAAKKO RINTA-FILPPULA FUNKTIONAALISEN OHJELMOINNIN HYÖDYT JA HAAS- TEET. Kandidaatintyö

Samankaltaiset tiedostot
Haskell ohjelmointikielen tyyppijärjestelmä

ELM GROUP 04. Teemu Laakso Henrik Talarmo

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

Ruby. Tampere University of Technology Department of Pervasive Computing TIE Principles of Programming Languages

1. Olio-ohjelmointi 1.1

Tyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet. TIES341 Funktio-ohjelmointi 2 Kevät 2006

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIE Principles of Programming Languages CEYLON

Ohjelmoinnin perusteet Y Python

15. Ohjelmoinnin tekniikkaa 15.1

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

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

15. Ohjelmoinnin tekniikkaa 15.1

Ohjelmoinnin peruskurssien laaja oppimäärä

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

TIEA255 Tietotekniikan teemaseminaari ohjelmointikielet ja kehitysalustat. Antti-Juhani Kaijanaho. 16. helmikuuta 2011

Tutoriaaliläsnäoloista

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

Ohjelmoinnin peruskurssien laaja oppimäärä

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

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin jatkokurssi, kurssikoe

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet

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

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

Bootstrap / HTDP2 / Realm of Racket. Vertailu

Ohjelmoinnin peruskurssien laaja oppimäärä

Funktionaalinen ohjelmointi

Ohjelmoinnin peruskurssien laaja oppimäärä

Common Lisp Object System

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

11/20: Konepelti auki

Monadeja siellä, monadeja täällä... monadeja kaikkialla? TIES341 Funktio ohjelmointi 2 Kevät 2006

Luku 5. Monadit. 5.1 Siirrännän ongelma

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

Sisällys. JAVA-OHJELMOINTI Osa 7: Abstrakti luokka ja rajapinta. Abstraktin luokan idea. Abstrakti luokka ja metodi. Esimerkki

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet

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

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

D-OHJELMOINTIKIELI. AA-kerho, 33. Antti Uusimäki. Arto Savolainen

Ohjelmoinnin peruskurssien laaja oppimäärä

ITKP102 Ohjelmointi 1 (6 op)

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

Ohjelmoinnin peruskurssien laaja oppimäärä

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

Ohjelmoinnin peruskurssien laaja oppimäärä

Java kahdessa tunnissa. Jyry Suvilehto

Järjestelmäarkkitehtuuri (TK081702)

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

Imperatiivisten ohjelmien organisointiparadigmojen. historia

Imperatiivisten ohjelmien organisointiparadigmojen historia

Ohjelmoinnin perusteet Y Python

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

Tietorakenteet ja algoritmit

Vertailulauseet. Ehtolausekkeet. Vertailulauseet. Vertailulauseet. if-lauseke. if-lauseke. Javan perusteet 2004

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Ohjelmoinnin perusteet Y Python

Tietorakenteet ja algoritmit

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

1.3Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Ohjelmoinnin perusteet Y Python

Groovy. Samuli Haverinen, Aki Hänninen. 19. marraskuuta 2015

Imperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

ITKP102 Ohjelmointi 1 (6 op)

Luku 3. Listankäsittelyä. 3.1 Listat

Javan perusteita. Janne Käki

Funktionaalisten kielten oppimisesta ja valinnasta

TIEA341 Funktio-ohjelmointi 1, kevät 2008

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

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

Harjoitustyö: virtuaalikone

Ohjelmoinnin perusteet Y Python

Tiina Partanen. Koodaamassa Matikantunnilla

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Clojure, funktionaalinen Lisp murre

1. Omat operaatiot 1.1

Funktio-ohjelmoinnin hyödyntäminen peliohjelmoinnissa

812341A Olio-ohjelmointi Peruskäsitteet jatkoa

Ohjelmoinnin perusteet Y Python

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

A TIETORAKENTEET JA ALGORITMIT

12. Javan toistorakenteet 12.1

Osoitin ja viittaus C++:ssa

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

Groovy. Niko Jäntti Jesper Haapalinna Group 31

4. Olio-ohjelmoinista lyhyesti 4.1

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

Transkriptio:

JAAKKO RINTA-FILPPULA FUNKTIONAALISEN OHJELMOINNIN HYÖDYT JA HAAS- TEET Kandidaatintyö Tarkastaja: Tiina Schafeitel-Tähtinen Jätetty tarkastettavaksi 16.12.2017

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 ohjelmien kirjoittamista sekä parantavat niiden toimintavarmuutta. Funktionaalinen ohjelmointi eroaa kuitenkin monin tavoin yleisemmin käytössä olevista proseduraalisista 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: proseduraalista ja olio-ohjelmointia. Tämän jälkeen esitellään funktionaalista ohjelmointia, sen historiaa sekä Haskell-ohjelmointikieltä. Funktionaalisen ohjelmoinnin hyötyihin ja haasteisiin perehdytään kirjallisuustutkimuksen ja oman ohjelmointiprojektin kautta.

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

1 LYHENTEET JA MERKINNÄT HTTP JSON URL Hypertext Transfer Protocol, WWW-palvelinten ja -selainten käyttämä tiedonsiirtoprotokolla. JavaScript Object Notation, datan serialisointiin tarkoitettu yksinkertainen ja laajasti käytössä oleva tekstiformaatti. Uniform Resource Locator, web-resurssin tekstimuotoinen osoite.

2 1. JOHDANTO Funktionaalinen ohjelmointi lupaa ohjelmoijille monia houkuttelevia ominaisuuksia, joiden avulla ohjelmista tulee muun muassa helpommin ymmärrettäviä ja toimintavarmempia. Tällaiset ominaisuudet ovatkin varmasti suurin syy opetella ja käyttää funktionaalisia ohjelmointikieliä. Funktionaalisia ohjelmointikieliä opetetaan ja käytetään kuitenkin vähemmän kuin proseduraalisia ja oliopohjaisia kieliä, ja siksi suurelle osalle ohjelmoijista funktionaalinen ohjelmointi onkin vieras aihealue. Tämän työn tarkoituksena on selvittää, millaisiin haasteisiin ohjelmoija törmää siirtyessään imperatiivisista ja oliopohjaisista ohjelmointikielistä funktionaalisiin. Mitkä asiat tulee toteuttaa funktionaalisessa ohjelmoinnissa eri tavalla? Miksi näiden uusien ajatusmallien omaksuminen saattaa olla haasteellista? Tutkimuskysymyksiä pohditaan aikaisimpien tutkimusten sekä oman ohjelmointiprojektin kautta. Aluksi työssä esitellään hieman proseduraalista ohjelmointiparadigmaa sekä olioohjelmointia. 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.

3 2. OHJELMOINTIPARADIGMAT Ohjelmointikieliä voidaan luokitella eri tavoilla: Jotkin kielet käännetään konekoodiksi, kun taas toisten suoritus tapahtuu tulkkaamalla. Joissakin kielissä on käytössä staattinen tyypitys ja toisissa tyypit tarkistetaan dynaamisesti ohjelman suorituksen aikana. Edellä mainittujen lisäksi ohjelmointikieliä voidaan luokitella myös niiden edustaman ohjelmointiparadigman mukaan. Ohjelmointiparadigmalla tarkoitetaan ajatusmallia, jonka mukaan ohjelmointi tapahtuu [16]. Se on lähestymistapa, joukko käytäntöjä sekä malleja, joiden kautta pyritään saavuttamaan haluttu lopputulos [11]. Neljänä pääparadigmana voidaan pitää imperatiivista, funktionaalista, logiikkapohjaista sekä olio-ohjelmointia [16]. Termejä imperatiivinen ja proseduraalinen ohjelmointi käytetään usein tarkoittamaan samaa asiaa. Myös tässä työssä termejä käytetään vaihdellen. Yksi ohjelmointikieli voi edustaa useampaa paradigmaa 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 suositut ohjelmointikielet kuten C, Java, Python ja Ruby ovat proseduraalisia kieliä. Näistä Java, Python ja Ruby ovat myös oliopohjaisia kieliä. Proseduraalisessa ohjelmoinnissa ohjelma suoritetaan rivi riviltä siinä järjestyksessä, jossa rivit ovat. Suoritusjärjestystä voidaan muuttaa komennoilla, kuten for, while, goto ja if [11]. Imperatiiviselle ohjelmoinnille on myös tyypillistä, että ohjelman globaali tila muuttuu suorituksen aikana [16]. Seuraavassa koodiesimerkissä on esitetty lukujen 1 20 summaaminen C-kielellä. 1 int sum = 0; 2 3 for ( int i = 1; i <= 20; ++i) { 4 sum = sum + i; 5 } 6 7 printf ("%d", sum ); // 210

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 kertoo annetun luvun kahdella ja palauttaa tuloksen. Funktiolla on kuitenkin myös sivuvaikutus: se ylikirjoittaa tärkeän tiedoston sisällön. 1 def double_ number ( num ) 2 File. open ( important - file, w ) { f f. write ( lorem ipsum ) } 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 ongelma, 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 ovat oliot, 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 jonkin luokan instanssi. Saman luokan instansseilla on kaikilla samat luokan määrittelemät toiminnot. Luokat voivat periytyä toisista luokista, jolloin jälkeläisellä on käytössä vanhemman data ja toiminnot. [2] Seuraavassa koodiesimerkissä on yksinkertainen luokka, joka kuvaa autoa.

2.2. Olio-ohjelmointi 5 1 c l a s s Car { 2 public : 3 int getspeed () const ; 4 void setspeed ( i n t newspeed ); 5 6 private : 7 int speed_ ; 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.

6 3. FUNKTIONAALINEN OHJELMOINTI Funktionaalisessa ohjelmoinnissa ohjelman suoritus perustuu funktioiden evaluointiin. Toisin kuin imperatiivisessa ohjelmoinnissa, funktionaalisessa ohjelmoinnissa ohjelmalla ei ole implisiittistä tilaa, jota funktiot voisivat muuttaa. Funktiot voivatkin siis käsitellä vain niille parametrina annettuja arvoja. Kun imperatiivisessa ohjelmoinnissa usein määritellään miten ohjelman suoritus tapahtuu, funktionaalisessa ohjelmoinnissa puolestaan kuvataan mitä halutaan saada tulokseksi. Moderneille funktionaalisille ohjelmointikielille tyypillisiä ominaisuuksia ovat muun muassa korkeamman asteen funktiot (engl. higher-order function), laiska evaluointi (engl. lazy evaluation) ja hahmonsovitus (engl. pattern matching). [6] Laiska evaluointi tarkoittaa, 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 funktion määritelmä valitaan annettujen parametrien perusteella. Funktionaalisessa ohjelmoinnissa datan käsittely on tehokasta, sillä funktioita voidaan yhdistellä helposti. Tämän takia se soveltuukin hyvin dataintensiivisten ongelmien ratkaisemiseen. Datan käsittely ei kuitenkaan ole ainoa käyttötarkoitus funktionaalisille ohjelmointikielille, vaan niitä voidaan käyttää kaikenlaisten ohjelmien 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 suunniteltukaan suoritettavaksi tietokoneella, toimi se pohjana moderneille funktionaalisille ohjelmointikielille. [6] Lambdakalkyyli on matemaattinen malli, joka formalisoi laskettavissa 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-

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 Milnertyyppijärjestelmän, joka on myöhemmin otettu käyttöön kaikissa staattisesti tyypitetyissä funktionaalisissa ohjelmointikielissä, kuten Haskellissa. ML oli staattisesti tyypitetty kieli, jossa tyypit pääteltiin automaattisesti ilman eksplisiittisiä tyyppimää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 quicksort :: (Ord a) => [a] -> [a] 2 quicksort [] = [] 3 quicksort ( p: xs) = ( quicksort smaller ) ++ [ p] ++ ( quicksort greater ) 4 where 5 smaller = f i l t e r (< p) xs 6 greater = 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 samantyyppisiä arvoja. Funktion paluuarvon tyyppi on nuolilla erotetun listan viimeinen osa. Rajoitus (Ord a) => rajoittaa lista-alkioiden tyypin olemaan ainoastaan järjestettä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

3.3. Hyödyt 8 alkioon p ja listan loppuosaan xs, jonka jälkeen tulos on määritelty rekursiivisesti käyttäen where-osassa määriteltyjä apufunktioita. Apufunktiot eivät ole näkyvissä quicksort-funktion ulkopuolella. 3.3 Hyödyt Nimensä mukaisesti funktionaalisessa ohjelmoinnissa funktiot ovat keskeisessä asemassa. Funktiot ovat aivan kuten mitkä tahansa muutkin arvot (engl. first-class citizen), joten niitä voidaan välittää parametreina toisille funktioille ja käyttää funktioiden 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ämisfunktiota voidaan siten helposti käyttää erilaisten listojen järjestämiseen vain vaihtamalla käytettävää vertailufunktiota. [8, 14] Ohjelman logiikan seuraaminen on helpompaa kuin proseduraalisessa ohjelmoinnissa, sillä kaikki funktiot ovat puhtaita. Haskellissa millään funktiolla ei ole koskaan sivuvaikutuksia, joten funktion arvo riippuu vain ja ainoastaan annetuista parametreista. 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 ohjelman toiminnan ymmärtämisestä helppoa sekä ohjelmoijalle että kääntäjälle. [12] Viiteläpinäkyvyys mahdollistaa myös koodin optimointeja. Esimerkiksi Haskellkää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 funktion arvo riippuu vain sille annetuista parametreista, voidaan se suorittaa milloin vain. Ohjelman suorituskykyä voidaan siis parantaa suorittamalla peräkkäisiä funktiokutsuja rinnakkain. [8] 3.4 Haasteet Funktionaalisessa ohjelmoinnissa on monia ominaisuuksia, jotka tekevät ohjelmista toimintavarmempia ja helpommin ymmärrettäviä. Funktionaalinen ohjelmointi saat-

3.4. Haasteet 9 taa kuitenkin tuntua hankalalta proseduraaliseen ohjelmointiin tottuneen ohjelmoijan 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 ohjelmoinnin opettelusta. Nämä konseptit tulevat kuitenkin usein esille myös muiden kokemuksissa [17, 3, 13], ja erityisesti rekursion haastavuutta on tutkittu aikaisemmin [5, 4]. 3.4.1 Rekursio Koska funktionaalisessa ohjelmoinnissa ei ole mahdollista muuttaa muuttujien arvoja, 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 kokonaislukulistan summan laskeminen, joka voidaan toteuttaa seuraavasti: 1 sum :: [ Int ] -> Int 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 opiskelleille. Rekursiota opetetaan ohjelmoinnin peruskursseilla yleensä melko myöhäisessä vaiheessa. Opetus on konkreettista ja perustuu rekursion suorituksen ymmärtämiseen, jolloin abstraktimpi ongelman rekursiivinen hahmottaminen jää vähemmälle huomiolle. [4] Rekursiivisia funktioita määriteltäessä on tärkeää määritellä rekursiolle lopetusehto, jotta suoritus ei jatku loputtomiin. Tällaisen lopetusehdon tunnistaminen on monelle rekursioon tottumattomalle haastavaa. Huono lopetusehdon valinta voi myös johtaa tarpeettoman monimutkaiseen toteutukseen. [5]

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 funktioiden 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 ottavatkin todellisuudessa vastaan yhden parametrin ja palauttavat funktion, joka ottaa parametrinaan yhden parametrin ja palauttaa funktion, joka ottaa parametrinaan yhden parametrin ja niin edelleen. [12] Esimerkiksi min-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ä todellisuudessa min ottaa 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ä palauttaa funktion, joka vertailee parametrina annettua lukua lukuun 2 ja palauttaa pienemmän. Tätä funktiota kutsutaan parametrilla 4 ja palautetaan tulos. Funktioiden osittainen soveltaminen saattaa tuntua aluksi vaikealta konseptilta, mutta se kuitenkin auttaa jakamaan ohjelmaa pienempiin uudelleenkäytettäviin osiin. Esimerkiksi funktio, joka kertoo listan kaikki luvut kahdella, voidaan määritellä seuraavasti: 1 doublelist :: (Num a) => [a] -> [a] 2 doublelist = map ((*) 2) Määrittelyssä on hyödynnetty funktion osittaista kutsumista kahteen kertaan. Kutsumalla kertolaskufunktiota vain parametrilla 2 luodaan funktio, joka kertoo parametrina annetun luvun kahdella. Funktiota map kutsutaan antaen parametrina tämä

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ä funktionsoveltamisoperaattoria $, 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 doublelist 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 lista xs on tarpeettomasti kirjoitettu auki, ja myös kertolaskuoperaatio on avattu lambdafunktioksi. Esimerkin mukainen toteutus saattaa tuntua luontevammalta funktionaalista ohjelmointia aloittelevalle. 3.4.3 Sivuvaikutuksien esittäminen Kuten aikaisemmin on todettu, puhtailla funktioilla ei voi olla sivuvaikutuksia. Sivuvaikutuksia tarvitaan kuitenkin moniin tavallisiin operaatioihin, jotka käsittelevät jotain ohjelman ulkopuolisia resursseja. Tällaisia operaatioita ovat esimerkiksi tekstin tulostus tai levyllä olevan tiedoston lukeminen. Tarvitaan siis jokin tapa esittää sivuvaikutuksia, jotta funktionaalisella ohjelmointikielellä voidaan toteuttaa käytännöllisiä ohjelmia. Seuraavassa perehdytään hieman Haskellin tapaan esittää sivuvaikutuksia. Haskellissa sivuvaikutusten esittäminen on toteutettu monadien ja IO a -tietotyypin avulla. IO a on tietotyyppi, joka kuvaa jotakin toimintoa, joka suoritettaessa aiheuttaa sivuvaikutuksen ja tuottaa a-tyyppisen tuloksen [9]. Esimerkiksi tiedostonlukufunktion tietotyyppi 1 readfile :: FilePath -> IO String tarkoittaa, että kun sitä kutsutaan, se palauttaa toiminnon, joka suoritettaessa tuottaa tuloksenaan merkkijonon, tässä tapauksessa tiedoston sisällön. Itse funktion kutsuminen ei siis vielä aiheuta sivuvaikutuksia, sillä se vain palauttaa toiminnon, jolla on sivuvaikutuksia. IO-toimintoja voi Haskellissa suorittaa vain mainfunktio. [9] IO-tietotyypin taustalla on monadi. Monadit ovat tapa liittää arvoon jokin konteksti sekä soveltaa funktioita näille arvoille konteksti huomioon ottaen [12]. Puhtaasti funktionaalisissa ohjelmointikielissä kaiken datan välityksen täytyy tapahtua eksplisiittisesti, sillä sivuvaikutuksia ei sallita. Tällä on, kuten luvussa 3.3

3.4. Haasteet 12 todettiin, hyviä puolia, mutta se saattaa myös toisinaan vaikeuttaa ohjelman toteutusta. 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 arvojen kuljettamisen funktiosta toiseen. [20] Haskellissa IO 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ä ilman, että edellisen operaation virheellisyyttä tarvitsee erikseen tarkistaa [20]. Monadit ovat hyödyllisiä työkaluja funktionaalisessa ohjelmoinnissa, ja niiden ymmärtäminen 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 olioohjelmoinnissa, sillä sille ei ole tarvetta.

13 4. OHJELMOINTIPROJEKTI Ohjelmointiprojektina toteutettiin Haskellilla web-palvelinsovellus, jolta voidaan kysyä TTY:n ruokaloiden ruokalistat valitulle päivälle. Ohjelmalla on yksinkertainen HTTP-rajapinta, jonka kautta ruokalistat palautetaan JSON-muodossa. Ruokalistan 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. Esimerkiksi marraskuun 21. päivän 2017 ruokalistat saataisiin pyynnöllä 1 GET http :// < osoite >/2017-11 -21/ Ruokalistojen tiedot haetaan ravintoloiden omista rajapinnoista ja muokataan yhtenä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 hakeminen kyseisen ravintoloitsijan rajapinnasta. Näiden moduulien rajapintoina toimivat uudet tietotyypit, jotka toteuttavat tyyppiluokan 1 c l a s s FoodService a where 2 getrestaurantsdata :: a - > Campus -> UTCTime - > IO [ Restaurant ] HTTP-rajapinnan palauttaman datan rakenne on määritelty luomalla uudet tietotyypit jokaiselle JSON-objektille. Esimerkiksi ruokalaji voidaan esittää seuraavien tietotyyppien avulla: 1 data MealContent = MealContent { name :: String 2, diets :: [ String ] 3 } deriving ( Generic, Show) 4 5 data Meal = Meal { name :: String 6, prices :: [ String ] 7, contents :: [ MealContent ] 8 } deriving ( Generic, Show) Vastaavalla tavalla on määritelty eri rajapinnoista saatavien ruokalistatietojen formaatit. Näiden parsiminen JSON-formaatista sekä vastauksen enkoodaminen JSON-

4. Ohjelmointiprojekti 14 muotoon on yksinkertaista aeson-kirjaston [1] avulla. Geneerisyyden ansiosta FromJSONja ToJSON-tyyppiluokkien oletustoteutukset ovat riittävät parsimisen ja enkoodauksen toteuttamiseen, jolloin edellämainituista rakenteista saadaan JSON-muotoon muutettavia lisäämällä rivit 1 instance ToJSON MealContent 2 instance ToJSON Meal Kun data on muutettu JSON-muodosta itsemääriteltyihin tietotyyppeihin, on sitä helppo käsitellä. Funktioiden käsittely arvoina tekee listojen käsittelystä miellyttävää. Listojen suodattaminen ja erilaisten datamuunnosten tekeminen listan alkioille 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äsittelyyn valittiin käytettäväksi scotty-kirjasto [19], sillä se vaikutti todella yksinkertaiselta käyttää. Seuraavassa koodiesimerkissä on ohjelman main-funktio kokonaisuudessaan 1 main = scotty 3000 $ 2 get "/: date " $ do 3 datestring <- param " date " 4 l e t date = isotoutctime datestring 5 6 sodexodata <- liftio $ getrestaurantsdata Sodexo TUT date 7 fazerdata <- liftio $ getrestaurantsdata Fazer TUT date 8 juvenesdata <- liftio $ getrestaurantsdata Juvenes TUT date 9 10 l e t alldata = concat [ sodexodata, fazerdata, juvenesdata ] 11 response = APIResponse { restaurants = alldata } 12 13 json $ response Ohjelma 4.1 Toteutetun ohjelman main-funktio Projektissa ei ollut tarvetta kirjoittaa itse rekursiivisia funktioita. Suurimmat vaikeudet toteutuksessa olivat IO-tyyppien käsittelyssä. Työssä onnistuttiin kuitenkin melko hyvin eristämään niiden käsittely vain pieneen osaan funktioista. Loput funktioista ovat täysin puhtaita. Funktioiden osittaisen soveltamisen mahdollisuus unohtui useaan kertaan, ja joistakin apufunktioista tuli aluksi tarpeettoman monimutkaisia. Projektin edetessä tämä väheni kuitenkin huomattavasti. Kokonaisuudessaan

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.

16 5. YHTEENVETO Funktionaalinen ohjelmointi tarjoaa paljon hyödyllisiä ominaisuuksia. Proseduraalisesta 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 ohjelmoinnissa haastavaa aihetta: rekursio, korkeamman asteen funktiot sekä sivuvaikutusten esittäminen. Rekursio on monille varsinkin aluksi hankala käsite, sillä sen osuus monilla ohjelmointikursseilla 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 osittainen soveltaminen on tyypillisesti vain funktionaalisten ohjelmointikielten ominaisuus. Funktioiden puhtaudesta johtuen sivuvaikutusten esittämistä varten tulee funktionaalisessa ohjelmoinnissa olla mekanismeja, kuten monadit, joita ei ole proseduraalisissa 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 funktionaalisessa ohjelmoinnissa. Kandidaatintyön laajuuden takia rajaus oli kuitenkin pidettävä kohtalaisen suppeana, ja valitut aiheet ovat kirjoittajan mielestä tärkeitä. Samoja aiheita on käsitelty myös muiden kirjoittamissa teksteissä, joten työn tulokset ovat ainakin jossain määrin yleistettävissä.

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 Difficult 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 Interdisciplinary 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.

LÄHTEET 18 [13] M. Mahto, Does Functional Programming scare you?, Mar. 2017 Saatavissa (viitattu 4.12.2017): https://hackernoon.com/does-functional-programmingscare-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_themesparadigms.html. [17] J. Reem, Functional Programming is Black Magic, Nov. 2013 Saatavissa (viitattu 4.12.2017): https://medium.com/@jreem/functional-programming-isblack-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.