• Ei tuloksia

Clojure-ohjelmointikieli

N/A
N/A
Info
Lataa
Protected

Academic year: 2022

Jaa "Clojure-ohjelmointikieli"

Copied!
64
0
0

Kokoteksti

(1)

Clojure-kieli

Mikael Puhakka

Pro gradu -tutkielma

Tietojenkäsittelytieteen laitos Tietojenkäsittelytiede

Syyskuu 2015

(2)

ITÄ-SUOMEN YLIOPISTO, Luonnontieteiden ja metsätieteiden tiedekunta, Joensuu Tietojenkäsittelytieteen laitos

Tietojenkäsittelytiede

Puhakka, Mikael Erkki Juhana: Clojure-kieli Pro gradu -tutkielma, 59 s., ei liitteitä.

Pro gradu -tutkielman ohjaaja: FT Matti Nykänen Syyskuu 2015

Tiivistelmä:

Clojure on funktionaalinen lisp-perheen kieli, joka toimii Java Virtual Machinen päällä.

Tutkimme Clojuren tuomia ominaisuuksia, ja kuinka ne edesauttavat jatkuvasti moni- mutkaistuvien sovellusprojektien yksinkertaistamisessa. Näihin kuuluvat tässä työssä esiteltävät ominaisuudet ja piirteet, kuten arvosemantiikka, funktionaalisuus, hallittu muutostenhallinta ja ilmaisuongelma ratkaisuineen. Ahkerasti suorittava kieli käyttää viivästettyä laskentaa tietorakenteissaan tuoden mukanaan laiskan laskennan element- tejä. Clojuren perustyypit ja tietorakenteet ovat pysyviä ja muuttumattomia, mikä saa aikaan arvosemantiikan. Nykyään paljon käytetyillä sovellusalustoilla käännetyt oh- jelmat ovat enemmän alustariippumattomia kuin ennen, ja ovat pitkälti myös yhtä no- peita kuin natiivikoodi optimoivien tulkkien ansiosta. Myös verkkoselaimissa enim- mäkseen toimiva JavaScript voidaan tulkita sovellusalustaksi. Clojuren toimiessa Java Virtual Machinen päällä ja ClojureScriptin toimiessa JavaScriptin päällä kielellä on ikäänsä nähden suuri vaikutusalue. Funktionaalisen ja oliopohjaisen paradigman mää- ritelmiä katselmoidaan kirjallisuudesta. Paradigmojen tavat ratkaista ongelmia eroavat huomattavasti toisistaan. Päädymme siihen tulokseen, että funktionaalinen ohjelmoin- tikieli painottaa funktioiden käyttöä perusrakennuspalasena, ja että Clojure on epäpuh- das funktionaalinen kieli. Ilmaisuongelma kysyy tietotyyppien ja niiden operaatioiden yhteensovittamisesta niin, että mukaan voidaan tuoda myöhemmin lisää tietotyyppe- jä ja operaatioita ilman, että entistä koodia tarvitsee muokata tai kääntää uudelleen.

Erilaisista ratkaisuista mielekkäin on geneeristen funktioiden käyttö. Clojure tarjoaa erään ratkaisun ilmaisuongelmaan geneeristen funktioiden avulla. Sovellusprojektin ja -koodin monimutkaisuutta voidaan hallita lukuisilla tekniikoilla. Muutamia tapo- ja käsitellään: sovellusaluekohtaisten kielten kehittäminen ongelmakuvauksiin, pysyvä ja oletuksena muuttumaton tieto tapana välttää hallitsematon muutoksenteko, ja kes- kitetty rajapinta koordinoiduille muutoksille silloin kun muutokset ovat välttämättö- miä. Clojure on näiden ominaisuuksien ansiosta hyvin varustautunut yksinkertaista- maan ratkaisuja.

Avainsanat: Clojure, lisp, sovellusalustat, funktionaalinen ohjelmointi, oliopohjainen ohjelmointi

ACM-luokat (ACM Computing Classification System, 1998 version): D.2.3, D.3.2, D.3.3

(3)

UNIVERSITY OF EASTERN FINLAND, Faculty of Science and Forestry, Joensuu School of Computing

Computer Science

Puhakka, Mikael Erkki Juhana: The Clojure Programming Language Master’s Thesis, 59 p., no appendices

Supervisor of the Master’s Thesis: PhD Matti Nykänen September 2015

Abstract:

Clojure is a functional Lisp language that runs on the Java Virtual Machine. In this work we study the features that Clojure brings to reduce ever-increasing complexity in software projects. These features include value semantics, functionality, controlled mutation and the expression problem with some solutions. An eager language, Clojure uses lazily evaluated data structures to simulate lazy evaluation. The values and data structures in Clojure are immutable and persistent, which results in value semantics.

Application platforms are greatly used in modern development. Modern software de- veloped on an application platform is more platform independent than before. Thanks to the matured optimizing interpreters, such software can be as fast as one compiled for native hardware. JavaScript, ubiquitously running on modern web browsers, can also be considered as an application platform. Clojure running on Java Virtual Machine and ClojureScript running on JavaScript make the languages have a great reach, despite the young age of the projects. We will review some definitions of both functional and object-oriented paradigms from literature. The methods of problem solving differ re- markably between the paradigms. A functional programming language emphasizes the function as a basic building block. We will conclude that Clojure is a nonpure functio- nal language. The expression problem asks whether data types and operations can be fit together so that more types or operations can be added later on without modifying or recompiling any existing code. Out of several solutions we study the most promi- nent: generic functions. Clojure provides a solution to the expression problem through an application of generic functions. The complexity of software projects and code can be managed with numerous techniques. We will cover some of them: domain speci- fic languages as a method to describe problems in languages tailored to the problem domain; persistent and immutable-by-default data as a way to avoid uncontrolled mo- difications; and a centralized interface for coordinated changes when such a situation is truly warranted. With these features, Clojure is well-equipped to simplify software solutions.

Keywords: Clojure, lisp, software platforms, functional programming, object-oriented programming

CR Categories (ACM Computing Classification System, 1998 version): D.2.3, D.3.2, D.3.3

(4)

Sanasto

ACID Atomic, Consistent, Isolated, Durable: muutostapahtumien neljä ta- voiteltavaa ominaisuutta.

Arvo Muuttumaton tiedonjyvänen.

CLR Common Language Runtime: ajonaikainen ympäristö .NET- ympäristössä käytettävän tavukoodin suoritukselle.

Ensiarvoisuus Ensiarvoisia funktioita voidaan käsitellä kuten muita arvoja.

Erikoiskutsu Kielen sisäänrakennettu käsky, jota ei ole kirjoitettu kielellä itsellään.

JIT Just In Time: suorituksen aikana tehtävä JIT-optimointi pystyy huo- mioimaan suoritettavan koodin kulloisen kontekstin ja yleisesti ot- taen tekemään paremman optimoinnin kuin etukäteen optimoitaes- sa.

JVM Java Virtual Machine: ajonaikainen ympäristö Java-tavukoodin suo- ritukselle.

Kuvaus Kokoelma avain-arvo-pareja. Usein sanakirjan käsitteellä.

Makro Käännösaikana luettavat ja suoritettavat funktiot ohjelmakoodista ohjelmakoodiksi.

MVCC Multi-Version Concurrency Control: malli hallita usean entiteetin muutoksia koordinoidusti.

POD Plain Old Data: tietoa ja tietorakenteita, joilla ei ole metodeja käsi- tellä itseään.

STM Software Transactional Memory: tapa hallita entiteettien muutoksen- tekoa jaetussa muistissa eri säikeiden välillä.

TCO Tail Call Optimization: funktion häntärekursion optimointi kutsupi- non hallitsemiseksi.

Viite Arvo, joka osoittaa tiettyyn entiteettiin, jota voidaan lukea tai muut- taa hallitusti epäpuhtain funktioin.

(5)

Sisältö

1 Johdanto 1

2 Clojuren syntaksi ja perusteet 4

2.1 Kielen perusteet . . . 4

2.2 Tietotyypit ja tietorakenteet . . . 8

2.2.1 Skalaarityypit . . . 8

2.2.2 Tietorakenteet . . . 10

2.2.3 Kokoelmien toteuttamista abstraktioista . . . 11

2.3 Makrot . . . 13

2.4 Kommunikointi Java-kirjastojen kanssa . . . 15

3 Sovellusalustojen merkitys pienille kielille 17 3.1 Java Virtual Machine . . . 18

3.2 Common Language Runtime ja JRE . . . 20

3.3 JavaScript-alustat: IonMonkey, V8 ja muut . . . 21

4 Oliopohjainen ja funktionaalinen paradigma 24 4.1 Oliopohjaisen ja funktionaalisen paradigman määritelmiä ja karakteri- soivia ominaisuuksia . . . 25

4.1.1 Oliopohjaisen paradigman määritelmiä . . . 26

4.1.2 Funktionaalisen paradigman määritelmiä . . . 27

4.2 Clojure on funktionaalinen kieli . . . 29

5 Ilmaisuongelma 31 5.1 Ongelmakuvaus . . . 31

5.2 Eräitä ratkaisuja . . . 33

5.3 Monimetodit . . . 35

6 Kompleksisuudenhallinta 38 6.1 Makrot ja sovellusaluekohtaiset kielet . . . 38

6.2 Pysyvät tietorakenteet suunnittelussa . . . 43

6.3 Tilanhallinta ja STM . . . 46

7 Yhteenveto 50

Viitteet 56

(6)

1 Johdanto

Ohjelmointi on kielineen ja paradigmoineen muuttunut kovasti viimeisen 50 vuoden ai- kana. Matalan tason konekielestä noustiin ensin proseduraaliseen ohjelmointiin, josta kehittyi myöhemmin vielä nykyään pääasiallisesti käytettävä oliopohjaisen ohjelmoin- nin paradigma. Vaikka kielet, tekniikat ja paradigmat ovat kehittyneet ja jalostuneet vuosien saatossa, on muuttuvan tilan hallinta ja jakaminen silti yhteistä kaikkien näi- den paradigmojen välillä yleisessä sovellutuksessa. Sama jaetun tiedon hallittu muut- taminen on edelleen sama ongelma kuin konekielten aikoina.

Sovelluksen sisäisen muistin käyttötapa ja allokointi on muuttunut merkittävästi vii- meisen 50 vuoden aikana. Matalan tason ja pienien muistimäärien näpräämisestä on siirrytty niin suuriin muistimääriin, että avoimen ja käytetyn muistin kirjanpito on ul- koistettu useaan kertaan, useille sovelluskerroksille. Siinä missä ennen ohjelmoija pys- tyi kirjaimellisesti pitämään paperilla ylhäällä käyttämiensä muistipaikkojensa osoit- teet, nyt eivät enää 32-bittiset kokonaisluvut riitä osoittamaan kaikkia samanaikaisessa käytössä olevia mahdollisia osoitteita.

Olemme tulleet pisteeseen, jossa voimme käyttää muistia ylellisesti. 50 vuotta sitten jouduttiin ohjelmoimaan säästeliäästi jokaista muistipaikkaa huolellisesti vaalien ja uu- delleenkäyttäen tehden muutoksia suoraan edellisen arvon päälle. Muistimäärän kehi- tyksestä huolimatta nykyaikainen ohjelmointikulttuuri yhä pitäytyy vanhoissa tavoissa, ja käsittelee muuttujia hienosti pukeutuneina muistiosoitteina. Muuttuvaa tilaa huonos- ti kätkevä olioparadigma ei onnistu olemaan proseduraalista ohjelmointiparadigmaa aidosti parempi tapa hallita kompleksisuutta. Syy on kielten käytännöissä, jotka kum- puavat vanhoista tekniikoista. Vaikka valtakielet ja suositellut tekniikat mahdollista- vatkin pysyvän tiedon kanssa työskentelyn, kielten vakiokirjastot ja käyttäjät edelleen kirjoittavat käytännössä glorifoituja globaaleja muuttujia, tässä vaiheessa pinnallisesti piilotettuna oppikirjaolioiksi kaikkine aksessorimetodeineen.

