Funktionaalisesta ohjelmoinnista: PHP vs. Scala

Thursday, November 26th 2009 at 18:21 in Technology

Funktionaalinen ohjelmointi on hirveän monille teknisesti muutoin valistuneille täysin vieras käsite. Olen huomannut tämän ihan konkreetisesti käydessäni netti- ja livekeskusteluita tuttavieni kanssa. Useimpien ensimmäinen reaktio on yrittää selittää funktionaaliset käsitteet proseduraalisen ohjelmoinnin kautta. Tämä on vähän sama, kuin yrittäisi hahmottaa miltä vieras hedelmä maistuu katselemalla vain sen värejä ja muotoja televisiosta. :-)

Koska aihe kiinnostaa minua, ajattelin käsitellä tässä muutamaa funktionaalisen ohjelmoinnin hyödyllistä ominaisuutta. Argumenttini on, että nämä ominaisuudet pystyvät tekemään koodista tehokkaampaa, selkeämpää ja bugittomampaa kuin perinteinen ohjelmointi. Esimerkkikieliksi vertailussa sopivat PHP, jota koodataan useimmiten erittäin proseduraalisesti, sekä Scala, joka puolestaan on hyvin funktionaalinen kieli.

Tiedon funktionaalinen käsittely bugittomasti

Käytin jo aiemminkin esimerkkinä artikkelien luuppausta läpi tähän PHP:lle ominaiseen tyyliin:

$results = array();
for ($i = 0; $i < count($articles); $i++) {
  if ($articles[$i]->published) {
    $results[] = $articles[$i]->title;
  }
}

Ylläolevassa koodissa on _seitsemän_ toisistaan täysin irrallista asiaa, jotka voivat mennä pieleen, jos ohjelmoija typottaa tai tekee ajatusvirheen. Ne ovat:

  • Tuloslistan alustus ($results = array())
  • For-loopin alustus ($i = 0)
  • For-loopin ehdon tarkistus ($i < count($articles))
  • For-loopin laskurin inkrementointi ($i++)
  • Tietyn artikkelin ottaminen taulukosta ($articles[$i]) - kahteen eri kertaan
  • Artikkelin lisääminen tuloslistan perään ($results[] = ...)

Ratkaisevaa tässä on se, että ohjelmointikieli ei pysty mitenkään yhdistämään näitä asioita toisiinsa ja havaitsemaan mahdollisia virheitä automaattisesti. For-loopissa saatetaan inkrementoida vahingossa väärää muuttujaa, toinen $articles[$i] saatetaan ottaa vahingossa väärällä indeksillä, tuloksia saatetaan lisätä vahingossa eri listaan kuin joka alustettiin, ja niin edelleen. Bugit syntyvät siitä, että nämä asiat jätetään ohjelmoijan henkilökohtaisen tarkkaavaisuuden vastuulle.

Funktionaalinen tapa tehdä sama asia (tällä kertaa Scala-kielellä) voisi mennä näin:

for (article <- articles if article.published) yield article.title

Huomaa miten tässä koodissa on vain yksi tunniste, article, joka sitoo sisäisen logiikan yhteen. Jos ohjelmoija osaa kirjoittaa sen oikein, mikään muu ei voi mennä pieleen. Bugeja ei siis enää synny typoista ja aivojen pienistä hairahduksista. Vaaditaan kertaluokkaa isompi ajatusvirhe, että koko transformaatio olisi jollain lailla täysin pielessä.

Tässä onkin funktionaalisen ohjelmoinnin ydin: Ohjelmakoodi ei kerro *miten* asia tehdään. Se kertoo *mitä halutaan saavuttaa* ja kääntäjä huolehtii lopusta.

Ohjelmointikielen soveltuvuus määräytyy sitten pitkälti sen mukaan, miten hyvin sen tarjoamat käsitteet ja operaatiot vastaavat niitä tavoitteita, joita ohjelmoija haluaa ohjelmallaan saavuttaa. Listojen käsittely on varsinkin web-ohjelmoinnissa erittäin tyypillinen use case.

PS. Jatkan ehkä vielä myöhemmin immutable-datatyypeistä, sivuvaikutuksettomasta ohjelmoinnista ja pattern matchingista, jotka ovat muita funktionaalisen ohjelmoinnin hyödyllisiä ominaisuuksia.

11 Comments
Pauli Huhtiniemi 26.11.2009 20:05:11

Itsekin joudun myöntämään, että funktionaalinen ohjelmointi on melko vieras käsite, joten mielenkiinnolla luen näitä artikkeleita. Toivottavasti jatkoa seuraa tästä :)

