815338A Ohjelmointikielten periaatteet 2015-2016 VI Funktionaalinen ohjelmointi
Sisältö 1. Johdanto ja peruskäsitteitä 2. LISP- ja Scheme-kielet 3. Haskell 4. IO funktionaalisissa kielissä 5. Muita funktionaalisia kieliä 6. Funktionaalisten ja imperatiivisten kielten vertailua 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 2
VI.1. Johdanto ja peruskäsitteitä Vaihtoehtoja imperatiiviselle ja olioparadigmalle: Funktionaalinen ohjelmointi ja logiikkaohjelmointi Yleisnimi deklaratiivinen ohjelmointi Joissakin lähteissä vain logiikkaohjelmointi deklaratiivista Imperatiivisten ohjelmointikielten tunnusmerkit Perustuvat von Neumannin arkkitehtuuriin Fyysisiin muistipaikkoihin sidottujen muuttujien käyttö Sijoituslause Iteratiivisen toiston tehokas soveltaminen 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 3
VI.1. Johdanto ja peruskäsitteitä (2) Funktionaalinen ohjelmointi Luovutaan komennoista Suoritetaan operaatiot lausekkeita käyttämällä, erityisesti funktioita toistuvasti soveltamalla Funktiot peruselementtejä Vastaavat muuttujia imperatiivisessa ohjelmoinnissa Ei sijoituslausetta Toisto tyypillisesti rekursiolla Monissa funktionaalisissa kielissä imperatiivisia piirteitä 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 4
VI.1.1. Matemaattiset funktiot ja korkeamman asteen funktiot (higher order functions) Matemaattisella funktiolla ei voi olla sivuvaikutuksia Ero imperatiivisen ohjelmoinnin funktioon Ainoa toimenpide palauttaa argumenttia vastaava arvo, joka on aina samalla argumentin arvolla sama Korkeamman asteen funktioita kutsutaan myös funktionaalisiksi muodoiksi (functional forms) Voivat ottaa parametreikseen funktioita Paluuarvo voi olla funktio 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 5
VI.1.2. Tavallisia funktionaalisia muotoja Funktioiden yhdistäminen (kompositio) Funktioiden f ja g yhdistetty funktio h = f g; h(x) = f(g(x)) Esimerkki. Jos f(x) = x 2 ja g(x) = 2x+1, h(x) = f g(x) = f(g(x)) = f(2x+1) = (2x+1) 2 = 4x 2 + 4x +1 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 6
VI.1.2. Tavallisia funktionaalisia muotoja (2) *x++=*y++ Konstruktio saadaan soveltamalla annetun funktiolistan jokaista funktiota yhteen argumenttiin: saadaan arvojen lista, jossa on yhtä monta alkioita kuin funktioita oli listassa Esimerkki f(x) = 3x-1 ja g(x) = x 4 +2 [f,g](3) tuottaa listan (8,83) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 7
VI.1.2. Tavallisia funktionaalisia muotoja (3) Sovella kaikkiin: Sovelletaan samaa funktiota listan kaikkiin alkioihin Merkitään symbolilla α Esimerkki f(x) = x/3 -> operaation α(f,(1,2,3)) tuloksena (1/3,2/3,1) *x++=*y++ 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 8
VI.1.3 Funktionaalisen ohjelmoinnin peruskäsitteitä Viittauksen läpinäkyvyys (referential transparency): Funktio antaa samoilla parametreilla aina saman tuloksen Käytetään myös nimityksiä viite-eheys, viittausten läpikuultavuus Tiukka semantiikka (strict semantics) Funktiota ei evaluoida ennen kuin sen parametrit on täysin evaluoitu Noudatetaan yleensä imperatiivisissa ja useissa funktionaalisissa kielissä Ellei voimassa, semantiikka joustava (non-strict) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 9
VI.1.3 Funktionaalisen ohjelmoinnin peruskäsitteitä (2) Innokas evaluointi (eager evaluation) : funktion ja lausekkeen arvo lasketaan välittömästi sen parametrien kulloisillakin arvoilla Käytetään kaikissa imperativiisissa ja monissa funktionaalisissa kielissä Laiska evaluointi (lazy evaluation): lausekkeen arvo evaluoidaan vasta kun sitä tarvitaan ja evaluointi tapahtuu ainoastaan kerran Mahdollinen jos semantiikka joustava Mahdollistaa näennäisesti äärettömien rakenteiden kirjoittamisen 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 10
VI.2. LISP- ja Scheme-kielet VI.2.1 LISP *x++=*y++ John McCarthy: LISP vuonna 1958 MIT:n tekoälyprojektin yhteydessä Haluttiin kieli, jolla vahva matemaattinen pohja McCarthy: rekursiivisten funktioiden teoria soveltuu perustaksi paremmin kuin Turingin koneeseen perustuvat mallit Mahdollisuus listojen käsittelyyn Funktion käsitteen mahdollisimman laaja soveltaminen 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 11
VI.2.1 LISP (2) McCarthy: LISPin yleisfunktio eval, joka laskee minkä tahansa LISP-funktion arvon -> Ensimmäinen LISP tulkki kun huomattiin, että implementoituna voidaan käyttää tulkkina Ensimmäinen kääntäjä toteutettiin LISPillä Tiettävästi ensimmäinen kerta, kun kielen kääntäjä kirjoitetaan samalla kielellä LISPistä monia murteita, COMMON LISP ja Scheme yleisimmin käytössä Tässä esitettävät ominaisuudet koskevat myös Schemeä 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 12
VI.2.1.1 LISPin symbolit Koostuvat kirjaimista, numeroista ja eräistä sallituista erikoismerkeistä Luvut eivät ole symboleja, koska ne aina edustavat ainoastaan numeerista arvoaan Symboleilla T ja NIL erikoismerkitys: T on totuusarvo tosi ja NIL epätosi Loogisissa lausekkeissa NIL on epätosi ja mikä tahansa siitä poikkeava arvo katsotaan todeksi Schemessä totuusarvot #t ja #f Symbolit ja luvut = atomit (atoms) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 13
VI.2.1.2 LISPin perustietotyypit Atomit (atoms) ja listat (lists) Lista on järjestetty joukko, jonka alkiot ovat atomeja tai toisia listoja Listan rajoittimina toimivat kaarisulut ja alkioiden erottimina sanavälit Lista voi olla tyhjä, merkitään ( ) tai NIL Schemessä null Atomit ja listat = symboliset lausekkeet tai s- lausekkeet (s-expression) Listan ensimmäinen alkio on sen pää (head) ja kaikki loput sen häntä (tail) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 14
VI.2.1.3. LISPin funktiot Kirjoitetaan listamuodossa (funktio parametri1 parametri2...) Noudattaa tiukkaa semantiikkaa Funktion nimi kirjoitetaan aina ensin Esimerkki. Operaatio 2+3 kirjoitetaan (+ 2 3) ja 3*4+5 (+ (* 3 4) 5) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 15
VI.2.1.3. LISPin funktiot (2) QUOTE ( ) Lauseke LISP tulkille -> määrää lausekkeen arvon laskemalla uloimman funktion kutsun argumenttien arvot vasemmalta oikealle Jos halutaan lauseke sellaisenaan, merkitään lainaukseksi kirjoittamalla tai QUOTE lausekkeen eteen, esimerkiksi (+ 3 4) ei laske arvoa 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 16
VI.2.2 Scheme 1970-luvun puolivälissä syntynyt LISPin murre Pieni ja kompakti Syntaksi ja semantiikka yksinkertaisia Suunnittelussa minimalistinen idea Käytetty ohjelmoinnin opettamisessa 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 17
VI.2.2.1 Funktioiden määrittely Schemessä *x++=*y++ Funktionaalisessa kielessä oltava mahdollisuus määritellä omia funktioita Lambda-lausekkeet Käytetään Schemessä funktioiden määrittelyyn (LAMBDA (x) (* x x)) Parametrien lista = lambda-lista Yleinen laskenta lambda-lauseen rungossa (yllä (* x x)) Abstrakti mekanismi funktion määrittelyä ja laskentaa varten; nimetön funktio, häviää kun muoto on laskettu -> tarvitaan mekanismi, jolla funktioon voidaan sitoa tunniste 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 18
VI.2.2.1 Funktioiden määrittely Schemessä (2) Uusien funktioiden määrittely tapahtuu funktiolla define Esimerkki. Edellinen funktio voidaan määritellä (define nelio (x) (* x x) ) -> voidaan ohjelmassa kutsua nimellä: (nelio 5) -> 25 *x++=*y++ 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 19
VI.2.2.2 Schemen kontrollirakenteet Muistuttavat ulkoisesti funktiokutsuja Sulkulausekkeita: ensimmäinen termi ohjausrakenteen nimi; seuraavat ikään kuin funktion argumentteja, joihin rakennetta sovelletaan Rakenteen laskennan tuloksena jokin arvo kuten funktioilla 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 20
VI.2.2.2 Schemen kontrollirakenteet (2) if-lause Kahden vaihtoehdon ehtolause, muoto: (if ehto tosi-muoto epätosi-muoto) Esimerkki. Kertoma-funktio (define (kertoma n) (if (<= n 1) 1 (* n (kertoma (- n 1))))) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 21
VI.2.2.2 Schemen kontrollirakenteet (3) cond-lause: monivalintarakenne Haarauttaa laskentaa predikaattien määrittelemien ehtojen nojalla, lauseen muoto: (cond (p1 a1) (p2 a2) (pn an) (else a)) Predikaatit pi ja arvolausekkeet ai ja a mielivaltaisia muotoja Arvoksi ensimmäistä tosi-arvoista predikaattia vastaava arvo tai elseä vastaava arvo. Else ei pakollinen -> lauseen arvo voi olla epämääräinen 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 22
VI.2.2.2 Schemen kontrollirakenteet (4) Esimerkki cond-lauseesta: Fibonaccin lukuja palauttava funktio (define (fibo n) (cond ((= n 0) 0) )) ((= n 1) 1) (else (+ (fibo (- n 1)) (fibo (- n 2)))) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 23
VI.2.2.3 Schemen listankäsittelyfunktioita Listojen käsittelyn neljä alkeisfunktiota car, cdr, cons, ja list car ja cdr listan purkufunktioita car palauttaa argumenttina saadun listan pään ja cdr listan hännän -> Funktion car paluuarvo on s- lauseke ja funktion CDR paluuarvo lista Esimerkki (car (a b c d) ) -> s-lauseke a ja (cdr (a b c d) ) -> lista (b c d) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 24
VI.2.2.3 Schemen listankäsittelyfunktioita (2) cons ja list listan muodostajia cons lisää ensimmäisen argumentin toisen listaargumentin alkuun. Esimerkiksi (cons 'C '(A B)) -> lista (C A B) *x++=*y++ list muodostaa listan parametreistansa. Esimerkiksi (list 'C '(A B) 'D) -> lista (C (A B) D) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 25
VI.2.2.3 Schemen listankäsittelypredikaatteja eq?, null? ja list? Schemen listojen käsittelyn alkeispredikaatit list? : Onko parametri lista vai ei null? : Onko parametrilista tyhjä vai ei eq? : Vertailee ovatko parametrisymbolit samat. Ei toimi järkevästi listoille *x++=*y++ 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 26
VI.2.2.4 Scheme-esimerkkejä Tehtävä: Kirjoita funktio onko_jasen, joka päättelee, onko parametrina saatu alkio toisena parametrina saadun listan alkio. Paluuarvona #t tai #f. Ratkaisun idea: Jos lista on tyhjä palautetaan #f Jos alkio löytyy listan päästä #t Muuten kutsutaan samaa funktiota listan häntä parametrina -> Lista lyhenee -> Rekursio päättyy 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 27
VI.2.2.4 Scheme-esimerkkejä (2) Ratkaisufunktio: (define (onko_jasen x lista) (cond ((null? lista) #f) ((eq? (car lista) x) #t) (else (onko_jasen x (cdr lista))) )) Esimerkkejä toiminnasta: (onko_jasen 'a '(b a c)) -> #t (onko_jasen 'a '(b (a c) d)) -> #f Ei tutki sisältyykö alilistoihin 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 28
VI.2.2.4 Scheme-esimerkkejä (3) Tehtävä: 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 Ratkaisuidea: Jos lista on tyhjä, palautetaan tyhjä lista Jos parametri on atomi, siitä muodostetaan yksialkioinen lista Muuten litistetään listan ensimmäinen alkio ja listan häntä sekä yhdistetään saadut listat Sovelletaan lyheneviin listoihin -> rekursio päättyy 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 29
VI.2.2.4 Scheme-esimerkkejä (4) Ratkaisufunktio: (define (litista lista) (cond ((null? lista) '() ) ((not (list? lista)) (cons lista '())) (else (append (litista (car lista)) (litista (cdr lista)))))) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 30
VI.3. Haskell Julkaistu vuonna 1987 Nimetty matemaatikko Haskell Brooks Curryn mukaan Puhtaasti funktionaalinen kieli Ehkä yleisin käytössä Vahva tyypitys Laiska evaluointi Syntaksi muistuttaa ML-kieltä Yhteisiä piirteitä ML:n kanssa vahva tyypitys ja moduulirakenne Toteutuksia vapaasti saatavilla https://www.haskell.org/ 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 31
VI.3.1 Haskellin funktiot Voidaan määritellä hahmontunnistuksella Esimerkki. Kertoma-funktio kertoma 0 = 1 kertoma n = n * kertoma(n-1) Ehtolauseen käyttäminen määrittelyssä kertoma(n) = if n == 0 then 1 else n*kertoma(n-1) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 32
VI.3.1 Haskellin funktiot (2) Esimerkki. Vaihtoehtorakenteen käyttö Fibonacci-lukuja laskevan funktion määrittelyssä fibo n n == 0 = 0 n == 1 = 1 n > 1 = fibo(n-1)+fibo(n-2) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 33
VI.3.1 Haskellin listat Esitetään hakasulkeiden sisällä, alkiot pilkulla erotettuna suunnat = [ N, S, E, W ] Listojen yhdistäminen: operaattori ++ [1,4,5] ++ [2,6,8] -> [1,4,5,2,6,8] Kaksoispisteellä merkitään osa listasta -> Funktio joka palauttaa listan pituuden pituus [] = 0 pituus(x:xs) = 1 + pituus(xs) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 34
VI.3.1 Haskellin listat (2) Listakehitelmillä (list comprehensions) voidaan määritellä listoja. Muoto [runko määreet] Esimerkki. Lista [1,4,9,16,,400] [n*n n <- [1..20]] Laiskan evaluoinnin ansiosta voi olla potentiaalisesti ääretön: parilliset = [2, 4..] neliot = [n*n n <- parilliset] Listassa periaatteessa kaikkien parillisten lukujen neliöt. Listaa muodostetaan käytettäessä niin pitkälle kuin tarvitaan 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 35
VI.3.2 Haskell-esimerkkejä Kirjoita Haskell-funktio, joka lajittelee listan Quicksortalgoritmilla Ratkaisu: Quicksort lajittelee listan hakemalla listan ensimmäiselle alkiolle oikean paikan listassa ja lajittelemalla rekursiivisesti vasemman ja oikean puolen -> ratkaisufunktio q_sort [] = [] q_sort (x:xs) = q_sort [z z <- xs, z <= x] ++ [x] ++ q_sort [z z <- xs, z > x] 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 36
VI.3.2 Haskell-esimerkkejä (2) Kirjoita Haskell-funktio, joka tutkii, onko syötejono palindromi, kun jonosta poistetaan valkomerkit eikä tehdä eroa isojen ja pienten kirjainten välillä Ratkaisu: Käytetään apuna Haskell-funktioita isalpha, joka tutkii onko merkki kirjain ja tolower joka muuttaa kirjainmerkin pieneksi. Näiden, sekä Haskell-funktioiden filter ja map avulla puhdistetaan syöte ja tutkitaan onko puhdistettu syöte käännettynä sama kuin puhdistettu syöte 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 37
VI.3.2 Haskell-esimerkkejä (3) Palindromin testaus -- tuodaan merkkifunktiot import Data.Char (tolower,isalpha) putsaa str = filter isalpha str pieneksi str = map tolower str kaanna [] = [] kaanna (x:xs) = kaanna(xs) ++ [x] -- varsinainen testifunktio onkopalindromi str = (m_str == (kaanna m_str)) where m_str = pieneksi (putsaa str) 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 38
VI.4. IO funktionaalisissa kielissä Sivuvaikutuksien takia IO:n toteuttaminen funktionaalisessa ohjelmoinnissa periaatteessa hankalaa Yleisimmin tietovirtojen (streams) avulla Virta laiskasti evaluoitava rajoittamattoman pituinen lista: kun tarvitaan syötettä, evaluoidaan listan pää Haskellissa listojen alkiot samaa tyyppiä -> erityyppisten syötteiden käsittely ongelmallista Haskellissa monadijärjestelmä: toimintoja (actions) voidaan ketjuttaa ja liittää niihin piilotettu tila Joissakin kielissä (esim. Clean) tyyppijärjestelmää laajennettu Uniqueness types: voidaan viitata vain kerran 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 39
VI.5. Muita funktionaalisia kieliä COMMON LISP (1984) Yhdistelmä LISPin murteista -> varsin laaja Sekä dynaaminen että staattinen näkyvyysalueen määräytyminen ML (MetaLanguage, 1973) Syntaksi muistuttaa enemmän Pascalia kuin LISPiä Sisältää imperatiivisia piirteitä Tyypit voidaan esitellä tai määräytyvät implisiittisesti Vahvasti tyypitetty staattinen tyypintarkistus -> luotettavuus ja tehokkuus lisääntyvät Moduuliominaisuus -> abstraktit tietotyypit 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 40
VI.5. Muita funktionaalisia kieliä (2) F# (2005) Kuuluu MS:n.NET-perheeseen Sisältää imperatiivisia piirteitä Tukee olio-ohjelmointia Funktiot muistuttavat Haskellin funktioita 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 41
VI.6 Funktionaalisten ja imperatiivisten kielten vertailua *x++=*y++ Kielen syntaksi ja semantiikka funktionaalisissa kielissä yksinkertaisempi Kiistanalaista kumman paradigman ohjelmointi tuottavampaa Todennäköisesti riippuu sovelluskohteesta Imperatiiviset ohjelmat tehokkaampia Ero ei kaikissa sovelluksissa merkittävä Luettavuus: Funktionaalinen koodi yleensä helpommin tulkittavissa Suuri suosioero imperatiivisten kielten hyväksi ehkä tottumuskysymys? 815338A Ohjelmointikielten periaatteet, Funktionaalinen ohjelmointi 42