Sovellusprojektien laajuuksien jatkuvasti kasvaessa on yhä tärkeämmäksi muuttunut tarve hallita sovelluskoodin sisäisiä ominaisuuksia niin, että koodia on helppoa lukea, laajentaa ja ylläpitää. Hyvä arkkitehtuuri tekee koodistayksinkertaista, joka tarkoittaa että komponenttien sisäiset riippuvuudet ovat selkeitä ja mielellään vähälukuisia. Vas- taavasti monimutkainen tai kompleksinen koodi sisältää keskinäisiä riippuvuuksia, jot- ka ovat usein lisäksi implisiittisiä. Vaikka pysyvä tieto ei suoraan tee ohjelmakoodista

(7)

yksinkertaista, muuttuva tila kannustaa kompleksisiin arkkitehtuureihin.

Moni nykyaikainen valtakieli, Java ja C# mukaanlukien, sallii luokkien ja niiden jäsen- ten asettamisen muuttumattomaksi ja lopulliseksi. Koska tämä käytös ei ole oletuksena voimassa, on tällaisten apukeinojen vaikutus kokonaiskuvassa vähäistä ja koodikan- nasta riippuvaista. Valtakielten vakiokirjastojen idiomaattiset tietorakenneluokat ovat myös aina muuttuvia. Muuttuva tieto on oletusarvoinen ja annettu tosiasia, jota moni ei edes osaa kyseenalaistaa. Vladimir Bartolia mukaillen voisi sanoa, että mikään ei ole muuttumatonta: kaikki on sallittua.

Eräs hyvä tapa vanhoja tapoja vastaan on pakottaa kielen käyttäjät hyville tavoille.

Hyvät, vaikka massasta poikkeavat oletukset toisaalta nostavat oppimiskynnystä.

Kieliä, joissa muuttumaton tieto tulee oletuksena, ja joiden vakiokirjastot mukailevat tätä seikkaa, on ollut keskuudessamme vuosikymmeniä, mutta ne eivät ole ottaneet suuresti tuulta alleen suuren yleisön edessä. Funktionaalinen ohjelmointi on pitkään peräänkuuluttanut puhtaiden funktioiden ja “muuttumattomien muuttujien” tai arvojen puolesta. Tekniikka vaatii toimivan roskienkeruun ja enemmän työmuistia kuin muisti- paikkojen ja paikallaanmuuttelun kanssa työskentely, mutta vastineeksi muuttumatto- muus vapauttaa ohjelmoijat yhdestä isosta huolenaiheesta lähestulkoon kokonaan.

————

Clojure on funktionaalinen, dynaamisesti tyypitetty lisp-perheen kieli. Rich Hickeyn alkujaan suunnittelema ja kirjoittama kieli syntyi vuonna 2008. Kieli toimii Java Vir- tual Machinen ajonaikaisen ympäristön päällä, mutta kielestä on olemassa toteutuk- set myös JavaScriptille ja .NET-maailmasta tutun Common Language Runtimen pääl- le. Clojure on kirjoitettu vastaamaan monimutkaistuvien sovellusprojektien haasteisiin tuomalla ratkaisuja yksinkertaistavia ominaisuuksia helposti saataville käyttäjille.

Clojure on funktionaalinen kieli, jossa etusija on annettu puhtaille funktioille ja pysy- ville tietorakenteille. Se ei ole kuitenkaan puhtaan funktionaalinen kieli, vaan sivuvai- kutuksia on tarvittaessa helppo tehdä. Oliopohjaisesta ja funktionaalisesta lähestymis- tavasta kerron vertaillen luvussa 4.

Clojure on lisp-kieliperheen nuori jäsen, joka suorittaa ahkerasti, mutta jossa on tapa- na käyttää laiskoja jonoja, näin tarjoten välimallin ahkerasti ja laiskasti suorittaville

(8)

kielille. Muiden lispien tapaan Clojuressa on vain lausekkeita, ei käskyjä. Clojure on useimpien muiden lispien tavoin homoikoninen, eli käyttää omia tietorakenteitaan oh- jelmakoodinsa ilmaisemiseen. Makrot, jotka ovat funktioita koodista koodille, toimivat Clojuressa kuten useimmissa muissa lispeissä. Makroista kerron kohdissa 2.3 ja 6.1.

Clojuren päätös toimia olemassaolevan sovelluspinon päällä on sallinut kielelle useita etuja jo varhaisessa vaiheessa. Kielen alustana toimiva Java Virtual Machine toi ympä- ristönsä mukana vuosien ajan hioutuneet tulkit ja kääntäjät, joiden suoritusnopeus on suoraan valjastettu kielen juhdaksi. Lisäksi Javalle kirjoitetut kirjastot toimivat Cloju- ressa sellaisinaan, ja Clojuren ja Javan välinen yhteistoiminta on suunniteltu vaivatto- maksi. Tämän ansiosta Javan rikas kokoelma erilaisia kirjastoja on suoraan käytettä- vissä verraten tuoreessa kielessä. Tästä kerron luvussa 3.

Clojure on dynaamisesti ja vahvasti tyypitetty kieli. Käännös JVM-tavukoodiksi on- nistuu suorakäänteisesti, sillä kyseinen tavukoodiesitys on pitkälti tyypitöntä.

Clojure on tietokeskeinen: pysyvät ja muuttumattomat tietorakenteet ja oletuksena muuttumaton tieto avaavat mahdollisuudet käsitellä tietoa avoimemmin, koska pelkoa muiden suoritusyksiköiden tekemistä muutoksista ajonaikana ei ole. Funktionaalisuus ja puhtaiden funktioiden suosiminen painottaa kirjoittamaan oliopohjaisen kapseloin- nin sijasta tietoa muodosta toiseen käsitteleviä funktioita. Tämä sallii paremman ja yk- sinkertaisemman tavan tehdä isoja järjestelmiä. Tietokeskeisyydestä kerron luvussa 6;

pysyvistä tietorakenteista ja muuttumattomuudesta kohdassa 6.2.

Clojuren Software Transactional Memory (STM) -pino on kielen tarjoama ratkaisu muutoksenhallintaan, ja täyttää ACID-luokituksen kirjaimista kolme ensimmäistä. Si- säänrakennettu STM-pino toimii hallitun, säikeidenvälisen muutoksenhallinnan kans- sa. Eristetyt ja atomiset tapahtumat auttavat pitämään jaetun tiedon eheänä. Yhtenäisen ja pienen rajapinnan kautta tehty tiedon ja tilan hallittu muuttaminen helpottaa sekin taistelemaan kompleksisuutta vastaan. STM käsitellään kohdassa 6.3.

Ilmaisuongelma ja sen ratkaisut ovat eräs tapa kohdata kompleksisuutta sovelluksis- sa. Kun ohjelman vaatimuksiin tulee ajan saatossa uusia, usein vieraita komponentteja, kaiken saattaminen toimintaan ylläpidettävästi voi olla vaikeata. Tutkimme ilmaisuon- gelmaa ja sen ratkaisuja kohdassa 5.

(9)

2 Clojuren syntaksi ja perusteet

Tässä luvussa esittelen lyhyesti Clojuren keskeisen syntaksin ja kielen peruskäyttöä.

Kielen ollessa vielä nuori erilaisia muutoksia tapahtuu nyt ja jatkossakin. Esitetyt ra- kenteet toimivat ainakin kielen versioissa 1.4–1.7, joista uusimmassa tulee hieman muista eroavia viestejä.

Tämä luku jakautuu seuraavasti: kohdassa 2.1 esittelen Clojuren perusteita ja kielen suunnitteluratkaisuja. Kohdassa 2.2 käyn läpi kielen valmiina tarjoamat tietotyypit ja tietorakenteet. Kohdassa 2.3 esittelen lisp-kielille ominaista makro-ohjelmointia Clo- juren tapaan ja siihen liittyvät perusideat. Kohdassa 2.4 esittelen, kuinka Clojuresta käsin voidaan luoda ja kutsua Java-olioita ja niiden metodeita.

2.1 Kielen perusteet

Clojure on dynaamisesti ja vahvasti tyypitetty ohjelmointikieli, joka käännetään Java Virtual Machine -tavukoodiksi ennen suoritusta. Dynaaminen tyypitys tarkoittaa, et- tä kielen tyyppejä ei tarkisteta staattisesti käännöksen aikana. Vahva tyypitys toisaalta tarkoittaa, että kieli ei tulkitse ja vaihda tyypitettyjä arvoja vapaasti ilman käyttäjän erillistä ilmoitusta. Clojure on ahkerasti suorittava Lisp-sukuinen kieli, joka perii mm.

funktiokutsujen etuliitenotaation ja S-lausekkein kirjoitettavan syntaksin. Tässä koh- dassa esittelen hyvin tiiviisti kielen käytön oleelliset piirteet siinä määrin kuin tässä työssä sitä tietämystä tarvitaan. Kattavat tekstit kielen yksityiskohtiin ja käyttöön löy- tyvät kirjoittajien Emerick et al. (2012) ja Fogus et al. (2011) kirjoista.

Clojure on täysin lausekeorienteinen kieli: jokainen lauseke tuottaa aina arvon. Kuten muissa lispeissä, Clojuren lausekkeet ovat niinsanottujaS-lausekkeita, jotka ovat joko yksittäisiä arvoja, tietorakenteita tai funktiokutsuja.

Funktiokutsutsuoritetaan kirjoittamalla funktion nimi sulkujen sisään ensimmäiseksi ja mahdolliset argumentit sanavälein eroteltuna sen perään.

user=> (+ 1 2 3)

#=> 6

user=> (/ (+ 1 2) (- 4 1))

#=> 1

(10)

Clojuressa on kolmenlaisia kutsuttavia olioita: funktioita, makroja ja erikoiskutsuja (special forms). Funktiot ovat perinteisiä mekanismeja tuottaa syötedatasta tulosda- taa. Makrot ovat käännöksen aikana suoritettavia funktioita, jotka tuottavat syötekoo- dista tuloskoodia. Erikoiskutsut ovat kielen sisäänrakennettuja erikoistapauksia, joilla Clojure-koodi sidotaan osaksi JVM-tavukoodia. Erikoiskutsuja on lispeissä tyypilli- sesti niin vähän kuin mahdollista: vain ne kielen ydintoiminnot, joita ei voida toteuttaa funktioina tai makroina, ovat erikoiskutsuja.

Funktion luominentapahtuufn-erikoiskutsulla. Kutsusyntaksissa merkitään funktion saamat argumentit, jos sellaisia on, hakasulkujen sisään. Argumenttien nimet merki- tään hakasulkujen sisään tyhjein eroteltuna. Kutsun saamat loput argumentit ovat S- lausekkeita, jotka tuottavat uusia arvoja tai kutsuvat muita funktioita. Funktion suo- rituslohko on joko tyhjä tai se koostuu vähintään yhdestä lausekkeesta. Lausekkeet suoritetaan peräkkäisjärjestyksessä, ja viimeinen suoritettava lauseke määrää samalla funktion paluuarvon.