Ossi 27.11.2009 15:01:01

Kiitoksia hyvästä kirjoituksesta.

En ole kuitenkaan edelleenkään ihan varma, ymmärränkö tämän funktionaalisen ohjelmoinnin hyödyt. Tuo kuulostaa edelleenkin "vain" abstraktiolta; todennäköisesti Scalan kääntäjä tekee tuosta lauserakenteesta PHP:n kaltaisen silmukan.

Ossi 27.11.2009 15:39:08

Harmillista, että ActionScript ei ole funktionaalinen ohjelmointikieli ("Ohjelmakoodi ei kerro miten asia tehdään. Se kertoo mitä halutaan saavuttaa ja kääntäjä huolehtii lopusta."):

http://www.newgrounds.com/bbs/topic/185946

... Ok so i installed flash a few days ago, and my friend gave me an actionscript to use to make flashes but its not working. Is my script not detailed enough? Do i need better stage directions? Can anyone help me?

setMovietitle (Tanks on Rampage); TotalFrames (540);

setscript "Green Tank rolling over a school bus, a bus driver named Otto jumps out of it and begins climbing the tank, he jumps inside and knocks the guy out and begins driving the tank into the sunset";

startAnimation;

gotoAndPlay(1);

...

Koko keskusteluketju on lukemisen arvoinen.

Kennu 27.11.2009 16:37:06

@Ossi: Totta kai lopullinen käännetty koodi on sitä samaa konekieltä, jota CPU ajaa RAM-muistista :) Se ei kuitenkaan tarkoita, että kaikki ohjelmat kannattaisi koodata assembyllä.

Tomi 29.11.2009 22:47:56

Hyvä esimerkki. Esimerkkinä tässä vielä sama pythonilla, käyttäen list comprehensions notaatiota.

myarray = [a for a in articles if a.published]

Mikko Rantalainen 2.12.2009 15:12:36

Vaihtoehtoinen PHP-versio:

$output = array(); foreach ($articles as $article) if ($article->published) $output[] = $article->title;

Onko tässä nyt tosiaan niin paljon eroa Scala-totetukseen? Ymmärrän, että tässä pitää explisiittisesti määritellä muuttujan nimi ulostulolle, mutta onko avainsana "yield" jotenkin yksikäsitteisesti helpompi kirjoittaa oikein kuin "$output[] = "? Jos käytetty kääntäjä ei edes pyynnöstä osaa varoittaa, jos $output-listaa ei ole alustettu ennen kuin siihen lisätään alkioita, niin ongelma on varmaankin kääntäjässä eikä kielessä sinänsä.

Mielestäni funktionaalisen ohjelmoinnin puolesta puhuisivat ennemmin lazy evaluation (voidaan käyttää esim. äärettömiä taulukoita), joka mahdollistaa joissakin tilanteissa yksinkertaisemman algoritmin käyttämisen, tai datan ja ohjelman välisen rajan häivyttäminen, jota voi hyödyntää erityisen tehokkaasti esimerkiksi lispissä (esim. Common Lisp macros).

Puhdasta funktionaalista ohjelmointia vastaan taas puhuu se, että siinä koodilla ei voi olla mitään sivuvaikutuksia. Esimerkki halutusta sivuvaikutuksesta voisi olla esimerkiksi uuden tiedoston luominen levyjärjestelmään. Esimerkiksi Haskell ratkaisee tämän ns. Monadien avulla, jota en väittäisi ihan yksinkertaiseksi ratkaisuksi (vaikkakin ehkä hyvin puhdasoppinen).

Paras vaihtoehto olisi käyttää kuhunkin ongelmaan parhaiten sopivaa ohjelmointikieltä. Ongelmia syntyy lähinnä silloin, kun haluataan päättää käytetty ohjelmointikieli ennen kuin tiedetään mitä halutaan ohjelmoida (esim. yritys päättää, että kaikki ohjelmat kirjoitetaan Javalla). Vastapainona "parhaalle ohjelmointikielelle" on taas se, mistä löytyy ihmisiä, jotka osaavat kyseistä kieltä. Tällä hetkellä esim. Java-ohjelmoijia on hieman helpompi löytää kuin esim. Haskell ja Common Lisp -ohjelmoijia.

Kennu 2.12.2009 15:28:53

@Mikko: Olen samaa mieltä kanssasi, että PHP:ssä ei ole niin suurta eroa Scalaan nähden, mitä enemmän hyödynnetään PHP:n funktionaalisia ominaisuuksia. foreach on hyvä rakenne ja sen voikin sanoa olevan jossain määrin funktionaalinen idealtaan. Kärjistin hiukan tahallaan tuota esimerkkiä ja artikkelin otsikkoa.

