Ideal Learning

Hauskin tapa oppia

Selma-koiran kesäloma-ajatukset funktionaalisesta ohjelmoinnista

Funktionaalisen ohjelmoinnin lomassa voi välillä ottaa rennostikin, tuumii Selma-koira

Apple ja Google ovat ottaneet rohkeita harppauksia kohti funktionaalista ohjelmointia. Hyvä esimerkki tästä on Applen Swift-ohjelmointikieli. Vaikka Swift ei sanan varsinaisessa merkityksessä funktionaalinen kieli olekaan, on siinä kuitenkin perhepakkauksittain esimerkiksi Haskellista lainattuja ominaisuuksia. Sama pätee myös Scala-ohjelmointikieleen ja jossain määrin myös Kotliniin. Java-kielen uusista versiosta tuttu Optional-tyyppi löytyy myös Swiftistä ja Kotlinissa on vastaava käsite eri tavalla ilmaistuna. Haskellissa sama asia tunnetaan nimellä Maybe.

Merkittävin ero Haskellin ja kolmen mainitun kielen välillä on, että kolmea viimeksi mainittua käyttävä ohjelmoija ei suunnittele ratkaisuaan funktionaaliseksi. Haskell-ohjelmoija sen sijaan suunnittelee ohjelman lähtökohtaisesti ilman muuttuvaa dataa, siinä missä Swift-, Kotlin ja Java-ohjelmissa muuttuvaa dataa on.

Miksi Selma-koirankin mielestä juuri Haskell on niin hyvä työkalu funktionaalisten käsitteiden oppimiseen? Siksi, että Haskellilla ohjelmoidessa on pakko ohjelmoida funktionaalisesti – kiertotietä ei ole ja kaikki mitä opit, on ”sitä itseään”. Kun tutustut Haskelliin ja tartut mihin tahansa funktionaalisia ominaisuuksia viljalti sisältävään ohjelmointikieleen, olet heti alusta asti tilanteen herra tai rouva tai vaikka Maybe rouva tai Maybe herra – Haskellissa kaikki on mahdollista.

Eikö olisi järkevää opetella funktionaalista ohjelmointia suoraan sillä kielellä, mitä käyttää esimerkiksi työpaikalla? Ajatus on houkutteleva, mutta oppimisen kannalta vaativa. Jotta oppisi Scalaa, tulisi olla kokemusta ja näkemystä olio-ohjelmoinnista ja Scalan erityispiirteistä olio-ohjelmointikielenä ja lisäksi nähdä missä kohtaa funktionaalisen mallin ja olio-ohjelmoinnin häilyvä raja milloinkin kulkee. Jos ei tiedä, miltä funktionaalisuus ”näyttää”, niin funktionaalisten oivallusten löytäminen hybridikieltä (esim. Scala, joka tukee olio-ohjelmointia ja funktionaalista ohjelmointia) opiskellessa voi olla haastavaa. Mahdotonta se ei ole, mutta Haskell tasoittaa tietä hyvin. Haskellin opiskelu on kuin Raamatun opiskelu – investointi iäisyyteen.

Jos vielä pohtii, onko funktionaalisessa tyylissä mitään järkeä, niin pohdinnan voi yhtä hyvin jo lopettaa. Kyllä on. Seuraava esimerkki voi toimia pontimena tutustua asiaan tarkemmin . Inspiraation lähteenä alla olevissa funktioissa on ollut tämä, missä lasketaan sisäinen korkokanta nykyhetkeen diskontatuista kassavirroista. Investointilaskelmia tehneille asia on tuttu juttu. Mikä alla olevissa funktioissa sitten on niin ihmeellistä, että Selma-koirakin haukkuu asiasta monen koodirivin verran?

secant :: (Double -> Double) -> Double -> Double
secant f delta = fst $ until err update (0, 1)
    where
    update (x,y) = (x - (x - y)*(f x)/(f x - f y), x)
    err (x,y) = abs (x - y) < delta


npv :: Double -> [Double] -> Double
npv i = sum . zipWith (\t c -> c / (1 + i)**t) [0..] -- \t c järjestyksellä on väliä!    


irr::[Double]->Double->Maybe Double
irr table@(x:xs) sig    
        | null table        = Nothing
        | length table < 2  = Nothing
        | sum table <= 0    = Nothing
        | all (>=0) table   = Nothing
        | all (<=0) table   = Nothing
        | otherwise         = Just ((secant (flip npv table)) ( 0.1**sig))    
    
cashFlows::[Double]
cashFlows = [-15000,37000,30000,25000,25000,2000,10000]

r = irr cashFlows 5