user=> (fn [x y] (+ x y))

#=> #<user$eval318$fn__319 user$eval318$fn__319@793057d2>

user=> ((fn [x y] (+ x y)) 2 10)

#=> 12

Arvojen nimeäminen globaalilla tasolla tapahtuu def-erikoiskutsua käyttämällä.

Funktioiden määrittelyä ja nimeämistä tapahtuu verraten paljon, ja sitä varten avuk- si löytyy erityinendefn-makro.

(def vastaus 42)

(def plus ; sama kuin jälkimmäinen (fn [a b]

(+ a b)))

(defn plus ; sama kuin edellinen [a b]

(+ a b))

Arvojen nimeäminen lokaalissa näkyvyydessä tapahtuulet-makroa käyttämällä. Van- hoja arvoja voi peittäälet-ympäristön suorituksen ajaksi.

(11)

user=> (def vastaus 42)

#=> #’user/vastaus user=> vastaus

#=> 42

user=> (let [vastaus 10 luku 5]

(+ vastaus luku))

#=> 15

user=> vastaus

#=> 42

Listoja voi rakentaa for-makron avulla. Huomioitavaa on, ettäfor ei ole perintei- nen imperatiivinen silmukkarakenne, vaan sillä tuotetaan uusia listoja deklaratiiviseen tapaan.

user=> (for [x (range 10)]

(* x x))

#=> (0 1 4 9 16 25 36 49 64 81) user=> (for [x (range 10)

:when (even? x)]

x)

#=> (0 2 4 6 8)

Ehtolauseettoteutetaanif-erikoiskutsun avulla. Clojuren ifsisältää aina “then”- ja

“else”-haarat. Jälkimmäisen puuttuessa se tulkitaan tyhjeenänil.

user=> (if (< 5 30) :totta :valhetta)

#=> :totta

Ketjutettuja ehto-lauseke-kokoelmia kirjoitetaan cond-makron avulla. Järjestyksessä ensimmäisen toteenkäyvän ehdon parina oleva lauseke lasketaan:

user=> (cond

(12)

(> 1 5) :foo (< 2 2) :bar (< 1 2) :totuus)

#=> :totuus

Arvon mukaan valittavaa suorituspolkua varten on kirjoitettu valmiscase-makro, joka vastaa esimerkiksi Javan switch-käskyä. Toisin kuin Javan switch, Clojuren case tukee myös rakenteisia arvoja.

user=> (case (+ 2 2) 3 :ei

4 :joo 5 :foo)

#=> :joo

Ehtolauserakenteiden suoritushaarat koostuvat aina yksittäisistä lausekkeista. Jos sivu- vaikutusten takia on tarvetta kirjoittaa useita funktiokutsuja samaan haaraan, voidaan käyttäädo-erikoiskutsua:

user=> (if (even? 2) (do

(println "Kakkonen on tasainen.") :tosi))

"Kakkonen on tasainen."

#=> :tosi

Listan tai nimen lainaamalla käyttäen quote-kutsua tai ’-lyhennettä ilmoitetaan kääntäjälle, että seuraavana tulevan nimen mahdollista arvoa ei tule hakea, tai seu- raavana tulevaa listaa ei tule tulkita funktiokutsuna. Kaikki listan sisään jäävä jätetään myös laskematta.

user=> vastaus

#=> 42

user=> ’vastaus

(13)

#=> vastaus user=> (+ 1 2)

#=> 3

user=> ’(+ 1 2)

#=> (+ 1 2)

2.2 Tietotyypit ja tietorakenteet

Tässä kohdassa esittelen Clojuren skalaarityypit tai atomiset tyypit ja kielen hyvin tukemat tietorakenteet. Lisäksi esittelen muutamia rajapintoja tai Clojure-jargoniksi protokollia, joita Clojuren ja Javan tietorakenteet toteuttavat. Esimerkiksi alakohdas- sa 2.2.3 käsiteltävä jonoabstraktio on ulkoisesti tavallinen Javan rajapinta ja Clojuren protokolla.

2.2.1 Skalaarityypit

Clojuren tietotyypit ovat pitkälti samat kuin Javassa, mutta lisäyksiäkin on. Valmiita skalaarityyppejä kieli määrittelee seuraavasti:

Kokonaisluvut ja liukuluvut ovat Javan ja JVM:n laatikoituja (boxed) tyyppe- jä, jotka voivat Clojuressa tarvittaessa korottua (promote) automaattisesti BigInteger- taiBigDecimal-tyyppisiksi, mikäli laskutoimituksen tulos ei sovi olemaan normaali kokonaisluku. Vaihtoehtoisesti suurten lukujen kokonaisluku- aritmetiikassa voidaan heittää poikkeus, jos operaation tuloksena syntyisi luvun ylivuoto.

Rationaaliluvuilla voidaan esittää tarkkoja murtolukuesityksiä. Rationaalilukuja voi esittää prefiksinotaation lisäksi algebrallisella notaatiolla:(/ 5 4)ja5/4tuot- tavat samanRatio-tyyppisen arvon.

Totuusarvot truejafalsetoimivat totuusarvovakioina kielessä. Lisäksi kielen sään- nöt eri arvojen totuusarvoille ovat yksinkertaiset: nil ja false ovat epätosia, kaikki muut arvot ovat ehtolausekkeissa tosia.

Tyhjearvonil on kaikille tyypeille kelvollinen tapa ilmaista, että arvoa ei ole. Toisin

(14)

kuin Javannull-vakiota, Clojuressa tyhjettänilkäytetään luontevammin osana algoritmeja ja paluuarvoja, sikäli kun sen käyttö sopii tilanteeseen.

Merkkivakiot kirjoitetaan kenoviivan avulla: kirjainakirjoitetaan Clojuressa\a. Li- säksi muutama tyhjemerkki voidaan kirjoittaa pitkässä muodossa:\newlineja

\spacetuottavat rivinvaihdon ja välilyönnin vastaavasti.

Merkkijonot ovat vastaavat kuin Javassa: ne toimivat skalaareina, mutta merkkijonon voi tulkita myös vektorina merkkejä. Merkkijonot kirjoitetaan kuin useimmissa kielissä:"merkkijono".

Säännölliset lausekkeet ovat Javan java.util.regex.Pattern-olioita, mutta joi- den kirjoittamiselle on Clojuressa tiivis syntaksi: #".*bar" kääntää valmiin Pattern-olion käännösaikana.

Symbolit kuvaavat kielen nimiä. Symbolit ovat kielen ensiluokkaisia (first-class) muuttujanimiä, joita voi käsitellä ohjelmallisesti. Useimmissa lisp-kielissä sym- boleilla on suurempi painoarvo erilaisina lippuarvoina, mutta Clojuressa käyte- tään tähän tarkoitukseenavainsanoja.

Avainsanat ovat itseisarvollisia vakioita, joita käytetään erilaisia lippuina ja kuvaus- ten avaimina. Avainsanat merkitään kirjoittamalla kaksoispiste symbolin eteen:

:off. Esimerkin tyypillisestä avainsanojen käytöstä kuvauksissa antaa kuva 1 sivulla 11. Muissa kielissä analogisia välineitä ovat luettelotietotyypit (Enum) ja staattiset vakiot.

Funktiot ovat funktionaaliseksi kutsutulle kielelle ensiasteisen tärkeitä arvoja. Cloju- ressa funktioita luodaan(fn [a b c] (...))-erikoiskutsulla.

Referenssityypeillä hallitaan atomisin tapahtumin koordinoitua muutostentekoa. Sa- manaikaisessa suoritusympäristössä, kuten säieohjelmoinnissa, muuttuvan ja jaetun tiedon tarve on todellinen. Referenssityypeillä yhtenäistetään ja forma- lisoidaan kaikki paikallaan tehtävät muutokset helposti hallittavissa oleviin ab- straktioihin. Referenssityyppejä ovatatom,agent,varjaref.

Referenssityypit osoittavat pysyviin arvoihin, ja kohteen arvoa haettaessa (dere- ference) palautetaan pysyvä arvo kohteen senhetkisestä tilasta, eli kutsunaikai- nenotos(snapshot). (Fogus et al., 2011)

(15)

2.2.2 Tietorakenteet

Clojuressa on neljä pääasiallista ja hyvin tuettua tietorakennetta: listat, vektorit, jou- kot ja kuvaukset. Skalaarityyppien tapaan kaikki Clojuren tietorakenteet ovat muut- tumattomia. Muutokset kuhunkin tietorakenteeseen tuottavat uuden version ilmenty- mästä. Rakenteellisen jakamisen ansiosta yhteiset osat tietorakenteista voidaan uudel- leenkäyttää muistinkäytön optimoimiseksi. (Okasaki, 1999, luku 2) Muuttumattomuus ja rakenteellinen jakaminen yhdessä muodostavat pysyvän(persistent) tietorakenteen käsitteen.

Dynaamisesti tyypitetylle kielelle tyypillisesti kaikki tietorakenteet ovat heterogeeni- siä, eli voivat sisältää minkätyyppisiä arvoja tahansa, mukaanlukien muut tietoraken- teet ja Java-natiivit oliot. Clojure perii edeltävistä lispeistä sen perinteen, että elementit erotellaan toisistaan tyhjeiden avulla. Pilkut ovatkin Clojure-kääntäjän näkökulmasta sanavälejä.

Listatovat lisp-henkisesti yhteen suuntaan linkitettyjä listoja. Sen seurauksena lisäyk- set ja poistot alusta ovat vakioaikaisia ja elementin hakeminen lineaarista. Koska listat ovat linkitettyjä ja koska Clojuressa on kääntäjän tukea muille usein käytetyille tieto- rakenteille, listoja ei käytetä tyypillisessä Clojure-ohjelmassa suuresti. Tässä Clojure poikkeaa perinteisemmistä lisp-perheen kielistä. (Emerick et al., 2012)

Clojure-ohjelmakoodi kirjoitetaan samalla notaatiolla kuin kielen listat, mikä tekee kielestä homoikonisen: kielen ohjelmakoodi on samalla saman kielen dataa. Sik- si tavallinen lista tulkitaan ensisijaisesti funktiokutsuna. Listoja voi käyttää tietora- kenteina lainaamalla lista käyttämällä quote-erikoiskutsua kohdan 2.1 mukaisesti:

(quote (1 2 3))ja’(1 2 3)ovat ekvivalentteja tapoja merkitä samaa kolmen al- kion listaa.

Vektorit ovat tyypillisesti kiinteämittaisia ja suorasaantisia (random-access) tauluk- korakenteita. Clojuressa vektorit käyttäytyvät samoin, vaikka teknisesti vektorit ovat 32-haaraisia hakupuita. Tämä suunnitteluratkaisu syntyi pysyvyysvaatimuksen seu- rauksena. Vektoreiden iterointi on edelleen aikavaativuusluokkaaO(n), vektorin koon laskeminen on vakioaikainen operaatio ja elementin hakeminen indeksin mukaan on O(log32n).

Vektori kirjoitetaan hakasulkujen avulla:[1 2 3],[[1] :foo]ja[[]]ovat esimerk-

(16)

;; Kolme avain-arvo-paria sisältävä kuvaus {1 "yksi"

2 "kaksi"

3 "kolme"}

;; Avaimet voivat olla rakenteisia {[0 0] :origo

[0 1] :pelaaja}

