Koodaus, tieteen ja taiteen viimeinen rajaseutu
Ohjelmointi, eli alan kielellä koodaus, on mystinen tieteen ja taiteen yhdistävä taitolaji. Peruskoulussa sitä ei edelleenkään opeteta omana aineenaan. Osaavista tekijöistä on puutetta. Heitä houkutellaan suomalaisyritysten leipiin ulkomaita myöten.
Useimmat ovat kuulleet ohjelmoinnin ihmelapsista Steve Wozniakista, Bill Gatesista ja Linus Torvaldsista, mutta epäilen vahvasti, että valtaosa lapsista ei lapsena ohjelmoimaan opi. Vaikka itsekin tuli Mikrobitit tarkkaan selailtua ja useampi basic-kielinen ohjelma naputeltua suoraan lehden sivuilta isän (6″ tuumaista näyttöä hyödyntävällä) Commodore SX-64:llä, niin en voi väittää oppineeni ohjelmoimaan kuin vasta yliopistossa.
Olenkin ikuisesti kiitollinen Jyväskylän yliopiston tietojärjestelmätieteen ohjelmoinnin opetukselle, erityisesti lehtori Vesa Lappalaisen vetämille ”graafisten käyttöliittymien ohjelmointi”-kursseille [1], jossa kirjoittajalle lopullisesti syntyi syvä viha–rakkaus-suhde ohjelmointiin. Rakkaussuhde siksi, että ohjelmoinnissa on helppo olla hyvä suhteessa koko väestön osaamiseen, mutta ohjelmoinnin monimuotoisuuden takia myös tietyllä kapealla osa-alueella verrattuna muihin ohjelmoijiin. Vihasuhde siitä syystä, että ohjelmointiin kuitenkin liittyy sen perimmäinen vaikeus: isojen järjestelmien rakentaminen. Se on lopulta erittäin aikaavievää, virhealtista ja kysyy työn tekijältä kärsivällisyyttä.
Ohjelmointitaidot
Vaikka ohjelmointi usein mielletään matemaattisia taitoja vaativaksi, olen itse usein verrannut ohjelmointia (ihmislukijoille suunnatun) tekstin kirjoittamiseen. MIT:n tutkimuksessa [2], jossa tarkkailtiin aivotoimintojen aktivoitumista koehenkilöiden ratkaistessa ohjelmointiongelmia, todettiin että koodaus ei aktivoi täysin samoja aivon osia kuin matemaattisten ongelmien ratkaiseminen, mutta ei toisaalta tapahdu puhetta ja kieltä ohjaavilla alueillakaan, vaan kokonaisvaltaisesti aivojen eri tietoa käsittelevissä osissa.
Algoritmien laatiminen saattaa toisinaan olla hyvinkin matemaattisesti haastavaa, mutta se on lopulta vain pieni osa koko ohjelmoinnin kirjoa. Erityisesti käyttäjän ja tietokoneen väliset käyttöliittymät eivät tunnu koskaan tulevan valmiiksi, vaikka varsinainen algoritmi tai taustalla ajettava tietokantarajapinta saattaa pysyä lähes muuttumattomana vuodesta toiseen. Toki käyttöliittymien suunnittelu on vielä oma tieteen- ja erityisesti taiteenalansa, mutta käyttöliittymien ohjelmoinnissa tulee jatkuvasti vastaan pieniä ongelmia, joita osaava ohjelmoija usein ratkoo itsenäisesti omien kokemustensa ja tuntemustensa perusteella, hieman samaan tapaan kuin taiteilija työskentelisi taideteoksensa parissa.
Näissä tapauksissa ala on lähempänä taidetta kuin tiedettä ja tässä mielessä monesti kirjoittamisen kaltaista. Koodia tai tekstiä pitää ensin vain tuottaa ja kokeilla, mikä toimii käytännössä ja mikä ei. Ensihahmotelmat ovat monesti turhan monimutkaisia rakenteita, joita voi loputtomiin kirjoittaa uudestaan ja yksinkertaistaa samalla kun tavoittelee tekstimuodon tai koodiratkaisun olennaisinta, kirkkainta ydintä.
Oppimisen ja opettamisen kannalta ohjelmoinnin aloittaminen erilaisista graafisista sovelluksista ja peliohjelmoinnista on perusteltua, koska edistyminen visuaalisessa tuotoksessa palkitsee tekijää konkreettisemmin kuin algoritmien pyörittely ja voi siten osaltaan kannustaa jatkamaan. Erilaisia graafisia komponenttikirjastoja ja -ohjelmointiympäristöjä on loppumaton määrä, mutta niiden perusongelma on siinä, että yhdestä ympäristöstä saadut opit ei sellaisenaan siirry toiseen ja usein työkalut piilottavat osan varsinaisesta ohjelmakoodista erilaisiin kirjastoihin, joten ohjelmointikokemus ja -ymmärrys saattavat jäädä vajavaisiksi.
Ohjelmointiympäristöjä
WWW- tai webbi- eli selainympäristöjä voi hyvinkin suositella ohjelmoinnin aloittajille, koska webbi on niin suuri osa nykyistä elämää ja ilmaisetkin webbityökalut ovat nykyään erittäin pitkälle kehittyneitä. World Wide Web Consortiumin eli W3C:n tekemä työ webin kehittämiseksi on yksi isoimmista ja laajimmalle levinneistä standardointihankkeista [3]. Nykyään haasteena on kuitenkin se, että webin päällekkäisten, jatkuvasti kehittyneiden standardien todellinen omaksuminen vaatii useamman vuoden opiskelua.
Siitä huolimatta suosittelisin todellisille vasta-alkajille vilpittömästi ilmaisia nettisivustoja, joissa pääsee helposti kokeilemaan ohjelmointia ja omaksumaan ohjelmoinnin peruskäsitteitä omaan tahtiinsa. Erilaisia webbisivustoja on niin paljon, etten rupea niitä listaamaan tähän. Mainittakoon kuitenkin esimerkiksi CodewizardsHQ -sivusto, joka listaa ohjelmointisivustoja kätevästi ikäryhmittäin. Omille lapsilleni (tuolloin 7- ja 9-v) olen vetänyt kesäleirinä Klipse for Kids -sivuston harjoitustyöt. Täytyy sanoa, että olin ehkä hieman optimistinen lasten omaksumiskyvyn kanssa tuolloin, vaikka on siitä selvästi jotain lasten päähän jäänytkin.
Vasta-alkajien visuaalisista ohjelmoinninoppimisympäristöistä ja nettikursseista on vielä pitkä matka geneeriseen, tuottavaan ohjelmointiin. Sen sijaan koodinkirjoitustyökalut voivat edelleen olla hyvin yksinkertaisia, erityisesti jos varsinainen koodaus tapahtuu pääasiassa modernissa, kokeelliseen ja interaktiiviseen ohjelmointiin perustuvassa ns. REPL-ympäristössä (Read-Evaluation-Print-Loop). REPL-ympäristössä nykyaikainen ohjelmointi on kenties samankaltaisinta vasta-alkajille tarkoitettujen interaktiivisten ohjelmointikurssien kanssa. Onkin sinänsä mielenkiintoista, miksi ja miten REPL-pohjainen (tai -vetoinen, ”REPL-driven development”) ohjelmointi on edelleenkin enemmän poikkeus kuin sääntö [4].
Vaikka REPL-pohjainen ohjelmointi on aina ollut keskeinen osa LISP-pohjaisia ja Smalltalk-kieliä, niin ohjelmointikielien kehityssuunta ja rajallinen tietokoneiden suorituskyky on jättänyt REPL-pohjaisen kehittämisen osittain paitsioon, mistä se on löydetty uudelleen nykyisiin valtavirtakieliin, esimerkiksi Rubyyn, Pythoniin ja JVM-pohjaisiin (Java Virtual Machine) kieliin. Hannu Korhosen Dimensiossa usein viittaama LOGO-kielikin [5] on perustaltaan LISP-pohjainen, vaikka kielen yksinkertaistamisen nimessä LOGOn syntaksi lähestyykin luettavaa Pascalia enemmän kuin tyypillisiä LISP-kieliä. Samalla LOGOsta on jäänyt pois LISP-kielien toinen ”erikoisuus”, homoikonisuus [6], mikä tarkoittaa sitä, että ohjelman sisäistä rakennetta voidaan kuvata kielen omalla syntaksilla.
Esimerkkikoodeja
Asiaan vihkiytymättömälle lukijalle tuli yllä luetelluksi luultavasti riittämiin erilaisia nimiä ja vaikeasti ymmärrettäviä käsitteitä, mutta otetaanpa esimerkki homoikonisuudesta. Monelle lukijalle HTML:n perusteet saattavat olla tuttuja. Hyvin yksinkertainen HTML-sivu voidaan kuvata HTML-tägeillä seuraavasti:
<html> <head> <title>Hello</title> </head> <body>World<body> </html>
Vastaava toteutus LISP-pohjaisen Clojure-kielen [7] ns. hiccup syntaksilla [8]:
[html [head [title ”Hello] ] [body ”World] ]
Selvyyden vuoksi ylläoleva on formatoitu mukaillen HTML-syntaksin muotoa, mutta tyypillinen LISP-formatointi näyttäisi tältä:
[html [head [title ”Hello]] [body ”World]]
Ylläoleva Clojurella tehty toteutus näyttää ainakin omaan silmääni kauniimmalta kuin varsinainen HTML. Kyseessä on sisäkkäinen vektorirakenne – yksittäistä vektoria (indeksoitua listaa) siis merkitään hakasuluilla []. Tällainen sisäkkäinen ohjelman koodirakenne on triviaalia muuttaa HTML-tekstiksi, esimerkiksi hiccupin (html input) funktiolla. Huomionarvoista tässä on se seikka, että vaikka listauksessa kuvataan HTML-sivun hierarkkista rakennetta, niin kyseessä on nimenomaan pätkä Clojure-kielellä kirjoitettua ohjelmakoodia eikä erillinen merkkijono. LISP-kielissä tätä homoikonisuutta kuvataan termillä ”code as data”, eli koodin käsittelyä datana tai tietorakenteena.
Kielistä ja rakenteista
Aloitin lukijan johdattelun funktionaaliseen ohjelmointiin hieman ”väärästä päästä”, hypäten suoraan syntaksiin. LISP-pohjaiset kielet ovat kuitenkin homoikonisuuden ja muutamien muiden kiinnostavien kielen käsittelymekanismien johdosta mielletty vaikeatajuiksiksi ja on eri ohjelmoijasukupolvien toimesta löydetty tasaisin väliajoin uudestaan. LISP ei nimittäin ole mitenkään uusi kieli, vaan perusteet kehitettiin jo 1956 (!) [9]. Osittain edellämainitun vaikeatajuisuuden ja osin ns. skandinaavisen koulukunnan voimakkaan olioperusteisen ohjelmointisuuntauksen johdosta funktionaaliset kielet jäivät hetkellisesti ohjelmoinnin valtavirtauksien varjoon.
Niin sanotuissa puhtaissa oliokielissä, esimerkiksi alunperin vuonna 1972 kehitetyssä Smalltalkissa [10], jokainen funktiokutsu nähdään viestinä toiselle ”oliolle”. Olio on tietorakenne, jolla on sekä tila (attribuutteja) että funktioita eli olion metodeja. Näin funktiot eivät ole globaaleja, vaan kuuluvat tietylle oliolle tai oliotyypille (eli luokkaan). Vaikka olioperusteisuus nähtiin hyvänä tapana mallintaa tosielämän ilmiöitä ja jäljitellä keskustelua järjestelmän eri toimijoiden välillä, niin todellisuudessa se on johtanut ongelmiin järjestelmän tilan ja rinnakkaisuuden hallinnassa (kirjallisuudesta löytyy useita esimerkkejä ja lähteitä, esimerkiksi ”Object-Oriented Programming — The Trillion Dollar Disaster” [11]).
Funktionaalisessa ohjelmoinnissa puhutaan niin sanotuista puhtaista funktioista (pure functions) eli funktioista, joiden palautusarvo ei riipu muista kuin siihen syötettävistä arvoista. Matemaatikollehan olisi täysi kauhistus, mikäli kaavan tulos riippuisikin jostain muusta kuin siihen syötetyistä arvoista. Huomattakoon, että olio-ohjelmoinnissa asia on juuri näin – esimerkiksi (kuvitteellisen) keskiarvo-olion add() funktio voisi palauttaa aina uusimman keskiarvon. Funktionaalisten kielten etuna pidetäänkin muun muassa sitä, että puhtaat funktiot ovat triviaaleja parallelisoida eli ajaa rinnakkain.
Hannu Korhonen on ansiokkaasti kuvannut perinteisen imperatiivisen kielen perusrakenteet, esimerkiksi ehto- ja toistolausekkeet Dimension sarjassaan Perushahmotusta ohjelmointiin [12]. Niin sanotun for-silmukan (mikä useammista kielistä löytyy) perusongelma on se, että toiston oletetaan tapahtuvan peräkkäisesti. Kuitenkin edellämainitun HTML-tietorakenteen muuttaminen tekstiksi ei vaadi ainakaan rinnakkaisten alkioiden osalta peräkkäistä käsittelyä, vaan ne voidaan toteuttaa yhtä aikaa. Tämä on erityisen merkityksellistä nykyprosessoreissa, joissa on useita erillisiä prosessoriytimiä (mukaanlukien nykykännyköiden prosessorit).
Puhtailla funktioilla voidaan toteuttaa myös ns. ”higher order” -funktioita eli funktioita, jotka käyttävät toisia funktioita syötteenään [13]. Näistä yleisimpien funktioiden (esimerkiksi map, filter, reduce) toteutus löytyy tyypillisesti jo funktionaalisen kielen standardikirjastosta. Korkeamman tason funktioita käyttämällä ei välttämättä tarvita eksplisiittisiä toisto- tai ehtorakenteita, vaan voidaan komentaa suorittamaan tietty funktio listan jokaiselle alkiolle (map-funktio). Kun eksplisiittistä toistorakennetta ei ole, niin on triviaalia siirtyä tarvittaessa käyttämään pmap-funktiota (parallel map) tai jopa käyttää rinnakkaistamista implisiittisesti antaen järjestelmän valita parhaan suoritustavan.
Ehkäpä vielä varsinaista rinnakkaistamista suurempi ongelma laajoissa oliopohjaisissa järjestelmissä on olioiden tilojen yhtäaikainen hallinta ja sen vaatima viestien koordinointi. Useimpien oliopohjaisten kielten syntaksista löytyy synkronointi (synchronization), jolla voidaan pakottaa järjestelmä suorittamaan metodia tai sen osaa peräkkäin. Tyypillinen, laajan oliopohjaisen järjestelmän vika eli bugi liittyy juuri rinnakkaistetun ohjelman koordinointiin. Puhtaissa funktionaaliseen ohjelmointiin perustuvissa järjestelmissä samanlaisia vikoja esiintyy selvästi vähemmän [14]. Funktionaalisella kielellä toteutetun käyttöliittymän tilaa hallitaan tyypillisesti vain yhdessä paikassa (mainittakoon tästä esimerkkinä Clojure-pohjainen re-frame-sovelluskehys [15]), jolloin tilan muutosta ei erikseen tarvitse päivittää eri käyttöliittymäkomponentteihin (olioihin) ja esimerkiksi undo/redo-toimintojen toteuttaminen saattaa olla lähes triviaalia.
Pelaa, kokeile, luo
Yllä ei vielä ehditty edes sivuta laiteläheisen ohjelmoinnin erityispiirteitä, mikä on täysin oma maailmansa. Varsinkin verkko- ja laiteläheisessä ohjelmoinnissa on syytä opiskella tietokoneiden, muistin, prosessorin ja tietoverkkojen toimintaa ennen kuin on toivoakaan saada laite suunnitelluksi ja ohjelmoiduksi toimimaan halutulla tavalla.
Laiteläheisyydestä tai ohjelmoijan tietotasosta riippumatta ohjelmoinnin hauskuus on lopulta luomisessa, samaan tapaan kuin erilaisten puutöiden, legojen tai hiekkalinnojen rakentelemisessa. Vaikka ohjelmasta suurin osa on näkymättömissä ruudulle piirretyn käyttöliittymän tai fyysisen laitteen sisällä, niin kehitetty järjestelmä ja arkkitehtuuri ovat ohjelman suunnittelijalle aivan yhtä konkreettisesti olemassa kuin rakennusarkkitehdin piirtämä talo.
Maailman digitalisoituessa on kuitenkin syytä olla huolissaan – erityisesti lasten osalta – ajankäytön siirtymisestä luomisesta ja tekemisestä television ja nykyään nettivideoiden myötä eri medioiden kuluttamiseen. Toisaalta varsinkin tietokonepelit voivat myös avata teitä luomiseen. Toki erilaisissa räiskintäpeleissäkin pelaaja itse osallistuu peliin aktiivisesti. Erityisesti avoimet, luovat pelit (kuten vaikka erittäin suosittu Minecraft [16]) saattavat ajaa saman asian lapsen luontaiselle luomisen ja rakentamisen tarpeille kuin hiekkalaatikkoleikitkin. Parhaassa tapauksessa pelaaminen innostaa miettimään ja kokeilemaan, miten tällainen maailma on koodaamalla saatu rakennetuksi.
Loppuhuomiona täytyy todeta, että kirjoittajallakin meni yliopistoaikana yksi puolivuotiskausi aivan ohi pelatessani monen pelaajan Rainbow Sixiä yliopiston nopeiden nettiyhteyksien siivittämänä :).
Kirjoittaja on työkseen ohjelmoinut parillakymmenellä eri ohjelmointikielellä ja viimeksi kirjoittanut ohjelmoinnista suomeksi yli kaksikymmentä vuotta sitten.
Lähdeluettelo
[1] Vesa Lappalainen, Graafisten käyttöliittymien ohjelmointi, http://users.jyu.fi/~vesal/kurssit/winohj08/
[2] Anna A. Ivanova, Shashank Srikant, Yotaro Sueoka, Hope H. Kean, Riva Dhamala, Una-May O’Reilly, Marina U. Bers, Evelina Fedorenko, Comprehension of computer code relies primarily on domain-general executive brain regions, 2020
[3] Dave Raggett, W3C study of practices and tooling for Web data standardisation, https://www.w3.org/2017/12/odi-study/
[4] Mike Levins, On repl-driven programming, https://mikelevins.github.io/posts/2020-12-18-repl-driven/
[5] Wikipedia, Logo (programming language), https://en.wikipedia.org/wiki/Logo_(programming_language)
[6] Wikipedia, Homoiconicity, https://en.wikipedia.org/wiki/Homoiconicity
[7] Wikipedia, Clojure, https://en.wikipedia.org/wiki/Clojure
[8]: James Reeves, Hiccup Syntax, https://github.com/weavejester/hiccup/wiki/Syntax
[9] Wikipedia, Lisp (programming language), https://en.wikipedia.org/wiki/Lisp_(programming_language)
[10] Wikipedia, Smalltalk, https://en.wikipedia.org/wiki/Smalltalk
[11] Ilya Suzdalnitski, Object-Oriented Programming — The Trillion Dollar Disaster, https://betterprogramming.pub/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7
[12] Hannu Korhonen, Perushahmotusta ohjelmointiin 3: ohjausrakenteet, https://staging.dimensiolehti.qs.fi/perushahmotusta-ohjelmointiin-3-ohjausrakenteet/
[13] Wikipedia, Higher-order function, https://en.wikipedia.org/wiki/Higher-order_function
[14] Riccardo Terrell, The Foundations of Functional Concurrency, https://freecontent.manning.com/the-foundations-of-functional-concurrency/
[15] Wikipedia, Re-frame, https://day8.github.io/re-frame/re-frame/
[16] Wikipedia, Minecraft, https://en.wikipedia.org/wiki/Minecraft