1. Koodi on tyyppiturvallista ja kääntäjä osaa usein myös päätellä tyypin ilman tyyppimäärittelyä. Koirakin tietää, kuinka tärkeää on, että tyyppien turvallisuuteen voi luottaa!

2. Matemaattiset konstruktiot ovat Haskellilla helppoja esittää, sillä esimerkiksi sisäisen korkokannan laskemiseen käytetyn Newtonin-Raphsonin -menetelmän yhtälön voi kirjoittaa Haskellilla lähes sellaisenaan. Joka kyseisen menetelmän tuntee, tunnistaa sen koodista yhdellä silmäyksellä. Esitys on siis yhtäältä erittäin kompakti ja toisaalta se kuvaa juuri ongelman ratkaisua sellaisenaan. Imperatiivisella ohjelmointityylillä samaa ongelmaa ratkaistaessa päädymme silmukoihin ja väliaikaisiin muuttujiin, joista ei Selma-koirakaan ota selvää. Java-kielellä tehtyä versiota voi vertailla Haskell-versioon täällä ja Golang-kielistä versiota täällä. Koiratkaan eivät mielellään pyörittele väliaikaisia muuttujia ja mieti, kuinka monta välitulosta lopputuloksen saavuttamiseen tarvitaan. Kun koira näkee kanaherkun, se nappaa sen suuhunsa ja syö ilman turhia välivaiheita ja silmukoita laskureineen, kunhan omistaja antaa luvan.   

Kuvassa Selma-koira odottaa syöntilupaa. Huomaa, kuvassa ei näy välikaikaisia muuttujia eikä laskureita!

3. Funktioiden tyyppimäärittely kertoo lukijalle yhdellä silmäyksellä mitä funktioon ”menee” ja mitä sieltä ”tulee ulos” ja tähän on voitava luottaa.

4. Jokainen Haskellin funktio on sisäisesti aina määritelty niin, että funktiota voidaan kutsua osissa. Alla oleva on tästä hyvä esimerkki. Npv funktion flippaus antaa mahdollisuuden kutsua funktiota ensin kassavirrat sisältävällä taulukolla ja seuraavassa vaiheessa etsiä funktion avulla sopivaa sisäistä korkoa. Funktioiden soveltaminen osissa on mahdollista, koska funktiot esitetään sisäisesti aina kuritetussa muodossa (curried). Ei koiraakaan voida kouluttaa niin, että tehdään heti sikavaikeita temppuja, vaan koira on opetettava vaiheittain ja vaiheita yhdistellen. Haskellissa ja koiran kouluttamisessa on paljon samaa – molemmat ovat yksinkertaisia asioita yksinkertaisille ihmisille ja koirille. Ei ihme, että Selman suosikki on juuri Haskell.

5. Kuten secant-funktiosta nähdään imperatiivisesta tyylistä tuttuja for-silmukoita ei tarvita, niiden opettelemisesta ei funktionaalisen ohjelmoinnin kannalta ole hyötyä. Väliaikaisten muuttujien pyörittely silmukoissa ei ole tätä päivää. Kun koirat toistavat temppuja, eivät nekään mielessään laske monesko temppu on menossa.

{youtube}p6WK-JD-KIQ|600|400|{/youtube}
Selma koirakaan ei laske kuinka monta kertaa se haukkuu siiliä. Siitä huolimatta Selma-koiran haukku-funktio toimii ja sitä tuntuu olevan myös hyvin helppo laajentaa

6. Npv-funktiossa rivillä 8 näkyy hyvin funktionaalisen paradigman näppäryys paitsi komposition muodossa myös lambda-lausekkeena. Jos koiralle tulee ongelma, ei se ala pohtimaan syntyjä syviä, vaan muodostaa lambda-funktion siihen paikkaan ja toimii häntä heiluen sen mukaan. Ja jos herkkupaloissa nuukaillaan, niin fiksu koira suorittaa funktionsa vain osittain, vaikkapa kutsumalla kaksiparametrista funktiota vain ensimmäisellä parametrilla ja jatkamalla kutsumalla funktiota uudelleen, vain jos palkintoja tulee lisää.

7. Maybe Double on ilmaisu tilanteille, joissa funktio voi palauttaa joko Nothing tai Double -tyyppisen arvon. Javan ja Swiftin Optional-käsitteen avulla pyritään samaan lopputulokseen. Swiftille löytyy käsin sorvattu Either-tyyppi (https://github.com/robrix/Either) ja Either löytyy myös Scalan standardikirjastosta. Either-tyypin avulla saadaan lisätietoa ajonaikaisesta virhetilanteesta.

Hauskaa kesää!

t. Selma

Piditkö artikkelista? Suosittele sitä muillekin!

Facebook