;; Sisäkkäiset kuvaukset ovat idiomaattisia {:nimi "Mikko"

:ikä 24

:koulutus {:peruskoulu 2005 :lukio 2009 :kandi 2011}}

Kuva 1: Esimerkkejä Clojuren kuvausten syntaksista.

kejä vektorinotaatiosta. Tyhjää vektoria merkitään seuraavasti:[].

Kuvaukset eli sanakirjat (maps) ovat avain-arvo-kokoelmia, joilla ei ole välttämättä järjestystä. Kuvausabstraktiota vasten kielessä on valmiina kolme erilaista toteutusta:

hajautukset (haku on O(log32n)), hakupuut (elementit ovat annetun kriteerin mukai- sessa järjestyksessä ja haku on O(log2n)) ja lisäysjärjestyksessä pysyvä “array map”

-toteutus, jolla on heikko, lineaarinen hakuaika.

Kuvaus kirjoitetaan aaltosulkujen sisään avain-arvo-pareina. Kuvassa 1 esittelen muu- taman kuvauksen ladottuna kielelle idiomaattiseen tapaan.

Joukotovat edellä esitetyn kuvauksen erityistapaus, jossa säilötään vain avaimia. Jouk- koja saa hajautetulla tai lajitetulla toteutuksella. Joukkoja merkitään Clojuressa risuai- tasymbolin ja aaltosulkujen kanssa:#{1 2 3 4}.

2.2.3 Kokoelmien toteuttamista abstraktioista

“It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.” – Alan Perlis Structure and Interpretation of Computer Programs -kirjan esipuheessa. (Abelson et al., 1996)

Clojuren kokoelmat toteuttavat monia abstraktioita yhtenäistääkseen kokoelmien kä-

(17)

sittelyä. Kaikkia neljää päätietorakennetta voidaan käsitellä yhtenäistetyin funktioin, jotka ovat tätenpolymorfisia. Kaikille kokoelmille määritellään useita tyypillisiä funk- tioita, kutencount alkiomäärän laskemista varten,conj(lyh. conjoin) uusien alkioi- den liittämiseksi mukaan jaempty, joka tuottaa polymorfisesti tyhjän, samaa tyyppiä olevan kokoelman. Lisäksi kokoelmat toteuttavat funktionseq, jolla kokoelmasta tuo- tetaan jononäkymä (sequence). (Emerick et al., 2012, luku 3) Jono on yhtenäistetty tapa lukea kaikkia tietorakenteita jonomaisesti. Jonoabstraktio tarjoaa funktionaalisen vastineen oliopohjaisissa kielissä tavatuille iteraattoreille.

Kaikki kokoelmat voidaan kääntää jonoiksi seq-funktion avulla. Joukot, vektorit ja listat esittäytyvät peräkkäismäisinä jonoina kutsun jälkeen. Kuvaukset puretaan jonok- si avain-arvo-vektoreita. (Emerick et al., 2012, luku 3) Jonoilla on omat metodinsa, kutenfirstjarestjonon ensimmäistä ja muita alkioita varten. Koska tyhjätkin ko- koelmat tulkitaan Clojuren vertailuissa tosiksi totuusarvoiksi, on idiomaattista testata kokoelman tyhjyys tarkastelemalla kokoelmaa jonoabstraktion takaa:

user=> (seq [1 2 3])

#=> (1 2 3) user=> (seq [])

#=> nil ; epätosi ehtolauseissa.

user=> (seq {:nimi "Mikko" :ikä 32})

#=> ([:nimi "Mikko"] [:ikä 32])

Jonoabstraktio on Clojuressa läpitunkeva ilmiö. Clojure-kokoelmien lisäksi jonoab- straktion toteuttavat kaikki java.util.* -tietorakenteet ja Java-kuvaukset. Lisäksi kaikki JavanIterable-rajapinnan toteuttavat oliot toimivat Clojuressa suoraan jonoi- na. Javan primitiiviset taulukot ja jopa tyhje null toteuttaa myös rajapinnan. Java- kehittäjä voi itse varmistaa jonoyhteensopivuuden toteuttamalla omissa luokissaan clojure.lang.Seqable-rajapinnan. (Emerick et al., 2012)

Pääosa kokoelmia käsittelevistä Clojuren standardikirjaston funktioista, kuten map, filter ja reduce, käsittelee argumenttina saamiaan kokoelmia implisiittisesti jo- noina. Siispä esimerkiksi kuvausten käyttämiseksi map-funktion läpi tavataan käyttää funktiota, joka saa argumentikseen sekä avaimen että arvon. Funktiomappalauttaa lop- putuloksen jonona, joten uudelleenkäännös alkuperäiseen tietorakenteeseen tulee teh- dä tarvittaessa itse. Seuraava esimerkki poimii kuvauksen avaimet talteen ja valaisee

(18)

kuvauksista tehtyjen jonojen iterointia:

user=> (def kuv {:yy 1 :kaa 2 :koo 3})

#=> #’user/kuv

user=> (map first kuv)

#=> (:koo :kaa :yy) ; järjestys voi vaihdella

2.3 Makrot

Lisp-kielenä Clojuressakin on tukimakrofunktioilleeli makroille. Kun rutiinin kääri- minen funktioksi ei onnistu, voidaan kirjoittaa funktion sijaan makro. Makrot suori- tetaan käännöksen aikana, eivätkä ne ole ensiarvoisia olioita. (Emerick et al., 2012, luku 5: When To Use Macros) Koska makroilla käsitellään varsinaisen ajonaikaisen tiedon sijaan staattista ohjelmakoodia muodosta toiseen, voidaan makroilla toteuttaa uusia, yksinkertaisia ehtorakenteita idiomaattisesti. Esimerkiksi Graham (1994) onkin sitä mieltä, että makrofunktiot tekevät lisp-kielistäohjelmoitavia ohjelmointikieliä.

Kuvassa 2 esitän samanif-not -tyylisen ehtorakenteen sekä funktiona että makrona.

Rakenne käyttäytyy kuinif, mutta nyt ehdon on oltava epätosi “then”-haaraan men- täessä. Koska ifnot on funktio ja Clojure suorittaa ahkerasti funktiokutsujen argu- mentit auki ennen kutsua, rivillä A tehtävä funktiokutsu laskee auki kaikki kolme ar- gumenttiaan suorittaen kaikki sivuvaikutukset samalla. Varsinaisen funktion suorituk- sen alkaessa funktio näkee vain aukilasketut argumentitfalse, niljanil. Vastaava makro saa syötteenään alkuperäiset, laskemattomat argumentit. Kuvassa 3 demonstroin tätä eroa.

Makroversioifnot-msuoritetaan sitä vastoin jo käännösaikana. Sen saamat argumen- tit näkyvät makrolle Clojure-koodina. Tuloksena makro tuottaa uutta Clojure-koodia, jolla korvataan koko alkuperäinen makrokutsu. Tätä toimenpidettä kutsutaanmakro- laajennukseksi(macro expansion), ja tämä tapahtuu funktion kääntämisen aikana. Kos- ka makrot voivat laajentua uusiksi makrokutsuiksi, laajentamista jatketaan rekursiivi- sesti kunnes syntyvässä koodissa ei ole enää kutsuja muihin kuin funktioihin tai eri- koiskutsuihin.

(19)

;; Funktiokutsu: kaikki kolme argumenttia lasketaan

;; auki ennen kutsua.

user=> (defn ifnot [expr then else]

(if (not expr) then else))

user=> (ifnot (= 4 2) ;; (A)

(println "neljä ei ole kaksi") (println "neljä on kaksi")) neljä ei ole kaksi

neljä on kaksi

#=> nil

;; Makroversio saa argumenttinsa puhtaana koodina,

;; jota ei ole suoritettu.

user=> (defmacro ifnot-m [expr then else]

(list ’if (list ’not expr) then else))

user=> (ifnot-m (= 4 2) (println "Ei sama") (println "Sama!")) Ei sama

#=> nil

Kuva 2: Funktioiden ja makrojen argumenttien suorittamisesta.

user=> (defn funk [& args] (println args)) user=> (funk (+ 3 4) (= 4 4))

(7 true)

#=> nil

user=> (defmacro mak [& args] (println args)) user=> (mak (+ 3 4) (= 4 4))

((+ 3 4) (= 4 4))

#=> nil

Kuva 3: Funktioiden ja makrojen argumenteista

(20)

2.4 Kommunikointi Java-kirjastojen kanssa

Clojuren ja Javan välillä vallitsee kuvainnollinen symbioosi. (Fogus et al., 2011) Ja- valla kirjoitettuja luokkia ja koodia voi käsitellä Clojuresta käsin selkeällä syntaksilla.

Seuraavassa esittelen Foguksen ja Houserin (2011) kirjasta lainattuja esimerkkejä Clo- juren ja Javan välisestä yhteistoiminnasta.

Staattisten vakioiden hakeminen ja staattisten metodien kutsuminen tapahtuu käyttä- mällä kauttaviivaa luokan ja jäsenen välissä.

java.util.Locale/JAPAN

(Math/sqrt 9)

#=> 3.0

Idiomaattinen tapa luoda instansseja Java-luokista on käyttää luokan nimeä ja pistettä sen perässä.

(java.util.HashMap. {"koo" 10})

#=> #<HashMap {koo=10}>

Piste nimen edessä tarkoittaa instanssin jäsenen hakemista tai metodin kutsua:

(.x (java.awt.Point. 10 20))

#=> 10

(.divide (java.math.BigDecimal. "42") 2M)

#=> 21M

Muutettavat ja julkiset oliojäsenet voidaan muuttaa erityisenset!-kutsun avulla. Täs- sä julkistaPoint-olionx-jäsentä muutetaan luvusta 1 lukuun 4. Muutokset kohdistuvat kertaalleen luotuunPoint-olioon, jonka koordinaatit ovat laskennan jälkeen (4,2). Tä- tä erityiskutsua tarvitaan vain ja ainoastaan julkisia ja muutettavia jäseniä sisältävien Java-olioiden käsittelemiseksi.

(21)

(set! (.x (java.awt.Point. 1 2)) 4)

Lisäksi Java-entiteettejä voidaan tuoda lyhennetyin nimin Clojure-nimiavaruuksiin import-kutsulla:

user=> (import java.util.HashMap)

#=> java.util.HashMap user=> (HashMap. {})

#=> #<HashMap {}>

Clojure tarjoaa..-makron ketjukutsujen siistimiseksi. Makron avulla Javassa tyypillis- ten ketjutettujen kutsujen kääntäminen Clojureksi on suoraviivaista, eikä muuta koodin lukusuuntaa:

// Java

new java.util.Date().toString().endsWith("2010");

;; Clojuren tavallinen pistenotaatio:

(.endsWith (.toString (java.util.Date.)) "2010")

;; Sama ..-makroa käyttäen:

(.. (java.util.Date.) toString (endsWith "2010"))

(22)

3 Sovellusalustojen merkitys pienille kielille

Loppukäyttäjille suunnattuja sovelluksia ei enää kirjoiteta suoraan tietylle laiteympä- ristölle, vaan selvä osa sovelluksista käännetään välitasonsovellusalustoille (applica- tion platform), esimerkiksi Java Virtual Machinelle (JVM) tai Windows-ympäristössä vakiintuneelle Common Language Runtimelle (CLR). Näin meneteltäessä luovutetaan matalimpien tasojen toteuttaminen ja käyttöjärjestelmän kanssa keskusteleminen käy- tetylle alustalle. Sovellusalustat tarjoavat tyypillisesti myös monipuolisen valikoiman vakiokirjastoja nopeata korkean tason kehitystä varten.