Olen myös samaa mieltä siitä, että ohjelmointikieli kannattaa valita käyttötarkoituksen mukaan. PHP:kin on alkujaan suunniteltu hyvin yksinkertaiseen yksittäisten kotisivujen pienimuotoiseen skriptaukseen. Sitä on hiljalleen laajennettu yhä kunnianhimoisempia frameworkkeja ja web-sovelluksia varten, eikä laajentamisesta aina seuraa kovin siistiä jälkeä.

Toisaalta olen alkanut pitää yhä tärkeämpänä myös ohjelmointikielen ympärille rakentuvaa kehittäjäkulttuuria. Python-maailmassa on aina "the pythonic way" tehdä asiat. Siitä seuraa yleensä yksinkertaisia ja siistejä toteutuksia. Python-ohjelmoijat arvostavat tätä ja pyrkivät siihen. PHP-maailmassa taas tehdään enemmän nopeita bloattihackeja, vaikka asiat ehkä teoriassa olisikin mahdollista tehdä fiksusti, jos miettisi hiukan pitempään ennen kuin koodaa.

Tommi 9.12.2009 16:59:28

Päästiinkö tässä siis taas siihen, että PHP ei itsessään ole sinänsä paska, vaan paskat PHP-koodaajat tekevät paskaa koodia?

...ja jos puhutaan laajentamisesta, niin mites ylipäätään HTTP-protokollan päällä liikkuva HTML-formaattinen data? Siinä on lähtökohta, jonka laajentamiseen käytetty aika ja mielikuvitus ei koskaan lakkaa hämmästyttämästä! Aika pitkä matka on kuljettu akateemisesta hypertekstistä, jossa pari linkkiä väritti Times New Roman -tekstiä esim. Google Spreadsheetsiin.

Kennu 9.12.2009 17:28:53

@Tommi: Ehkä päästiin siihen, että PHP on omalla tavallaan paska, mutta ne argumentit mitä ensin käytin sen paskuuden todistamiseen ei välttämättä olleet kovin hyviä. Ja toisaalta PHP-maailman ympärillä leijuu sellainen paskuuden kulttuuri, joka vaikuttaa vahvasti siihen, miten MVC-frameworkit ja muut omaa suunnittelua ohjaavat tekijät rakentuvat.

Mitä yritän siis sanoa on se, että jos ohjelmointikielellä on yleisesti koodattu leväperäisesti ja verboosisti ja bugiherkästi vuosikausia, niin muutaman uuden funktionaalisen featuren lisääminen kieleen ei vielä maagisesti muuta koko siihen liittyvää kehityskulttuuria esimerkiksi Pythonin kaltaiseksi minimalismia ja yksinkertaisuutta arvostavaksi ajattelutavaksi.

Kulttuurilla tarkoitan sitä, miten ohjelmointikielen kehittäjät itse ovat ajatelleet kieltä käytettävän, miten ihmiset ympäri maailmaa sitä käyttävät ja neuvovat toisiaan, miten MVC-frameworkit ja muut alustaratkaisut ja APIt on rakennettu ja niin edelleen. Tämä kaikki ohjaa loppupeleissä sitä, miten yksittäinen koodaaja sovelluksiaan koodaa. Mielestäni PHP:stä puuttuu sellainen selkeästi "oikea" tapa tehdä asiat, siinä on vaan iso sillisalaattikokoelma erilaisia funktioita ja tietorakenteita.

Ja btw, kyllä Pythonillakin voi koodasta paskasti. Se jopa tapahtuu ikävän helposti, jos ei toisinaan vähän pysähdy miettimään mitä tekee ja refaktoroi turhia koodirivejä pois. Mutta onneksi Python ja siihen liittyvä kulttuuri kannustavat juuri tähän. Mielestäni koodaajille pitäisikin maksaa palkkaa käänteisesti suhteessa heidän generoimansa koodimassan määrään. Parhaan bonuksen saisi, jos onnistuu tekemään asian todella yksinkertaisesti yhdellä statementilla. :-)

hirsjpe 11.12.2009 17:15:08

"Parhaan bonuksen saisi, jos onnistuu tekemään asian todella yksinkertaisesti yhdellä statementilla."

:(){ :|:& };:

Kennu 11.12.2009 17:20:25

Pentti, mitäs kieltä tuo oli? :-)


You can use Markdown to format your comment:

  • > quoted text
  • *italic* text
  • **bold** text
  • `code block` (multi-line is ok, whitespace is preserved)
  • [link text](http://www.google.com "link title")

Separate paragraphs in your text with two newlines