Sovellusalusta toimii erillisena osana käyttöjärjestelmän päällä, ja siten sellaisen käyt- tö voi edesauttaa ohjelman siirrettävyyttä käyttöjärjestelmien välillä. Esimerkiksi Javan 90-luvun myyntipuheisiin sisältyi lupaus “kirjoita kerran, aja kaikkialla” -tyylisestä so- velluskehityksestä. (Sun Microsystems, 1996) Samasta sovellusalustasta voi olla usei- ta ilmentymiä samanaikaisessa ajossa suorituksessa olevien sovellusten tarpeiden mu- kaan. Yhden sovellusalustan samanaikainen hajauttaminen usealle eri käyttöjärjestel- mälle tai yksikölle ei kuitenkaan kuulu vaatimuksiin.

Sovellusalusta voi olla pelkkä kattava ohjelmakirjasto eli sovelluskehys (application framework), jolloin kirjoitettu koodi käännetään ja linkitetään kehyksen kanssa yhdek- si sovellukseksi. Sovelluskehykset voivat toimia usealla käyttöjärjestelmällä ja alus- talla, mutta niille kirjoitetut sovellukset joudutaan tyypillisesti kääntämään kullekin ympäristölle erikseen.

Sovellusalusta voi toisaalta toteuttaa oman välikooditason, jolloin alustalle tehtyjä so- velluksia voidaan jakaa eteenpäin käännettynä tavukoodina. Suoritettaessa käännettyä sovellusta sovellusalustan omasuoritusaikainen ympäristö (runtime environment) te- kee lopullisen muunnoksen käyttöympäristön mukaiselle laitteistolle. Välikoodikään- nöstä ei tarvitse tehdä jokaiselle suunnitellulle kohdeympäristölle erikseen, vaan mah- dollisesti binäärisen välikoodin jakelu riittää.

Kohdassa 3.1 käsittelen Clojurenkin kotina toimivan Java Virtual Machinen (JVM) tek- nisiä ominaisuuksia ja sitä, kuinka JVM tukee Javan lisäksi muita kieliä. Kohdassa 3.2 vertailen lyhyesti Microsoftin Common Language Runtimen (CLR) teknisiä ominai- suuksia hieman vanhempaan JVM-alustaan nähden. Kohdassa 3.3 käsittelen nykyai- kaisissa verkkoselaimissa toimivan JavaScriptin saamaa uutta roolia alustariippumat- tomana kielenä ja pienenä ympäristönä.

(23)

3.1 Java Virtual Machine

Tässä kohdassa esittelen Java Virtual Machinen ominaisuuksia kielialustana: sen puut- teita ja tapoja kiertää alustan ongelmia.

Java Virtual Machine (JVM) on sovellusalusta, joka sisältää kattavat valmiskirjas- tot ja ajonaikaisen suoritusympäristön (Java Runtime Environment; JRE), joka tulk- kaa binääristä tavukoodia kääntäen sitä optimoivasti käytön mukaan (Just In Time - kääntäminen). JIT-käännön ansiosta JVM-sovellukset voivat käytännössä olla yhtä no- peita kuin C-kielellä kirjoitetut ohjelmat. (Serpette et al., 2002)

JVM:n käyttämä tavukoodiesitys ei ole sidottu alkuperäiseen kirjoituskieleen, vaan se on kieliriippumatonta tavukoodia. Esimerkiksi Java-ohjelmaa käännettäessä tuloksena syntyvä tavukoodi on JVM-tavukoodia; kaikki viitteet tavukoodin lähdekielestä ovat hävinneet käännöksessä. Tämän seikan ansiosta Clojure pystyy JVM-kielenä hyödyn- tämään kaikkea alustalle kirjoitettua koodia täysin yhteensopivasti. (Emerick et al., 2012, luku 9)

JVM-tavukoodi on lisäksi alustariippumatonta ja siten sellaisenaan siirrettävää ym- päristöstä toiseen. (Serpette et al., 2002) Sovelluksen toiminnan takaamiseksi pitäisi riittää, että kohdeympäristölle on olemassa sopiva versio JRE:stä.

Clojuren ohella moni muukin kieli toimii JVM:n päällä: Scala (Odersky, 2014b), Groo- vy (2014), Jython (2014) ja JRuby (2014) ovat muutamia esimerkkejä. Erityisesti kaksi jälkimmäistä kieltä ovat läheisiä klooneja esikuvistaan, joilla on kyllä omatkin suori- tusympäristönsä. Yleisimmät syyt tavoitella JVM-yhteensopivuutta tällä tavalla ovat JRE:n kehittyneet ajonaikaiset optimoijat, hyvä siirrettävyys ja kypsyneet sovellus- ja luokkakirjastot. (Emerick et al., 2012; Bres et al., 2004)

JRE tukee Serpetten et al. (2002) mukaan dynaamista tyyppitarkastusta ja roskien- keruuta, jotka auttavat toteuttamaan dynaamisesti tyypitettyjä kieliä, kuten Scheme- johdannaisia, JVM:n päälle. Clojure myös dynaamisesti tyypitettynä kielenä tarvitsee tehokkaasti suoritettavaa JVM-tavukoodia varten tämänlaista tukea.

JRE on kuitenkin alunperin suunniteltu vain Javan suoritukseen, ja nykyisinkin se on suunnattu korkean tason oliopohjaisten kielien alustaksi. (Serpette et al., 2002) Tämä näkyy edelleen tavukoodikäskyjen oliokeskeisyydessä. Esimerkiksi vapaita funktioita saati sulkeumia JRE ei Javan tapaan tunne lainkaan. (Monteiro et al., 2005; Serpette

(24)

et al., 2002)

JRE ei myöskään tue rekursiivissa funktiokutsuissa hyödyllistä häntäkutsujen opti- mointia (tail call optimization; TCO). Tällöin rekursiivisia rakenteita hyödyntävät kielet joutuvat joko hyväksymään mahdolliset pinoylivuodot, toteuttamaan hitaam- pia vaihtoehtoja tai kirjoittamaan ylimääräistä kääntäjälogiikkaa häntärekursiokutsu- jen purkamiseksi ilman suoritusympäristön tukea. (Bres et al., 2004)

Nämä puutteet on kuitenkin tehty kierrettäviksi. Vapaat funktiot voidaan kirjoittaa julkisten luokkien julkisina ja staattisina metodeina. Sulkeumat voidaan vastaavasti toteuttaa lisäämällä näille funktio-olioille kerran alustettuja yksityisiä jäsenmuuttu- jia. (Monteiro et al., 2005) Javan versiossa 8 on myös tuki nimettömien funktioiden vastineelle,funktionaalisille rajapinnoille. Tämä tapahtuu muiden JVM-kielten tapaan kääntämällä käytetyt rakenteet JVM:n tuntemille oliokäsitteille. (Weiss, 2014; Oracle, 2015)

Clojuressa päätettiin ratkaista tämä kertyvän pinon ongelma toteuttamalla kieleen ek- splisiittinenrecur-kutsu, jonka käyttö takaa häntäkutsujen optimoinnin. Kutsua käyt- tämällä suoritus hyppää joko määriteltävänä olevan funktion alkuun tailoop-makron alkuun. Käytännössä loop on yksinkertainen nimettömän funktion sovellus. Koska häntäkutsuja voi tehdä vain suoritusrungon lopussa, kääntäjä kykenee tunnistamaan recur-kutsun virheellisen käytön, ja antamaan käännösvirheen käännösaikana. Impli- siittinen häntäkutsuoptimointi voisi ilman kunnollisia kääntäjädiagnostiikoita jättää valheellisen vaikutelman tehdyistä optimoinneista, ja altistaa ajonaikaisille pinonyli- vuodoille.

Useamman funktion välinenkeskinäinen rekursio(mutual recursion) toteutetaan yleis- luonteisella trampoliinitekniikalla: jos rekursio ei lopu, suorittava funktio ei kutsu suo- raan seuraavaa funktiota, vaan tekee sulkeuman tarvittavista argumenteista, ja palauttaa sen välittäjäfunktiolle. Koska palautettavassa sulkeumassa on kaikki seuraavan iteraa- tion tarvittavat argumentit mukana, välittäjäfunktio voi kutsua tätä nolla-argumenttista funktiota jatkaakseen rekursiota, eikä sen tarvitse tuntea funktioita. Rekursio loppuu ennaltasovitun käytännön perusteella, Clojuressa silloin kun joku funktioista palaut- taa muuntyyppisen arvon kuin funktion. Clojuren vakiokirjastoon kuuluva funktio trampoline(Clojure, 2014,core.clj:5790) toimii juuri näin kuvatulla tavalla.

(25)

3.2 Common Language Runtime ja JRE

Microsoftin .NET-ympäristö lanseerattiin vuonna 2001, kuusi vuotta JVM:n jälkeen.

Ympäristön suoritusaikainen ympäristö on nimeltään Common Language Runtime (CLR) ja välimuotoinen tavukoodi tunnettiin ensin nimellä Microsoft Intermediate Language (MSIL) ja nyttemmin nimellä Common Intermediate Language (CIL). Tässä kohdassa esittelen, mitä ominaisuuksia .NET tukee vaihtoehtoisten kielten suunnitteli- joille.

CLR on JRE:n tapaan pinopohjainen virtuaalikone, joka suorittaa alustariippumaton- ta, välimuotoista tavukoodia. Microsoftin ylläpitämä suoritusympäristö toimii vain Windowsilla, mutta avoin Mono-projekti toimii hyvin kaikilla suurilla ympäristöil- lä. Kaikkia .NET-kirjastoja ei ole kirjoitettu alustariippumattomasti, mutta itse kieli, tavukoodi ja ydinkirjastot ovat sellaisenaan suoritettavissa Monolla. (Mono Project, 2015) Microsoft on myös tuonut myös Linuxilla ja Mac OS X:llä toimivan .NET Co- re -nimisen ratkaisun, jonka määränä on tarjota alustariippumaton suoritusympäristö .NET-ympäristön ydinkirjastoille. (Microsoft, 2015) Projektin skaala on siis vastaava kuin Monon.

CLR:n välikoodikäskyt ovat JRE:n tapaan vahvasti oliopohjaisen paradigman mukai- sia. (Singer, 2003) CLR tukee monimuotoisuutta ja roskienkeruuta, ja sisältää JIT- kääntäjän, kuten myös JRE. (Bres et al., 2004)

JRE:stä poiketen CLR tukee monipuolisemmin erilaisia kielirakenteita, joiden avul- la ympäristölle on helpompi kirjoittaa kääntäjiä muistakin kuin oliopohjaisista kielis- tä. (Bres et al., 2004; Singer, 2003) CLR tukee suoraan esimerkiksi luettelotyyppe- jä (enumerations), rakenteisia tyyppejä (structures) ja arvotyyppejä (value types) eli tyyppejä, joiden alkioiden identtisyys määritellään sisällön perusteella.

Lisäksi CLR osaa optimoida rekursiiviset häntäkutsut pinoa säästäviksi silmukkara- kenteiksi. CLR tukee sulkeumia paremmin kuin JRE: ei suoraan, mutta ympäristöön kuuluviendelegaattienvälityksellä paremmin kuin JRE:n oliopohjaisissa ratkaisuissa.

Ympäristö tukee myös puhtaita funktioita ja funktio-osoittimia. (Bres et al., 2004)

(26)

3.3 JavaScript-alustat: IonMonkey, V8 ja muut

ECMA-standardoitu EcmaScript (ECMA-262, 2011) on dynaaminen ja heikosti tyy- pitetty selaimille suunnattu skriptikieli, ja se tunnetaan paremmin web-sivuille dynaa- mista sisältöä tuottavana JavaScriptinä. Kieli kehitettiin 90-luvulla, ja lyhyessä ajas- sa JavaScript on noussut 2000-luvun aikana yhdeksi yhdenvertaiseksi komponentiksi HTML:n ja CSS:n rinnalle.

Nykyisin voidaankin puhua web-sivujen lisäksi web-sovelluksista (web application), jotka ovat JavaScriptiä vahvasti hyödyntäviä sivuja, ja joilla tehdään perinteisiä työ- pöytäsovelluksille kuuluneita tehtäviä. Tämän ilmiön JeffAtwood (2007) on nimennyt itsensä mukaan Atwoodin laiksi: “mikä tahansa sovellus, joka voidaan kirjoittaa Ja- vaScriptillä,kirjoitetaanennen pitkää JavaScriptillä.”

Tätä lakia vahvistavat lukuisat tapaukset, joissa perinteinen työpöytäsovellus on siir- retty web-sovellukseksi. Esimerkiksi Google Drive sisältää JavaScriptillä toteutetut sovellukset tekstinkäsittelylle, taulukkolaskennalle ja esitysgrafiikalle. Microsoft on myös kirjoittanut osittaiset siirrokset Office-paketistaan verkkoversioiksi. Harrastajat ovat kirjoittaneet kokonaisia MS DOS -emulaattoreita JavaScriptille selainten ajetta- vaksi.

Tämän kehityksen mahdollistaa jatkuva kilpailu selainkehittäjien kesken. Ollakseen paras Web-standardien hyvän noudattamisen lisäksi sivujen piirtämisen tulee olla no- peampaa kuin kilpailijoilla. Lyhyiden vasteaikojen ja sulavan käytettävyyden aikaan- saamiseksi selainten on suoritettava sivujen lataus- ja alustusvaiheessa esiintyvä upo- tettu JavaScript-koodi tehokkaasti. Kilpailu ja kehitys onkin tuottanut useita tehokkaita JavaScript-suoritusympäristöjä.

Firefox-selainta kehittävä Mozilla ylläpitää omaa IonMonkey-nimistä JavaScript- suoritusympäristöä. IonMonkey tukee JIT-kääntämistä konekielelle. Google puoles- taan ylläpitää Chrome-selaimessa käytettyä V8-nimistä JavaScript-moottoria. Moot- tori kääntää kaiken koodin laiteympäristön mukaiselle konekielelle ennen sen suoritta- mista ollen näin ääripään esimerkki JIT-optimoivasta ympäristöstä. V8 on myös hyvin tehokas suorituksessaan.

Eri selainten JavaScript-moottoreiden nopeuksia automatisoidusti suorittava testipal- velu Are We Fast Yet (Mozilla, 2014) näyttää syyskuussa 2015, että IonMonkey ja V8

(27)

ovat molemmat verraten tasaväkisiä toisiaan vastaan. Lisäksi molemmat suoritusalustat pärjäävät hyvin valituissa synteettisissä testeissä puhtaasta C++:sta käännettyä natii- vikoodia vastaan. Luonnollisesti palvelu voi kärsiä suppeasta ja puolueellisesta testien otoksesta.

Nämä jatkuvasti merkityksettömämmäksi muuttuvat nopeustekijät ja selainten alus- tatuet kannustavat edelleen kehittämään JavaScript-pohjaisia sovelluksia perinteisem- pien sovelluksien sijaan. Nykyaikainen verkkoselain voidaankin samaistaa erääksi so- vellusalustaksi JVM:n ja .NET:n rinnalle. Uusien standardien myötä selaimet ovat al- kaneet tukea esimerkiksi musiikin ja äänien soittamista ja laitteistokiihdytettyä 3D- grafiikkaa.

JavaScriptiä on sanottu webin konekieleksi (Wirfs-Brock, 2013). Vertauskuvan alle- kirjoittavat myös kielen kehitykseen suuresti vaikuttaneet Brendan Eich ja Douglas Crockford. (Hanselman, 2011)

Saman vertauskuvan JavaScriptistä internetin konekielenä voi ajatella myös kielteises- sä sävyssä: JavaScript on toki korkean tason kieli, mutta se sisältää paljon huonosti suunniteltuja yksityiskohtia. (Crockford, 2008, liitteet A ja B) Dynaaminen ja heikko tyypitys on erityisesti kritisoitu ongelma puhtaassa JavaScriptissä. Lisäksi JavaScriptin prototyyppiperustainen oliomalli liian rajoittamattomana herättää huolta rajoittuneem- pien oliokielten kehittäjien keskuudessa. (Crockford, 2008)

Hiljan onkin kirjoitettu kieliä, jotka on tarkoitettu käännettäväksi koneen luettavak- si JavaScriptiksi. Näiden kielien tarkoitus on tarjota johdonmukaisempia abstraktioita ja parempia idiomeja kuin puhtaassa JavaScriptissä. Esimerkkejä tällaisista kielistä on antaa useita. MicrosoftinTypeScript tarjoaa valinnaisen staattisen tyypityksen JavaSc- riptin päälle. (Microsoft, 2014) Koodi käännetään puhtaaksi JavaScriptiksi tyyppitar- kistusten jälkeen.CoffeeScripton toinen esimerkki, joka kääntää Pythonia muistutta- vaa syntaksia JavaScriptiksi. Samalla CoffeeScript tuo yhtenäistetyn mallin funktioille, sulkeumille ja olioille. (CoffeeScript, 2014)Google Web Toolkit (GWT) kääntää käyt- töliittymäkoodia Javasta JavaScriptiksi. (Google, 2014) Haskellistakin on olemassa Ja- vaScriptiksi käännettävä murre, Haste. (Haste, 2014)

Clojurella on oma JavaScript-kääntäjäprojekti. ClojureScript on kielen kehittäjien vi- rallinen yritys tuoda Clojure selaimiin. Isäntäkielensä tavoin ClojureScript toimii koh- deympäristössä voiden hyödyntää valmiita JavaScript-kirjastoja. Vaikka kaikkea Clo-

(28)

juresta ei ole toistaiseksi voitu siirtää sellaisenaan JavaScript-ympäristöön, useimmat Clojuren idiomit ovat kuitenkin suoraan käytettävissä projektin tämänhetkisessä tilas- sa.

ClojureScript-kääntäjä koostuu useista modulaarisista komponenteista, ja sallii uusien ulostuloformaattien kirjoittamisen. Pääasiallisen JavaScript-kääntämisen lisäksi pro- jektille on jo kirjoitettu kääntäjiä Schemen kautta C:lle ja Python-tavukoodille.

(29)

4 Oliopohjainen ja funktionaalinen paradigma

Oliopohjaisen paradigman perusidea syntyi jo 1950-luvulla ja eräs formaali määritel- mä oliopohjaiselle ohjelmoinnille saatiin Simula-67:n myötä vuonna 1967. (Holmevik, 1994; Nierstrasz, 1989) Simula innoitti monien puhtaiden olio-ohjelmointikieltenke- hitykseen. Puhdas olio-ohjelmointikieli on kieli, jonka suunnittelu- ja toteutusperiaate on pitää kaikki asiat olioina. Eräs varhaisista puhtaista oliokielistä on Smalltalk, jota kehiteltiin 70-luvulla ja virallisesti julkaistiin vuonna 1980. (Kay, 1993)

Oliopohjainen ohjelmointi nousi yleisimmäksi paradigmaksi 1990-luvulla ensin C++:n ja sitten Javan siivittämänä. Erityisesti jälkimmäinen puhtaana oliopohjaise- na kielenä pakottaa harjoittamaan oliopohjaista ajattelua ja suunnittelua syvemmälti, koska kielessä ei alunpitäen ollut paljoa muita ohjelmointityylejä avustavia rakenteita.

Kaikenlainen laajempi logiikanhallinta oli toteutettava oliohierarkioiden ja -ajattelun avulla.

Funktionaalisen paradigman alkuperän määritellään useimmiten olevan Alonzo Churc- hin 1930-luvulla kehittämä lambdakalkyyli. (Barendregt, 1997) Lambdakalkyylissä funktiot toimivat ensiluokkaisina olioina, joiden avulla tuotetaan kaikki laskennassa tarvittavat arvot. Lambdakalkyyli myös formalisoi useita matemaattisia käsitteitälas- kettavaanmuotoon ja laskennan teorialla on silläkin juuria lambdakalkyylissä.

Funktionaalinen paradigma kulminoituu nykyään kolmen kielen tai kieliperheen kes- ken. Ensin John McCarthy esitteli Lisp-kieleen johtaneet periaatteet uudesta mate- matiikan ja laskentojen merkintätavasta. Tästä johdetut ohjelmointikielet mielletään usein funktionaalisiksi, koska ne muistuttavat perustuksiltaan lambdakalkyyliä. Toi- saalta mm. Robin Milnerin johdolla 1970-luvulla kehitetty ML toi omia funktionaa- lisia elementtejään. Tänä päivänä Ocaml ja Microsoftin F# pitävät ML-johdannaisten kielten kannatusta yllä.

Funktionaalisten kielten käyttö oli alussa vähäistä, mutta 80-luvulla jatkuneesta kehi- tyksestä seurasi myös eräs suljettu kieli, David Turnerin suunnittelema Miranda. Vuon- na 1985 julkistettu Miranda sisälsi monia ominaisuuksia, jotka tukivat staattisesti tyy- pitettyä ja funktionaalista sovelluskehitystä. Näihin ominaisuuksiin lukeutuvat esimer- kiksi laiska suoritus ja matemaattisin yhtälöin määritellyt funktiot. Mirandasta johdet- tiin myöhemmin vuosikymmenen lopulla varta vasten kootun komitean johdolla avoin johdannainen, Haskell. Haskell on staattisesti tyypitetty puhdas funktionaalinen kieli,

(30)

joka tukee rentoa ja laiskaa suoritusta.

Tässä luvussa käsittelemme ja vertailemme oliopohjaisen ja funktionaalisen paradig- man eroja ongelmanratkaisussa. Kohdassa 4.1 teemme kirjallisuuskatsauksen erilai- siin paradigmojen määritelmiin ja paradigmoja karakterisoiviin ominaisuuksiin. Koh- dassa 4.2 tutkimme määriteltyjen paradigmojen pohjalta Clojuren funktionaalisuutta.

Lisäksi vertaamme Clojurea funktionaaliseksi tunnustettuun Haskelliin.

4.1 Oliopohjaisen ja funktionaalisen paradigman määritelmiä ja karakterisoivia ominaisuuksia

Tässä kohdassa tutkimme, löytyykö oliopohjaiselle paradigmalle ja funktionaalisel- le paradigmalle konsensuksen saaneita määritelmiä kirjallisuudesta. Erityisesti paneu- dumme funktionaaliselta ohjelmointikieleltä vaadittaviin ominaisuuksiin, jotta voim- me vastata kohdassa 4.2 esitettävään kysymykseen: “onko Clojure funktionaalinen oh- jelmointikieli?” yleisen konsensuksen mukaan.

Oliopohjaisen paradigman keskeinen ohjenuora on: “kaikki asiat ovat olioita” – every- thing is an object. Steve Yeggen (2006) kärjistettyä kirjoitusta mukaellen oliopohjaiset luokat ovat ohjelman määrittelykielen substantiiveja. Vastaavasti oliopohjaisessa ohjel- moinnissa suunnittelu keskittyy olioiden ja substantiivien ympärille. Mikään toiminto, eli verbi, ei toimi itsenäisesti, vaan vaatii substantiivin isännäkseen. Data on määritel- ty näiden substantiivien avulla olioiksi, joilla on omaa tilaansa hyödyntäviä metodeja:

data on tällöin oliomaailman oppien mukaisesti “älykästä”. Olioajattelussa datan on määrä pitää huoli omista asioistaan – eli oma tilansa yhtenäisenä – ja delegoida tehtä- viä osaolioilleen. (Fogus et al., 2011; Freeman et al., 2004)

Funktionaalisessa paradigmassa vastaavasti kaikki toimet ovat itsenäisiä funktioita.

Funktiot toimivat kielen verbeinä ja ovat datan kanssa yhdenvertaisia elementtejä. Da- ta kootaan kielen ja käytäntöjen tukemiin tietorakenteisiin. Datalla ei ole perinteisesti funktionaalisessa maailmassa metodeja, eli se on oliomaailman oppien mukaisesti ta- vallista, (POD; plain old data) tai “tyhmää” dataa. Funktionaalisessa ajattelussa data pidetään tyhmänä ja niitä käsittelevät tietorakenteet ja funktiot älykkäinä. (Fogus et al., 2011)

Tämä ei estä säilömästä funktioita tai muuta toiminnallisuutta tietorakenteisiin, sillä

(31)

funktiot eivät suoraan ole tarkoitettu sisältämänsä tietorakenteen käsittelyyn. Funktiot ovat kuin muita säilöttäviä arvoja tietorakenteelle. Älykkään datan käsite koskee funk- tionaalisuutta, joka käsittelee suljettuja ja kapsuloituja muuttujia. Funktionaaliseen pa- radigmaan kuuluva funktiosulkeumien käyttö hämärtää tätä rajaa varsinaisten olioiden ja funktioiden välillä: arvoja määritelmäänsä sulkenut funktio voi käsitellä tätä dataa kapseloidussa mielessä, mutta tätä silti pidetään funktionaalisena, jopa puhtaana.

4.1.1 Oliopohjaisen paradigman määritelmiä

Yksittäinen olioparadigman ominaisuus, joka määrittelee koko paradigmaa eniten, on tiedon ja tilankapselointi (encapsulation) olioiden sisään. (Schärli et al., 2004; Nier- strasz, 1989; Gorschek et al., 2010)

Schärli et al. (2004) kuvailevat olio-ohjelmointia kuvailemalla oliopohjaisen paradig- man tärkeintä ominaisuutta, kapselointia. Tiedon hyvin tehty kapselointi olioiden si- sään kuuluu heidän mukaansa olioparadigman oleellisiin ominaisuuksiin. Hyvin tehty kapselointi määrittelee hyvin luokkien tarjoamat rajapinnat, ja helpottaa täten koodin uudelleenkäytettävyyttä ja hallittavuutta.

Nierstrasz (1989) päätyy myös siihen tulokseen, että olioparadigman määräävin piirre on kapselointi, ja tiivistää aiheesta kirjallisuuskatsauksessaan seuraavasti: “oliopohjai- set käsitteet, kuten olioluokista alustaminen, luokkaperiytyminen, polymorfismi, ylei- syys ja vahva tyypitys ovat kaikki riippuvaisia olioiden kapseloinnista.” Nierstrasz kir- joittaa myös, että olioparadigma kannustaa käsittelemään “olioita” datan tai ohjelma- koodien sijaan. Tämä menetelmä yhtyy Yeggen (2006) käsitykseen olioilla tehtävästä mallintamisesta.

Nierstrasz (1989) toteaa raportissaan myös, että luokkahierarkiat ja -periytyminen ei- vät ole oleellinen oliopohjaisuutta määrittävä tekijä. Nierstrasz ehdottaa, että jokainen kieli, joka tarjoaa menetelmiä kapseloinnin hyödyntämiseen, on oliopohjainen kieli.

Lisäksi oliopohjainen kieli tyypillisesti helpottaa olioiden ohjelmoimista tarjoamalla sopivia kielirakenteita niiden käsittelyyn.

Kay (1993) kuvailee Smalltalkia suunnitellessaan kielen oliopohjaisuutta muovailleita suunnittelupäätöksiä: pysyvä tila, polymorfismi, olioiden alustaminen ja olioiden me- todittavoitteinaovat kaikki tekijöitä oliopohjaisuudessa. Näihin suuntaviivoihin nojaa-

(32)

va olio-ohjelmointi, Kayn mukaan, on kuitenkin kaukana nykyaikaisien oliopohjaisten kielien, kuten C++:n ja Javan, periaatteista.

4.1.2 Funktionaalisen paradigman määritelmiä

Funktionaalinen paradigma on kirjallisuudessa hyvin hämärästi määritelty käsite. (Fo- gus et al., 2011; Hutton, 2007; Turner, 1995) Artikkelien kirjoittajien näkemykset funk- tionaalisuuden määritelmästä näyttävät riippuvan voimakkaasti heidän käyttämien- sä kielten vahvoista puolista. Haskell-taustaisille funktionaalisuus tarkoittaa puhtaita funktioita ja laiskaa suoritusta. Vastaavasti Clojure-kirjoissa funktionaalisuus on usein sidottu pysyviin tietorakenteisiin.

Fogus et al. (2011) tiivistävät funktionaalisen ohjelmoinnin siihen, että sen ytimessä on formaali laskennallisen teorian malli, lambdakalkyyli. Funktionaalinen paradigma edellyttää heidän mukaansa sitä, että funktiot ovatensiarvoisiaelementtejä (first-class objects/citizens), joita voidaan luoda, käsitellä, yhdistellä keskenään ja palauttaa toi- sista funktioista. Toisin sanoen funktiot ovat kuin mitä hyvänsä arvoja kielen kannalta.

Halloway (2009) laskee funktionaaliseen paradigmaan mukaan myöspuhtaiden funk- tioiden(pure functions) käsitteen, pysyvät tietorakenteet ja laiskojen jonojen käytön.

Samalla linjalla ovat Emerick et al. (2012). He lukevat funktionaaliseen paradigmaan kuuluvaksi pysyvät tietorakenteet, ensiarvoiset funktiot ja korkeamman asteen funk- tiot. Kirjoittajat erityisesti painottavat funktionaalisen paradigman suosivan muuttu- matonta tietoa. Funktionaalisuus on myös enemmän kuvainnollista kuin imperatiivista ohjelmointia. Lopuksi funktionaalinen paradigma painottaa yhdisteltävyyttä.

Varhaisemmassa kirjallisuudessa funktionaalisuuteen riittää pelkkä funktioiden painot- taminen ohjelmakoodin organisoinnissa. Hughes (1989) tiivistää funktionaalisen ohjel- moinnin tarkoittavan pienistä, modulaarisista funktioista tehtäviä koosteita ja isompien kokonaisuuksien rakentamista. Hutton (2007) on samalla linjalla funktionaalisen ohjel- moinnin määritelmän kanssa. Hughes lisäksi katsoo, että laiska suoritus tai laiskojen listojen – joita joissakin kielissä kutsutaan myösvirroiksi (streams) – rooli on tärkeä osa funktionaalista arkkitehtuuria.

Toiset, kuten Peyton Jones et al. (1993), jakavat edelleen rajan funktionaalisen kielen japuhtaan funktionaalisen kielenvälille. Tämä ero määräytyy sen mukaan, kuinka oh-

(33)

jelmointikieli tukee sivuvaikutusten toteuttamista ohjelmakoodissa. Kirjoittajat määrit- televät esimerkiksi lisp- ja ML-perheiden kielet epäpuhtaiksi funktionaalisiksi kieliksi, koska niissä sivuvaikutusten kirjoittamista ei valvota kielen rakenteiden avulla. Vastaa- vasti puhtaina funktionaalisina pidetyissä kielissä, kuten Haskellissa, sivuvaikutuksia ei voida tehdä ilman eksplisiittisten formalismien käyttöä.

Bloch (2008) määrittelee funktionaalisen ohjelmoinnin olevan sitä, että funktiot tuotta- vat uusia arvoja muuttamatta edellisiä. Bloch ei kuitenkaan lähteessä varsinaisesti ota kantaa funktionaalisiin kieliin.

McNamara et al. (2000) mukailee edellisiä siinä suhteessa, että funktionaalinen ohjel- mointi edellyttää funktioiden ensiarvoisuutta. Lisäksi kirjoittajat nostavat esille tärkeän huomion funktionaalisesta koodista: funktionaalinen koodi ei käsittele muistipaikkoja tai viittauksia niihin, vaan se keskittyy käsittelemään arvoja.

Näistä määritelmistä voimme poimia joitakin usein toistuvia ominaisuuksia. Funktioi- den ensiarvoisuus ja korkeamman asteen funktioiden tuki ovat eniten mainittuja omi- naisuuksia. Kaikissa määritelmissä myös esiintyy lambdakalkyylistä peräisin oleva kä- site, että yksittäinen funktio on kielen perusyksikkö, eivät luokat tai oliot. Lisäksi py- syvien ja muuttumattomien arvojen läsnäolo esiintyy valtaosassa määritelmistä. Tämä olkoon funktionaalisen ohjelmointikielen määritelmä tässä työssä. Lisäksi määrittelen puhtaan funktionaalisen kielen Simon Peyton Jonesin (1993) tapaan olevan sellainen funktionaalinen kieli, jossa funktioiden sivuvaikutuksettuleekääriä kielen käyttämiin rakenteisiin. Tällöin puhtaassa funktionaalisessa kielessä puhtaiden ja epäpuhtaiden funktioiden sekoittamista keskenään ei voi tapahtua vahingossa.

Tästä funktionaalisen kielen määritelmästä voidaan päätellä joitakin seurauksia. Funk- tiot ovat funktionaalisen kielen perusyksiköitä, rakennusosia. Funktiot on tulkittavis- sa atomisina olioina, joille ei ole hyvin määriteltyä perintäjärjestelmää, kuten luokille oliopohjaisissa kielissä on. Tästä seuraten funktioiden yhdisteet ovat luontaisin ja ylei- sin tapa uudelleenkäyttää koodia funktionaalisessa kielessä. Tämä sopii esimerkiksi Blochin (2008) esittämään ohjenuoraan suosia yhdisteitä perimisen sijaan sovellusark- kitehtuureissa.

Pysyvän ja muuttumattoman tiedon kanssa työskennellessä olion identiteetti ei ole si- dottu tiettyyn instanssiin ja sen muistipaikkaan, vaan vain olion arvolla on väliä. Toi- sin sanoen arvoon identtinen toisen arvon kanssa, jos ja vain jos niiden sisällöt ovat

(34)

samat. Kielet, joissa tällaista arvosemantiikkaa ei käytetä, olioiden identtisyys määri- tellään olioiden instanssien samuusvertailulla, käytännössä vertailemalla muistiosoit- teiden samuutta. Clojuren kehittäjä Rich Hickey (2012) kuvaakin tätä ajattelumallia paikkakeskeiseksi ohjelmoinniksi (PLOP; place-oriented programming). Myös Bloch (2008) kannustaa tekemään luokista ja olioista muuttumattomia aina kun mahdollista.

4.2 Clojure on funktionaalinen kieli

Edellisessä kohdassa koostimme kirjallisuudessa esiintyneistä funktionaalisen paradig- man määritelmistä yhteenvedon. Tätä koostetta hyväksikäyttäen voimme nyt analysoi- da Clojuren funktionaalisuutta. Vertaamme Clojurea sekä edellälöydettyihin määritel- miin että tunnustettuun funktionaaliseen kieleen, Haskelliin.

Clojuressa funktiot ovat ensiarvoisia elementtejä, eli niitä voidaan vastaanottaa ja pa- lauttaa toisista funktioista. Lisäksi funktioita voidaan luoda funktioiden sisällä ja sa- malla luodasulkeumia (closures) arvojen ylitse. (Fogus et al., 2011, luku 7.1) Tämä ominaisuus yksinään oikeuttaa useimmissa lähteissä Clojuren kutsumisen funktionaa- liseksi kieleksi. Clojure myös asettaa funktiot ohjelmakoodin perusyksiköiksi, ja suosii yhdisteltävyyttä toiminnallisuutta perivien hierarkioiden sijaan.

Clojure ei ole kuitenkaan puhdas funktionaalinen kieli. Ulkoisesti tarkastelemalla an- netusta funktiosta ei voida päätellä mitään funktion puhtaudesta. On vain esitetty ni- meämiskäytäntöjä, joiden mukaan toiminnallisia sivuvaikutuksia aiheuttavat funktiot nimettäisiin huutomerkillä. (Emerick et al., 2012, luku 2: Pure Functions)

Emme valinneet laiskaa suoritusta tai laiskoja tietorakenteita funktionaalisen paradig- man määritelmiin. Tarkastelemme kuitenkin näidenkin ominaisuuksien toteutumista.

Clojure suorittaa ahkerasti tarkoittaen, että jokaisen funktiokutsun argumentit laske- taan auki ennen kutsun suorittamista. Lisäksi Clojuren konkreettiset tietorakenteet ovat ahkeria. (Emerick et al., 2012, luku 3) Tämä tarkoittaa sitä, että tietorakenne säilytetään aina suoritusmuistissa aukilaskettuna.

Clojuressa on kuitenkin jonoabstraktion, jota käsittelimme johdantoluvun kohdas- sa 2.2.3, toteuttava “laiska jono” (lazy sequence) -toteutus. Koska valtaosa Clojuren standardikirjaston funktioista käyttää kokoelmia juuri jonoabstraktion kautta, ja ky- seistä abstraktiota on luontevaa käyttää koodissa, on laiskojen jonojen käyttö läpinä-

(35)

kyvää ja yleistä. Pääasiallisesti kaikki jonomuotoista tietoa tuottavat standardikirjaston funktiot tuottavat laiskoja jonoja. Näihin funktioihin kuuluvat esimerkiksi listarakennin forja funktionaaliset perustyökalutmapjafilter. Tällä keinoin laiskat jonot asettu- vat vertailukelpoisiksi Haskellin laiskoille listoille: kummassakin tapauksessa sisältöä lasketaan käytännössä auki vain tarpeen mukaan.

Kielitasolla Clojure ei tue laiskoja funktiosovelluksia, mutta lisp-kielenä Clojure tu- kee kyllä makroja, joilla voidaan kaapata käännöksenaikaista syötettä ja esimerkiksi kääriä annettua tietoa myöhemmin suoritettaviksi funktioiksi. Tällainen myöhemmin tarvittavan laskennan kääriminen funktioksi onnistuu sopivien makrojen avulla hyvin ja käy köyhän miehen laiskasta laskennasta. (Graham, 1994, luku 15) Käsittelimme makrojen käyttöä kohdassa 2.3. Kuvassa 2 totesimme, että uusien ehtorakenteiden kir- joittaminen vaatii ahkeralta kieleltä esimerkiksi makrotoiminnallisuutta toimiakseen halutulla tavalla.

Vastaavanlaisten ehtorakenteiden kirjoittaminen Haskellissa, kielen ollessa perustuksi- aan myöten laiska, onnistuu kirjoittamalla tavallisia funktioita. Ne osat funktion mää- rittelystä tai argumenteista, joita ei koskaan sovelleta suorituksen aikana, jätetään yk- sinkertaisesti suorittamatta.

(36)

5 Ilmaisuongelma

Ilmaisuongelma kysyy, miten laajennamme olemassaolevaa koodikantaa kattamaan uusia käyttötapauksia. Tässä luvussa tutkimme eri kielten tarjoamia ratkaisuja ilmai- suongelman ratkaisemiseksi.

Kohdassa 5.1 käymme läpi ilmaisuongelman määritelmän ja ongelmaan liittyvän sa- naston. Kohdassa 5.2 tarkastelemme pintapuolisesti muutamia erilaisia lähestymista- poja eri kielistä ilmaisuongelmaan, ja kohdassa 5.3 tutkimme erityisesti Clojuren tar- joaman ratkaisun,monimetodien, ominaisuuksia.

5.1 Ongelmakuvaus

Ilmaisuongelman määrittelemiseksi käsittelemme ongelmaa kahden käsitteen kautta:

tietotyyppion jokin, mahdollisesti rakenteinen tyyppi. Ilmaisuongelman kannalta tie- totyyppien ongelmallisuus syntyy niiden heterogeenisyydestä, eli oletamme, että uu- det tietotyypit eroavat ratkaisevasti entisestään tuetuista. Operaatio on jokin funktio tai proseduuri, jonka lähtöjoukon olisi määrä kattaa kaikki tuetut tietotyypit.

Olkoon meillä järjestelmä, jossa on toteutettuja operaatioita usealle eri tietotyypille, ja jossa ilmaisuongelma ilmenee. Järjestelmän toteutuksesta riippuen joko uusien tieto- tyyppien tai uusien operaatioiden lisääminen järjestelmään on mahdotonta ilman van- han koodin muokkaamista.Ilmaisuongelmaksikutsutaan sitä ongelmaa, jossa järjestel- mään halutaan tuoda uusia tietotyyppejä tai operaatioita ilman vanhan muokkaamista.

Torgersen (2004) esittelee ongelman kaksi kääntöpuolta.

Tietokeskeinen järjestelmä edustaa suoraviivaista oliopohjaista lähestymistapaa, jossa operaatiot ovat luokkien virtuaalisia metodeita ja jotka kirjoitetaan kullekin perittävälle tietotyypille (luokat) erikseen. Uusia tietotyyppejä on helppo tuoda järjestelmään kir- joittamalla uusi aliluokka operaatiometodeineen. Sen sijaan uusien operaatioiden tuo- minen edellyttäisi kaikkien ennestään kirjoitettujen tietotyyppien avaamista ja muok- kaamista.

Operaatiokeskeinen lähestymistapa edustaa enemmän funktionaalista tai proseduraa- lista lähestymistapaa, jossa kukin operaatio toimii itsenäisenä funktionaan, ja valitsee annetun syötteen perusteella, mitä tarkalleen ottaen tehdään. Oliopohjaisissa kielissä

(37)

ja suunnittelumalleissa tämä kulkeevierailijamallin (Visitor pattern) nimellä. Uusien operaatioiden lisääminen järjestelmään on helppoa, mutta uusien tietotyyppien lisää- minen vastaavasti edellyttää jokaisen operaation muokkaamista.

Torgersen (2004) jatkaa, että ilmaisuongelman annetun ratkaisun tulisi tukea sekä tie- totyyppien että operaatioiden lisäämistä järjestelmään siten, että mitään vanhaa toteu- tusta ei tarvitse muokata. Torgersen edellyttää ratkaisuilta lisäksi, että ohjelmakoodia ei toisteta suuresti useampaan kertaan missään, ja että kaikenlaiset tietotyyppien ja operaatioiden kombinaatiot ovat mielekkäästi toteutettuna.

Ilmaisuongelman ratkaisut ovat ratkaisuja muuttuvien vaatimusten ongelmaan. Chris Houser (2010) mainitsee esityksessään esimerkkinä raportintuottajaoperaation, joka tukee ensin muutamia annettuja tietotyyppejä, mutta myöhemmin pitäisi päivittää tu- kemaan uusia.

Esimerkki

Esittelen konkreettisen Java-henkisen tapauksen, joka mukailee Houserin esityksen (2010) esimerkkiä. Olkoon meillä tilausjärjestelmä, joka mallintaa tilauksia nimeltä Tilaustapahtuma. Järjestelmässä on myös tarvetta pitää kirjaa varastotuotteista, ja sitä varten siellä on luokkaVarastotuote. Luokilla on toki omat kenttänsä ja ne eivät ole suoraan yhteensopivia keskenään. Nämä ovat esimerkkimme tietotyypit.

Esitetään vaatimus, että järjestelmän pitäisi osata tuottaa HTML-raportteja kum- mastakin tyypistä. Olio-oppien mukaisesti syntyvä luokka HtmlRaportti ei sel- laisenaan erityisesti tue kumpaakaan tyyppiä, vaan sille täytyy tuoda erityisen Tietorivi-rajapinnan mukaista dataa. Tietorivi sisältää kaksi julkista metodia:

haeAttribuuttienNimet()jahaeArvo(attribuutinNimi).

Vaikka Tietorivi abstrahoikin lähdedatan riittävän yleiselle tasolle, nyt ilmai- suongelma on ilmeinen, koska alunperin luodut luokat Tilaustapahtuma ja Varastotuoteeivät toteuta tätä rajapintaa. Ongelma on lähinnä teoreettinen, jos kaik- ki tietotyypit tulevat omasta koodikannasta. Käytännön ongelma syntyy siinä vaihees- sa, kun raportteja tulisi tuottaa sellaisista tyypeistä, jotka tulevat kielen omista kirjas- toista tai kolmannen osapuolen mustista laatikoista, joita ei voi manipuloida.

Viittaukset

LIITTYVÄT TIEDOSTOT

juontavat siit¨a, ett¨a hyperboliset funktiot k¨aytt¨aytyv¨at monessa suhteessa kuten trigonometriset funktiot. Hy- perboliset funktiot

juontavat siit¨a, ett¨a hyperboliset funktiot k¨aytt¨aytyv¨at monessa suhteessa kuten trigonometriset funktiot. Hy- perboliset funktiot

Yksinkertaisena, mutta monipuolisena esimerkkinä AutoLISP:n käytöstä voidaan tehdä funktio kirjoittamalla rivit yksittäin komentoriville tai kopioimalla ja liittämällä

Tässä paperissa esitelty ProDigi-konsepti on esi- merkki siitä, miten työohjeet voidaan luoda suunnittelun 3D-malleista, miten niihin voi- daan lisätä strukturoidun tekstin

12 Kasvuyrittäjien määritelmän vaikutus (kasvurittäjien määrää arvioitaessa) on suuri; vuonna 2002 uuden liiketoi- minnan käynnistymisessä mukana olleista suomalaisista noin

Periaatteessa tuleva päättökoe edellyttää kaikilta oppilailta kaunokirjallisuuden analyysitaitoja, mutta huolta herättää se, missä määrin kouluissa ehditään käsitellä

usein ilmaistu huolenaihe liittyy siihen (periaatteelliseen) mahdollisuuteen, että eri rekistereiden tietoja voidaan yhdistellä, sekä siihen, että esimerkiksi

Kotiliesi ja Me naiset -lehdissä käyty naisten ansiotyötä koskeva keskustelu ei ollut suora- viivaista, vaan velloi yleisen tason periaatekeskusteluista yksityiskohtiin,