64
Clojure-kieli Mikael Puhakka Pro gradu -tutkielma Tietojenkäsittelytieteen laitos Tietojenkäsittelytiede Syyskuu 2015

Clojure-kieli - UEF · 2016-01-14 · 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

  • Upload
    others

  • View
    7

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Clojure-kieli - UEF · 2016-01-14 · 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

Clojure-kieli

Mikael Puhakka

Pro gradu -tutkielma

Tietojenkäsittelytieteen laitos

Tietojenkäsittelytiede

Syyskuu 2015

Page 2: Clojure-kieli - UEF · 2016-01-14 · 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

ITÄ-SUOMEN YLIOPISTO, Luonnontieteiden ja metsätieteiden tiedekunta, JoensuuTietojenkäsittelytieteen laitosTietojenkäsittelytiede

Puhakka, Mikael Erkki Juhana: Clojure-kieliPro gradu -tutkielma, 59 s., ei liitteitä.Pro gradu -tutkielman ohjaaja: FT Matti NykänenSyyskuu 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, hallittumuutostenhallinta 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ä saaaikaan 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 JavaVirtual Machinen päällä ja ClojureScriptin toimiessa JavaScriptin päällä kielellä onikäänsä nähden suuri vaikutusalue. Funktionaalisen ja oliopohjaisen paradigman mää-ritelmiä katselmoidaan kirjallisuudesta. Paradigmojen tavat ratkaista ongelmia eroavathuomattavasti 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 operaatioidenyhteensovittamisesta 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 tarjoaaerään ratkaisun ilmaisuongelmaan geneeristen funktioiden avulla. Sovellusprojektinja -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, oliopohjainenohjelmointi

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

i

Page 3: Clojure-kieli - UEF · 2016-01-14 · 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

UNIVERSITY OF EASTERN FINLAND, Faculty of Science and Forestry, JoensuuSchool of ComputingComputer Science

Puhakka, Mikael Erkki Juhana: The Clojure Programming LanguageMaster’s Thesis, 59 p., no appendicesSupervisor of the Master’s Thesis: PhD Matti NykänenSeptember 2015

Abstract:

Clojure is a functional Lisp language that runs on the Java Virtual Machine. In thiswork we study the features that Clojure brings to reduce ever-increasing complexityin software projects. These features include value semantics, functionality, controlledmutation and the expression problem with some solutions. An eager language, Clojureuses lazily evaluated data structures to simulate lazy evaluation. The values and datastructures 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. Thanksto the matured optimizing interpreters, such software can be as fast as one compiled fornative hardware. JavaScript, ubiquitously running on modern web browsers, can alsobe considered as an application platform. Clojure running on Java Virtual Machine andClojureScript running on JavaScript make the languages have a great reach, despitethe young age of the projects. We will review some definitions of both functional andobject-oriented paradigms from literature. The methods of problem solving differ re-markably between the paradigms. A functional programming language emphasizes thefunction 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 befit together so that more types or operations can be added later on without modifyingor recompiling any existing code. Out of several solutions we study the most promi-nent: generic functions. Clojure provides a solution to the expression problem throughan application of generic functions. The complexity of software projects and code canbe 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 problemdomain; persistent and immutable-by-default data as a way to avoid uncontrolled mo-difications; and a centralized interface for coordinated changes when such a situationis truly warranted. With these features, Clojure is well-equipped to simplify softwaresolutions.

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

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

ii

Page 4: Clojure-kieli - UEF · 2016-01-14 · 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

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.

iii

Page 5: Clojure-kieli - UEF · 2016-01-14 · 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

Sisältö

1 Johdanto 1

2 Clojuren syntaksi ja perusteet 42.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 173.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 244.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 315.1 Ongelmakuvaus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

5.2 Eräitä ratkaisuja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

5.3 Monimetodit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

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

6.2 Pysyvät tietorakenteet suunnittelussa . . . . . . . . . . . . . . . . . . 43

6.3 Tilanhallinta ja STM . . . . . . . . . . . . . . . . . . . . . . . . . . 46

7 Yhteenveto 50

Viitteet 56

iv

Page 6: Clojure-kieli - UEF · 2016-01-14 · 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

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 koodista yksinkertaista, 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

1

Page 7: Clojure-kieli - UEF · 2016-01-14 · 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

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

2

Page 8: Clojure-kieli - UEF · 2016-01-14 · 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

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.

3

Page 9: Clojure-kieli - UEF · 2016-01-14 · 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

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 niinsanottuja S-lausekkeita, jotka ovat joko

yksittäisiä arvoja, tietorakenteita tai funktiokutsuja.

Funktiokutsut suoritetaan 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

4

Page 10: Clojure-kieli - UEF · 2016-01-14 · 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

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 luominen tapahtuu fn-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 erityinen defn-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ä tapahtuu let-makroa käyttämällä. Van-

hoja arvoja voi peittää let-ympäristön suorituksen ajaksi.

5

Page 11: Clojure-kieli - UEF · 2016-01-14 · 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

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)

Ehtolauseet toteutetaan if-erikoiskutsun avulla. Clojuren if sisä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

6

Page 12: Clojure-kieli - UEF · 2016-01-14 · 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

(> 1 5) :foo

(< 2 2) :bar

(< 1 2) :totuus)

#=> :totuus

Arvon mukaan valittavaa suorituspolkua varten on kirjoitettu valmis case-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

7

Page 13: Clojure-kieli - UEF · 2016-01-14 · 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

#=> 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- tai BigDecimal-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) ja 5/4 tuot-

tavat saman Ratio-tyyppisen arvon.

Totuusarvot true ja false toimivat 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.

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

8

Page 14: Clojure-kieli - UEF · 2016-01-14 · 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

kuin Javan null-vakiota, Clojuressa tyhjettä nil käytetään luontevammin osana

algoritmeja ja paluuarvoja, sikäli kun sen käyttö sopii tilanteeseen.

Merkkivakiot kirjoitetaan kenoviivan avulla: kirjain a kirjoitetaan Clojuressa \a. Li-

säksi muutama tyhjemerkki voidaan kirjoittaa pitkässä muodossa: \newline ja

\space tuottavat 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 tarkoitukseen avainsanoja.

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ä ovat atom, agent, var ja ref.

Referenssityypit osoittavat pysyviin arvoihin, ja kohteen arvoa haettaessa (dere-

ference) palautetaan pysyvä arvo kohteen senhetkisestä tilasta, eli kutsunaikai-

nen otos (snapshot). (Fogus et al., 2011)

9

Page 15: Clojure-kieli - UEF · 2016-01-14 · 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

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ä.

Listat ovat 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 aikavaativuusluokkaa O(n), vektorin koon

laskeminen on vakioaikainen operaatio ja elementin hakeminen indeksin mukaan on

O(log32 n).

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

10

Page 16: Clojure-kieli - UEF · 2016-01-14 · 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

;; 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(log32 n)), hakupuut (elementit ovat annetun kriteerin mukai-

sessa järjestyksessä ja haku on O(log2 n)) 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.

Joukot ovat 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ä-

11

Page 17: Clojure-kieli - UEF · 2016-01-14 · 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

sittelyä. Kaikkia neljää päätietorakennetta voidaan käsitellä yhtenäistetyin funktioin,

jotka ovat täten polymorfisia. Kaikille kokoelmille määritellään useita tyypillisiä funk-

tioita, kuten count alkiomäärän laskemista varten, conj (lyh. conjoin) uusien alkioi-

den liittämiseksi mukaan ja empty, joka tuottaa polymorfisesti tyhjän, samaa tyyppiä

olevan kokoelman. Lisäksi kokoelmat toteuttavat funktion seq, 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,

kuten first ja rest jonon 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 Javan Iterable-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. Funktio map palauttaa lop-

putuloksen jonona, joten uudelleenkäännös alkuperäiseen tietorakenteeseen tulee teh-

dä tarvittaessa itse. Seuraava esimerkki poimii kuvauksen avaimet talteen ja valaisee

12

Page 18: Clojure-kieli - UEF · 2016-01-14 · 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

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 tuki makrofunktioille eli 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 saman if-not -tyylisen ehtorakenteen sekä funktiona että makrona.

Rakenne käyttäytyy kuin if, 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 argumentit false, nil ja nil. Vastaava

makro saa syötteenään alkuperäiset, laskemattomat argumentit. Kuvassa 3 demonstroin

tätä eroa.

Makroversio ifnot-m suoritetaan 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ä kutsutaan makro-

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.

13

Page 19: Clojure-kieli - UEF · 2016-01-14 · 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

;; 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 kaksineljä 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

14

Page 20: Clojure-kieli - UEF · 2016-01-14 · 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

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 erityisen set!-kutsun avulla. Täs-

sä julkista Point-olion x-jäsentä muutetaan luvusta 1 lukuun 4. Muutokset kohdistuvat

kertaalleen luotuun Point-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.

15

Page 21: Clojure-kieli - UEF · 2016-01-14 · 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

(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"))

16

Page 22: Clojure-kieli - UEF · 2016-01-14 · 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

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älitason sovellusalustoille (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 oma suoritusaikainen 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ä.

17

Page 23: Clojure-kieli - UEF · 2016-01-14 · 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

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

18

Page 24: Clojure-kieli - UEF · 2016-01-14 · 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

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-

splisiittinen recur-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 tai loop-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älinen keskinä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.

19

Page 25: Clojure-kieli - UEF · 2016-01-14 · 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

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

kuuluvien delegaattien välityksellä paremmin kuin JRE:n oliopohjaisissa ratkaisuissa.

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

20

Page 26: Clojure-kieli - UEF · 2016-01-14 · 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

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 Jeff Atwood (2007) on nimennyt

itsensä mukaan Atwoodin laiksi: “mikä tahansa sovellus, joka voidaan kirjoittaa Ja-

vaScriptillä, kirjoitetaan ennen 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

21

Page 27: Clojure-kieli - UEF · 2016-01-14 · 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

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. Microsoftin TypeScript tarjoaa valinnaisen staattisen tyypityksen JavaSc-

riptin päälle. (Microsoft, 2014) Koodi käännetään puhtaaksi JavaScriptiksi tyyppitar-

kistusten jälkeen. CoffeeScript on 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-

22

Page 28: Clojure-kieli - UEF · 2016-01-14 · 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

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.

23

Page 29: Clojure-kieli - UEF · 2016-01-14 · 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

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-ohjelmointikielten ke-

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-

kettavaan muotoon 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,

24

Page 30: Clojure-kieli - UEF · 2016-01-14 · 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

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ä jakarakterisoivia 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ä

25

Page 31: Clojure-kieli - UEF · 2016-01-14 · 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

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 tilan kapselointi (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-

todit tavoitteina ovat kaikki tekijöitä oliopohjaisuudessa. Näihin suuntaviivoihin nojaa-

26

Page 32: Clojure-kieli - UEF · 2016-01-14 · 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

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 ovat ensiarvoisia elementtejä (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ös puhtaiden 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ös virroiksi (streams) – rooli on tärkeä

osa funktionaalista arkkitehtuuria.

Toiset, kuten Peyton Jones et al. (1993), jakavat edelleen rajan funktionaalisen kielen

ja puhtaan funktionaalisen kielen välille. Tämä ero määräytyy sen mukaan, kuinka oh-

27

Page 33: Clojure-kieli - UEF · 2016-01-14 · 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

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 sivuvaikutukset tulee kää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 arvo on identtinen toisen arvon kanssa, jos ja vain jos niiden sisällöt ovat

28

Page 34: Clojure-kieli - UEF · 2016-01-14 · 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

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 luoda sulkeumia (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ä-

29

Page 35: Clojure-kieli - UEF · 2016-01-14 · 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

kyvää ja yleistä. Pääasiallisesti kaikki jonomuotoista tietoa tuottavat standardikirjaston

funktiot tuottavat laiskoja jonoja. Näihin funktioihin kuuluvat esimerkiksi listarakennin

for ja funktionaaliset perustyökalut map ja filter. 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.

30

Page 36: Clojure-kieli - UEF · 2016-01-14 · 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

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:

tietotyyppi on 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. Ilmaisuongelmaksi kutsutaan 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ä

31

Page 37: Clojure-kieli - UEF · 2016-01-14 · 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

ja suunnittelumalleissa tämä kulkee vierailijamallin (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 luokka Varastotuote. 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() ja haeArvo(attribuutinNimi).

Vaikka Tietorivi abstrahoikin lähdedatan riittävän yleiselle tasolle, nyt ilmai-

suongelma on ilmeinen, koska alunperin luodut luokat Tilaustapahtuma ja

Varastotuote eivä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.

32

Page 38: Clojure-kieli - UEF · 2016-01-14 · 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

5.2 Eräitä ratkaisuja

Sovitinmalli (Adapter pattern) on eräs ilmeisin tapa ratkaista uusien tietotyyppien sopi-

vuus vanhoihin operaatioihin. Oliopohjaisissa ratkaisuissa käärimme uuden, tuettavan

tietotyypin uudeksi luokaksi, joka toteuttaa operaatioiden tarvitsemat rajapinnat. Chris

Houser (2010) mainitsee muutamia ongelmia tässä ratkaisussa. Päällimmäisin ongel-

ma on samuussemantiikan katoaminen; kääritty tietotyyppi ja sen ilmentymä ei enää

vastaa alkuperäisen luokan oliota. Lisäksi Houser nostaa esille ylläpitokoodin tarpeet-

toman suuren määrän, jota tarvitaan verraten luonnollisen ongelman ratkaisemiseksi.

Paikkakoodi (Monkey patching) on eräs joidenkin kielten tarjoama ratkaisu laajentaa

annettuja luokkia ja olioita siten, että uudet laajennokset tulevat osaksi suljettuja luok-

kia ollen näin samanvertaisia jäseniä kuin alkuperäiset. Paikkaamalla epäyhteensopivat

luokat luomalla operaatioihin sopivat metodit voidaan ilmaisuongelma ratkaista. Me-

netelmällä on monia ongelmia, joista tiedon kapseloinnin rikkoutuminen ja käytettyjen

jäsennimien potentiaaliset yhteenotot ovat kaksi merkittävintä ongelmaa. Paikkakoo-

din etuna sovittimien käyttöön verrattuna identiteetti saadaan säilytettyä alkuperäisten

kanssa, vaikka sitten alkuperäisiä luokkia manipuloimalla. Prototyyppipohjainen Ja-

vaScript ja dynaaminen oliokieli Ruby sallivat luokkien paikkaamisen. (Houser, 2010)

Yleiset funktiot (generic functions) ovat muutamassa kielessä käytetty tapa harjoit-

taa oliopohjaista ohjelmointia määrittelemällä metodeja luokan ulkopuolella. Yleisten

funktioiden nimitys tulee Common Lisp -kielestä (Seibel, 2005, luku 16), mutta tek-

niikka on vanhempi. Common Lispissä yleisen funktion kirjoittaminen muistuttaa ul-

koisesti tavallisen funktion määrittelemistä, mutta pelkkä nimi ei määritäkään yksikä-

sitteistä suoritettavaa rutiinia: yleisen funktion nimi ja sen argumenttien tyypit yhdes-

sä muodostavat tavan osoittaa yksikäsitteisesti haluttu funktio. Vaikka samalla nimellä

näyttääkin olevan määritelty metodin useita eri toteutuksia, kieli tekee ajonaikaisen lä-

hettämisen oikealle toteutukselle argumenttien tyyppien perusteella. Jos ajattelemme

oliometodeita itsenäisinä funktioina, jotka päättävät suorituksestaan annettujen argu-

menttien perusteella, ovat seuraavat kaksi tapaa kutsua metodeita samat:

metodi(olio, argumentti)

<=>

olio.metodi(argumentti)

33

Page 39: Clojure-kieli - UEF · 2016-01-14 · 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

Tämä on sama idea kuin uudemmissa oliopohjaisissa kielissä, joissa luokan metodit

esitellään ja joskus toteutetaankin luokan sisällä. Kun metodin “yksinomistavan” luo-

kan käsite unohdetaan, metodia kutsuva olio ja sen luokka onkin vain yksi argument-

ti metodikutsulle lisää. Yleisiä funktioita voikin kirjoittaa ja lisätä Common Lispis-

sä luokkiin mielivaltaisissa vaiheissa, missä käännösyksikössä tai moduulissa tahansa.

Näin yleinen funktio ratkaisee ilmaisuongelman: uuden tietotyypin esittely vaatii vain

tarvittavan määrän uusia yleisiä funktioita uuden luokan kanssa.

Tyypilliset nykykielet, kuten Java ja C#, tukevat metodin lähettämistä vain yhden ar-

gumentin – tavallisesti metodin luokan tyypin – mukaan. Metodit määritellään luokan

määrittelyn yhteydessä ja ne saavat liittyvän olionsa viitteen implisiittisenä argument-

tinaan.

Reflektiivisiä ominaisuuksia sisältävät kielet ja suoritusalustat voivat tarvittaessa to-

teuttaa omat toteutuksensa yleisistä funktioista. Javassa esimerkiksi voisi kukin ylei-

nen funktio kääntyä yhdeksi luokan ilmentymäksi, jolla on julkinen rekisteröintimeto-

di. Tätä metodia kutsumalla voisi uusi toteutus ilmoittaa olemassaolostaan ja tukemis-

taan tyypeistä. Rekisteröinnin ohessa nämä tiedot “yleinen funktio -luokka” säilöisi

tietorakenteeseensa.

Luokan metodi voidaan aivan yhtä hyvin päättää useamman argumentin avulla, jolloin

puhumme monilähetyksestä (multiple dispatch). Esimerkiksi Common Lisp ja Cloju-

re tukevat yleisiä funktioita monilähetyksen kanssa. Common Lispissä yleinen funk-

tio voidaan valita yhden tai useamman luokan tyypin mukaan, Clojure erityisesti vielä

antaa käyttäjän määritellä mielivaltaisen funktion, jonka tuottaman arvon mukaan lä-

hettäminen tapahtuu. Clojuren käyttämästä yleisten funktioiden toteutuksesta keskus-

telemme syvemmin kohdassa 5.3.

Scala-kielessä on useita mekanismeja ilmaisuongelman ratkaisemiseksi. Niinkutsu-

tut tapausluokat (case classes) vastaavat yleisiä funktioita tyyppipohjaisen lähetyksen

kanssa, yhden tai useamman tyypin mukaan. (Odersky, 2014a, kohta 5.4) Lisäksi Scala

tukee implisiittisiä määritelmiä (implicit definitions) metodeille, luokille ja funktioil-

le. Tämä on paikkakoodin tapainen mekanismi, mutta tyyppiturvallinen tapa laajentaa

olemassaolevia rakenteita. Niinsanotut implisiittiset näkymät tarjoavat läpinäkyvän ta-

van antaa kielen tehdä sopivia tyyppimuunnoksia kehittäjän omalla koodilla. (Odersky,

2014a, luku 7)

34

Page 40: Clojure-kieli - UEF · 2016-01-14 · 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

user=> (defmulti Len type)#’user/Len

user=> (defmethod Len java.lang.String [s] (.length s))user=> (Len "abc")#=> 3

user=> (defmethod Len clojure.lang.IPersistentVector[v] (count v))

user=> (Len [1 2 10 5 4])#=> 5

Kuva 4: Clojuren monimetodit ilmaisuongelman eräänä ratkaisuna.

5.3 Monimetodit

Clojuren oma ratkaisu ilmaisuongelmaan on yleisten funktioiden toteutus nimeltään

“monimetodi” (multimethod). Monimetodin julkisivuna on yksi yhteinen nimi ja yhte-

näinen joukko argumentteja, ja metodin taustalla on useita eri toteutuksia, joista sopiva

valitaan ajonaikaisesti kutsussa käytettävien argumenttien arvojen mukaan. Tätä valin-

taa ja toteutuksen kutsumista sanotaan lähettämiseksi (dispatch). Toteutuksen valinnan

tekee käyttäjän määrittelemä valitsijafunktio.

Kuva 4 näyttää lyhyen esimerkin, jossa näytetään monimetodien käyttöön kuuluvia

makroja ja erikoiskutsuja. Makrolle defmulti annetaan monimetodin nimi ja käytetty

valitsijafunktio. Tämän valitsijafunktion lähtöjoukko on sama kuin monimetodin, eikä

funktion tuottamaa paluuarvoa käytetä muuhun käsittelyyn kuin sopivan toteutuksen

löytämiseen. Makrolla defmethod vastaavasti lisätään jokin uusi toteutus monimeto-

dille.

Koodissa toteutan erilaisten arvojen pituuksia mittailevan Len-monimetodin. Merkki-

jonoilta pituudeksi määrittelemme merkkijonon merkkien lukumäärän ja kokoelmilta

yleisesti niiden sisältämien alkioiden lukumäärän. Matkimme arvon luokan mukaan

tapahtuvaa yksinkertaista lähettämistä, ja siihen käytämme Clojuren vakiokirjastoon

kuuluvaa type-funktiota valitsijafunktiona.

Ilman yhtäkään toteutusta, johon arvo lähetettäisiin, monimetodilla ei tee mitään. To-

teutusten lisäämiseksi monimetodille käytämme makroa defmethod, joka käyttäytyy

kahden ensimmäisen argumenttinsa jälkeen kuin funktioita määrittelevä defn-makro.

35

Page 41: Clojure-kieli - UEF · 2016-01-14 · 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

user=> (defmulti pituus count)user=> (defmethod pituus :default [_] "joku muu pituus")user=> (defmethod pituus 1 [_] "pituus yksi")user=> (defmethod pituus 2 [_] "pituus kaksi")user=> (pituus "ab")#=> "pituus kaksi"user=> (pituus "a")#=> "pituus yksi"user=> (pituus "hei maailma!")#=> "joku muu pituus"

Kuva 5: Clojuren monimetodit voivat lähettää mielivaltaisen arvon perusteella. Oletus-käsittelijän käyttöä.

Ensimmäinen argumentti on viite siihen monimetodiin, johon olemme toteutusta li-

säämässä. Toinen argumentti on se valitsijafunktion laskema arvo, jonka mukaan tämä

toteutus on valmis käsittelemään monimetodin kutsun. Makron loput argumentit ovat

yhtenevät funktioita esittelevän defn-makron kanssa.

Kuvassa 4 ensin toteutamme Len-metodin Javan String-tyypille, ja täten String-

luokkaa on jatkettu uusilla metodeilla ilman alkuperäisen koodin muokkaamista tai

uudelleenkääntämistä. Vertailun vuoksi kuvan koodin lopussa teemme vielä vastaa-

vanlaisen toimenpiteen Clojuren vektoreille.

Kuvan 4 ratkaisua voimme jatkaa uusin operaatioin, eli funktionaaliseen tapaan kir-

joittamalla uusia monimetodeita. Mitään kuvan koodista ei myöskään tarvitse muokata

tätä laajennosta varten. Vastaavasti uuden tietotyypin esitteleminen ja tukeminen vaa-

tii vain yhden uuden defmethod-toteutuksen kirjoittamisen kutakin tuettua operaatiota

kohden. Clojuren monimetodit täyttävät näiltä osin kaikki ilmaisuongelman ratkaisulta

vaatimamme ominaisuudet.

Monimetodit ja valitsijafunktio voidaan kirjoittaa lähettämään mielivaltaisten arvojen

mukaan. Kuvan 5 esimerkissämme määrittelemme pituus-nimisen monimetodin, jo-

ka delegoi varsinaisen käsittelyn eteenpäin argumentin pituuden suhteen. Funktioargu-

mentti count palauttaa sille annetun kokoelman (mukaanlukien merkkijonojen) pituu-

den. Ensin määrittelemme monimetodillemme oletuskäsittelijän, jota kutsutaan siinä

tapauksessa, kun yksikään käsittelijä ei vastaa valitsijafunktion laskemaa arvoa.

Edellisessä esimerkissä lähetämme yhden argumentin perusteella, mutta valitsijafunk-

36

Page 42: Clojure-kieli - UEF · 2016-01-14 · 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

user=> (defmulti collide (fn [a b] [(odd? a) (odd? b)]))user=> (defmethod collide [true false] [_ _] "hmm...")user=> (collide 1 1)#=> IllegalArgumentException No method in multimethod ’collide’#=> for dispatch value: [true true]

user=> (collide 1 2)#=> "hmm..."

Kuva 6: Clojuren monimetodit monilähetyksessä: lähettäminen toimii rakenteisen ar-von lähetyksen mukaan.

tio voi vastaanottaa useita argumentteja ja vastaavasti palauttaa rakenteisen paluuarvon

— tavallisesti vektorin — jolla on riittävästi ulottuvuutta käsitellä kaikki ongelman

sanelemat tapaukset. Tässä tapauksessa oletusarvojen määrittely ja toteuttaminen jää

valitsijafunktion harteille.

Kuvassa 6 on esimerkki kahden muuttujan funktioista ja koosteisesta valitsijafunktion

paluuarvosta. Menetelmällä voimme imitoida usean tyypin mukaan tapahtuvaa lähet-

tämistä. Lisäksi kuvan 6 testikutsusta (collide 1 1) näemme, mitä käy, kun oletus-

käsittelijää ei ole määritelty.

Monimetodien toteutukset voivat myös mennä päällekkäin keskenään siten, että saa-

tuja argumentteja olisi valmis käsittelemään useampi toteutus. Clojure antaa asettaa

tietyn toteutuksen etusijalle ristiriitaisessa tapauksessa. Myös ad hoc -hierarkioiden

määritteleminen avainsanojen välille on mahdollista. (Fogus et al., 2011) Näitä hie-

rarkioita käyttämällä monimetodien valitsijafunktioissa voidaan mallintaa tyypillinen

olioalgebra.

Clojuren monimetodeilla ratkaisemme ilmaisuongelman käyttämällä yhtä ylimääräis-

tä kutsukerrosta metodia käyttävän koodin ja metodin välillä. Tämä välikerros näkyy

suorituskyvyssä yhtenä ylimääräisenä funktiokutsuna. Monimetodit voidaan toteuttaa

millä tahansa kielellä, jonka suoritusympäristö tukee reflektion käyttöä ajonaikaiseen

argumenttien tyyppien tutkimiseen.

37

Page 43: Clojure-kieli - UEF · 2016-01-14 · 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

6 Kompleksisuudenhallinta

Yksinkertainen ei ole aina helppoa. Rich Hickey (2011) perustelee asiaa sanakirja-

määritelmistä johtamalla: monimutkainen tarkoittaa kietoutunutta ja sisäisesti keske-

nään riippuvista osista muodostunutta kokonaisuutta. Yksinkertainen on monimutkai-

sen vastakohta: erillisiä komponentteja, jotka eivät ole toisiinsa toivottomasti sitoutu-

neita. Yksinkertainen arkkitehtuuri voi olla vaikea saada rakennettua ilman tarpeetto-

man työläitä rakenteita. Tässä luvussa käsittelemme joitakin tekniikoita, joilla Clojure

ja kielet yleensäkin taistelevat monimutkaisia rakenteita vastaan.

Jo luvussa 5 otimme kantaa yhteen kompleksisuutta karsivaan menetelmään. Tässä lu-

vussa jatkamme muutamien muiden työkalujen kanssa. Kohdassa 6.1 esittelen sovel-

lusaluekohtaiset kielet, kohdassa 6.2 esittelen pysyvät tietorakenteet ja muuttumatto-

mat arvot kestävän logiikan taustalla, ja kohdassa 6.3 käyn läpi Clojuren tapaa hallita

muutoksia hallitusti STM-moottorin avulla.

6.1 Makrot ja sovellusaluekohtaiset kielet

Lisp-kielissä ovat aina olleet vahvasti mukana makrofunktiot, ja niiden oikeaoppinen

käyttäminen on ratkaissut ongelmia siististi. Anekdotaalisesti voisi sanoa, että oikeaop-

pisessa lisp-ohjelmoinnissa ei ratkaista ongelmia, vaan kirjoitetaan sovellusaluekohtai-

sia kieliä (domain specific languages; DSL), jotka parhaiten sopivat ongelman teemaan

ja helpottavat ratkaisun miettimistä. Paul Graham (1994, luku 1) kirjoittaa makrojen

kyvyistä muovata kieltä niin syvästi kohti tarvittavaa muotoa, että ratkaistava ongelma

alkaa tuntua triviaalilta uusien työkalujen valossa.

Sovellusaluekohtainen kieli tai sovelluskieli kehittäjän näkökulmasta voi tarkoittaa yk-

sinkertaisesti sopivaa joukkoa funktioita ja luokkia, jotka on kirjoitettu liiketoiminta-

logiikka mielessä. Tätä voi tehdä missä tahansa kielessä, mutta lispien makroilla on

tavallisesti voitu entisestään hälventää kielen omien rakenteiden näkyvyys ongelman

“tieltä”.

Myös Clojuressa kielen tarjoamien rakenteiden ja sovelluskielten välinen raja hämär-

tyy vastaavalla tavalla makrojen myötä. Kielen tarjoama vakiokirjasto käy oikein suun-

nitellulle sovelluskielelle sellaisenaan työkaluiksi. (Fogus et al., 2011, luku 13.1) Fogus

ja Houser jatkavat, että jo käyttämällä oman sovelluskielen argumentteina ja paluuar-

38

Page 44: Clojure-kieli - UEF · 2016-01-14 · 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

[:html[:body[:h1 "Hei maailma"][:p "Hiccup tekee näin."]]]

<html><body><h1>Hei maailma</h1><p>Hiccup tekee näin.</p>

</body></html>

Kuva 7: Hiccup kääntää Clojure-tietorakenteita (ylempi) HTML-koodiksi (alempi).

voina Clojuren omia tietotyyppejä ja jonoabstraktiota on kirjastoa käyttävän kehittäjän

saatavilla huima määrä valmiiita ja idiomaattisia työkaluja. Clojuren tietorakenteista

HTML-koodia generoiva Hiccup-kirjasto on eräs esimerkki tästä, ja sen käsittelemme

tässä kohdassa myöhemmin.

Emerick et al. (2012, luku 5) tuovat esille, kuinka kielissä yleisesti kirjoitetaan ratkai-

suja kerros kerrokselta, aina nousten abstraktiotasoissa ylöspäin. Tämä tapa mallintaa

ja toteuttaa sovellus alhaalta ylöspäin on lisp-kielillä perinteisesti käytetty tapa, koska

lisp-kehotteet (REPL) sallivat nopeatempoista koodin kokeilemista järjestelmän tilassa

ilman. Kunnollisen abstraktiotason puuttuminen johtaa tarpeettomaan koodin toistoon

ja turhaan seremoniaan (boilerplate-koodiin). Kielissä, joissa on riittävän hyvä makro-

tuki, voidaan turha toisto vähentää olemattomaksi.

“If you give someone Fortran, he has Fortran. If you give someone Lisp, he has any

language he pleases.” – Guy Steele (Fogus et al., 2011)

Perinteisten lisp-makrojen idiomaattinen toiminta on sitä, että makroilla kuvaillaan so-

vellusalueen olioita ja asioita, ja makrot kääntyvät funktiomääritelmiksi taustalla. Näin

voidaan myös optimoida suoritusaikaa vaativa laskenta tehtäväksi kääntämisen aikana.

(Fogus et al., 2011; Graham, 1994)

Fogus ja Houser (2011, luku 13.1) luonnehtivat lisp-kielistä ongelmanratkaisua niin,

että sovellusalueen ongelman käsittely alkaa ongelman formalisoinnilla sovelluskie-

leksi. Vahvasti ja staattisesti tyypitetyissä kielissä vastaava formalisointi käsittäisi käy-

tettyjen tyyppien määrittelemistä ja funktioiden lähtö- ja maalijoukkojen luonnostelua.

39

Page 45: Clojure-kieli - UEF · 2016-01-14 · 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

(select author(with country)(where (like :first_name "Ch%"))(order :last_name :asc)(limit 1)(offset 1))

(select author(fields :first_name :last_name)(where (or (like :last_name "C%")

(= :first_name "Mark"))))

Kuva 8: Kaksi esimerkkiä Korma-kirjaston SQL:ksi kääntyvästä kyselykielestä. Esite-tyt funktiot ja makrot on tuotu Korman nimiavaruuksista. (Emerick et al., 2012)

Makroilla voi toisaalta kirjoittaa ja luoda funktioita, toisaalta siistiä ja rakentaa jo-

tain keskitettyä tietorakennetta pinnan alla ja tarjoilla valmis tuotos kehittäjän käytet-

täväksi. Nykyaikainen deklaratiivinen määritteleminen, eli halutun toiminnallisuuden

kuvaileminen tietorakenteina, ja sen lähettäminen argumentteina rakentajafunktioille

sopii myös makroille ja datakeskeiselle Clojurelle hyvin. Makrot eivät tässä mieles-

sä enää ole välttämättömiä työvälineitä saumattoman sovelluskielen kirjoittamiseksi,

mutta optimoinnin ja esteettisen siistinnän nimissä ne voivat pitää edelleen paikkansa.

Hiccup on suosittu kolmannen osapuolen Clojure-kirjasto, joka kääntää Clojure-

tietorakenteita XML- ja HTML-pohjaiseksi lähdekoodiksi. Kuva 7 antaa esimerkin

Hiccupin toiminnasta ja sen käyttämästä syntaksista. Koska Hiccupin kaikki argumen-

tit ovat puhdasta Clojurea, on mahdollista rakentaa ja muotoilla syöte Hiccupille käyt-

tämällä Clojuren kaikkia vakiokirjaston funktioita. Ero hyvin suunnitellun funktion,

mitä Hiccup käytännössä on, ja sovelluskielen välillä on suorastaan mitätön. Sekä Fo-

gus et al. (2011) että Emerick et al. (2012) pitävät kirjastoa idiomaattisena tyyppiesi-

merkkinä hyvästä ja dataorientoituneesta sovelluskielestä.

Eräs esimerkki sovelluskielestä on .NET:n versiossa 3.5 ja C#:n versiossa 3.0 lansee-

rattu LINQ-kyselykielimoottori. LINQ:n avulla voidaan tehdä SQL-kyselyitä muistut-

tavia lausekkeita, jotka kääntäjä optimoi käytettyjen tietorakenteiden ja tietolähteiden

mukaan tehokkaasti ajettavaksi tavukoodiksi. Esimerkiksi erilaiset aggregaatiot suuris-

ta tietomassoista ovat nopeita ja samalla lyhyitä kirjoittaa LINQ-kyselyinä.

Yleinen paikka sovelluskielelle syntyy tietokantahakujen ja SQL:n käytön tarpeesta.

40

Page 46: Clojure-kieli - UEF · 2016-01-14 · 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

(defn meters->feet [m] (* m 3.28083989501312))

(meters->feet 9.2);=> 30.1837270341207

Kuva 9: Yksikkömuunnosfunktio yksinkertaisimmillaan. (Fogus et al., 2011)

Siinä missä SQL on itsessään jo määritelmällisesti DSL, Clojurelle löytyy useita kir-

jastoja, joilla sen tietorakenteista voidaan edelleen kääntää SQL-kyselyitä tietokannalle

ajettavaksi. Eräs näin käytetyistä kirjastoista on Korma, jonka tukemasta sovelluskie-

lestä on lyhyt esimerkki kuvassa 8. (Emerick et al., 2012) Korma käyttää enemmän

funktio- ja makropohjaisia avusteita kyselyiden rakentamisessa, vaikka puhtaampi da-

takeskeinen syöte Hiccupin tapaan olisi yhtälailla mahdollista. Kuvan 8 select-makro

palauttaa esikäännetyn kyselykuvauksen, joka voidaan edelleen antaa varsinaisen ky-

selyn suorittavalle funktiolle.

Fogus ja Houser (2011, luku 13.1) demonstroivat yksikkömuunnoksiin keskittyvän so-

velluskielen kirjoittamista puhtaalta pöydältä. Siinä missä kielestä kävisi jo yksinker-

tainen kokoelma funktioita kuvan 9 tapaan, kannattaa heidän mukaansa toteutus abstra-

hoida laajennettavuuden nimissä jo alusta alkaen hieman vapaammaksi. Oikein valitun

abstraktion kanssa on luontevaa sovittaa mukaan myös yksikköjen välisiä suhteita niin,

että myös Clojureen vähemmän perehtynyt ymmärtää, mitä määritelmillä on tarkoitus

tehdä.

He esittelevätkin yhden apufunktion ja yhden makron – yhteensä 22 riviä koodia – joil-

la kuvan 10 mukainen yksikköjä määrittelevä makro syntyy. Sovelluskielelle syntynyt

syntaksi on sellaista muotoa, että Clojureen perehtymätönkin kykenee tulkitsemaan ja

kirjoittamaan määritelmiä. Taustalla piilevän koodin vähäisyys vähentää huolimatto-

muudesta ja muista ihmissyistä johtuvia ohjelmavirheitä ja auttaa ajattelemaan ongel-

maa enemmän sovellusalueen termein.

Foguksen ja Houserin (2011) versio hieman lyhennetyssä versiossa, missä korjasin

tekstissä olleen lyöntivirheen ja kirjoitin makron lyhyemmässä, vaikka hitaammas-

sa muodossa, menee kuvan 11 listauksen tapaan. Työskentelemme siis käyttäjältä

saatujen muunnossääntöjen (kuva 10) voimin ja haluamme tuottaa säännöistä yhden

uuden funktion, joka tekee yksikkömuunnoksia ennalta kiinnitettyyn yksikköön, ku-

van tapauksessa metreiksi. Makro defunits-of tuottaa käännöksen aikana funktion

41

Page 47: Clojure-kieli - UEF · 2016-01-14 · 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

(defunits-of distance :m:km 1000:cm 1/100:mm [1/10 :cm]:ft 0.3048:mile [5280 :ft])

(units-of-distance 23 :mm);=> 23/1000

Kuva 10: Pituusyksiköitä määrittelevän sovelluskielen käyttöesimerkki. (Fogus et al.,2011)

units-of-distance, kun sitä on kutsuttu esimerkin syötteellä.

Säännöt kerätään makron alussa kuvaukseksi, joka on seuraavaa muotoa:

{:m 1

:km 1000

:cm 1/100

:mm [1/10 :cm]}

Tämäntapainen kuvaus (makron määritelmässä käytetty arvo units-map) käy apu-

funktiolle relative-units hyvin argumentiksi niin, että “vaikea työ” on ulkoistettu

makron määritelmästä helpommin testattavaan funktioon. Rekursiivista sisäkkäisten

määritelmien aukipurkamista varten tämä onkin hyvä idea. Kuvan 11 versio säilöö tä-

män kuvauksen sellaisenaan generoitujen funktioiden sisään ja Foguksen ja Houserin

versio laskee makrovaiheessa kaikki yksiköt valmiiksi auki pienenä optimointitoimen-

piteenä.

Makro luo lopuksi apufunktion, joka nimetään käyttäjän toiveiden mukaisesti

unit-of- -alkuiseksi symboliksi. Itse funktion runko on yksinkertainen tulo, kun käy-

tämme apufunktiota avuksemme. Tuloksena on käytöltään Foguksen ja Houserin työn

kanssa toiminnallisesti identtinen makro.

42

Page 48: Clojure-kieli - UEF · 2016-01-14 · 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

(defn relative-units [u units](let [spec (u units)](if (nil? spec)(throw (Exception. (str "Undefined unit " u)))(if (vector? spec)(let [[conv to] spec](* conv(relative-units to units)))

spec))))

(defmacro defunits-of [name base-unit & conversions](let [magnitude (gensym "magnitude")

unit (gensym "unit")units-map (into {base-unit 1}

(map vec (partition 2 conversions)))]‘(defn ~(symbol (str "units-of-" name))[~magnitude ~unit](* ~magnitude(relative-units ~unit ~units-map)))))

Kuva 11: Yksikkömuunnosten apufunktio ja makro. (Fogus et al., 2011)

6.2 Pysyvät tietorakenteet suunnittelussa

Pysyvät ja muuttumattomat tietorakenteet johtavat siihen, että tiedon muuttelu on eri-

tyinen, erikseen suunniteltava operaatio, ja siten koordinoitava huolellisesti. Muuttu-

maton tieto oletuksena avaa ovet koodille, joka on oletuksena puhdasta.

Clojuren pysyvät tietorakenteet pohjautuvat käytännössä kahden pysyväksi suunnitel-

lun tietorakenteen päälle: linkitettyjen listojen ja hakupuiden päälle. Chris Okasakin

väitöskirjasta (1999) löytyvien algoritmien nojalla perustelen, kuinka nämä kaksi tie-

torakennetta voidaan toteuttaa pysyvästi yksinkertaisimmissa tapauksissa.

Pysyvä lista on Clojuren tapauksessa yhteen suuntaan linkitettynä triviaalitapaus: lis-

tasta L = (L0, L1, . . . , Ln) tehtäessä uutta kopiota lisäämällä uusi alkio a listan alkuun

saadaan aikaan uusi lista, joka alkaa alkiosta a: L′ = (a, L0, . . . , Ln). Lista L alkioi-

neen uudelleenkäytetään kokonaan lisäystapauksessa. Kahta listaa xs = (xs0, . . . , xsn)

ja ys = (ys0, . . . , ysm) yhdistettäessä joudutaan lista xs kopioimaan kokonaan, koska

alkion xsn osoitin pitää kirjoittaa osoittamaan tyhjeen sijasta kohti alkiota ys0. Vastaa-

vasti ketjureaktion tavoin alkio xsn−1 pitää päivittää osoittamaan kohti uutta alkiota xs′n

43

Page 49: Clojure-kieli - UEF · 2016-01-14 · 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

a

b

c h

d

f

g

p

(a) Esimerkkipuu p, jossa on alkiot(a, b, c, d, f , g, h).

a

b

c h

d

e

f

g

p

d'

f'

g'

p'

(b) Päivitetty puu p′, jossa on edellisten li-säksi uusi elementti e.

Kuva 12: Kaksi puuta, joiden alkiot sisäjärjestyksessä luettuna ovat järjestyksessä.(Okasaki, 1999)

ja niin edelleen. (Okasaki, 1999)

Alkion muuttaminen ja poistaminen vastaavalla tavalla johtavat kaikkien alkioiden ko-

pioimiseen listan alusta aina poistettavaan tai muutettavaan alkioon asti. Loput listasta

säilyy käyttökelpoisena sellaisenaan, ilman kopiointia. Toinen seuraus on, että molem-

piin suuntiin linkitetty lista ei ole mielekäs ratkaisu pysyvän listan pohjaratkaisuksi,

koska joutuisimme käytännössä kopioimaan koko listan alusta loppuun muuttuvien

osoitteiden takia. Listan alkiolle tehtävät operaatiot ovat siis näin kuvatussa algorit-

missa aikavaativuusluokkaa O(n), missä n on kohteena olevan alkion sijainti, tai ylei-

semmin johdettuna listan koko.

Pysyvä puu on Clojuressa 32-haarainen hakupuu, mutta käsitelkäämme tässä binääri-

puun tapaus yksinkertaisemman esityksen takia. Oletetaan puu, jossa on alkiot sisäjär-

jestyksessä (a, b, c, d, f , g, h) niin, että arvot ovat myös samassa suuruusjärjestyksessä

kirjaimen mukaan. Listaesityksenä kyseinen puu voidaan esittää esimerkiksi seuraa-

valla tavalla: (d (b (a c)) (g ( f h))), tai visuaalisena puugraafina kuten kuvassa 12a.

Elementin e, d < e < f , lisääminen puuhun siten, että järjestys säilyy, tapahtuu aset-

tamalla se alkioiden d ja f väliin: sisäjärjestetyssä hakupuussamme solmun f vasem-

maksi lapsisolmuksi. Puun solmuelementti tietää sisältönsä lisäksi vasemman ja oikean

lapsisolmunsa osoitteet. Muuttuvan puun kanssa lisäystä varten meidän siis tarvitsisi

muuttaa vain solmua f . Pysyvän puun tapauksessa meidän täytyy korvata solmu f

uudella versiolla, samoin kuten kaikki solmun f esivanhemmat juureen saakka. Jos

aloitamme juuresta p, niin korvaamme sen uudella solmulla p′, jossa voimme uudel-

44

Page 50: Clojure-kieli - UEF · 2016-01-14 · 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

leenkäyttää kokonaan vasemman lapsipuun, koska muuttunut solmu sijaitsee oikean

lapsipuun alla. Toistamme tätä kunkin juuren kanssa uudelleenkäyttäen muuttumatto-

mia lapsipuita. Okasaki (1999) mainitsee, että Sarnak ja Tarjan (1986) nimesivät tämän

menetelmän polun kopioinniksi (path copying).

Kuva 12b edustaa tätä päivitettyä puuta p′ siten, että tummennetut solmut ovat uusia

ja vaaleammat ovat uudelleenkäytettyjä tai käyttämättömiä solmuja vanhasta puusta.

Näemme, että tässä binääripuun tapauksessa voimme jakaa vanhan puun rakenteesta

aina yhden lapsisolmun kultakin muuttuvalta solmulta. Kun haaroja on kahden sijaan

32, 31 haaraa voidaan uudelleenkäyttää ja yksi pitää uusia. Näin tehty muutos, kuten

tässä, tietää alkion lisäämiselle aikavaativuutta O(loga n), missä parametri a on puun

haarautumiskerroin, eli “leveys”.

Elementin poistaminen puusta seuraa kuten lisäyskin, ja elementin vaihtaminen uuteen

on järjestetyn hakupuun tapauksessa vanhan poistamisen ja uuden lisäämisen yhdistet-

ty operaatio. Kaikkien perusoperaatioiden aikavaativuus on siis luokkaa O(log n).

Pysyvä vektori seuraa Clojuren pysyvästä puuratkaisusta, eikä ole käytännössä suora-

saantinen. Vektoria tai taulukkoa voi ajatella kuvauksena indeksinumeroista arvoihin.

Clojure käyttääkin 32-haaraisia hakupuitaan vektoreina samoilla algoritmeilla kuin

puiden tapauksessa. Vektorit eivät ole tässä mielessä suorasaantisia, mutta käytännön

rajallisilla, lokaaleilla resursseilla haku on vakioaikaista: esimerkiksi 230 elementin

vektorista, jollainen vaatii pienimmillään 8 gigatavua muistia, tarvitsee tehdä vain 6

vertailua päästäkseen halutun indeksin osoittaman arvon lähettyville.

Arvot

Rich Hickey (2012) määrittelee arvon asiana, jolla on suuruus tai merkitys itsessään.

Muuttumattomuus yksinään johtaa datan arvopohjaisuuteen. Muuttuva tieto ei omaa

tällaista merkitystä, koska se on sidottu aikaan ja paikkaan, ja voi olla saavuttamatto-

missa ilman erityisen toiminnallisuuden käyttämistä. Paikkakeskeinen ohjelmointi joh-

taa puolustavaan kopiointiin, (defensive copying) jota tehdään välttääksemme näyt-

tämästä muutoksia, jotka näkyisivät kutsuvissa funktioissa tai muissa säikeissä. Sii-

nä missä muuttumaton data ja arvojen käyttäminen kannustaa funktionaaliseen ohjel-

mointiin ja puhtaisiin funktioihin, Hickey jatkaa, että kääntäen muuttuva data kannus-

taa epäpuhtaaseen, imperatiiviseen ohjelmointiin.

Hickeyn (2012) mukaan arvot ovat niin paikka-, ohjelma- kuin kieliriippumaton tapa

45

Page 51: Clojure-kieli - UEF · 2016-01-14 · 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

välittää tietoa. Ilman erityisiä metodeja, joita olisi välttämättä käytettävä tiedon käsit-

telemiseksi, tieto on vapaampaa ja universaalimpaa. Muuttuva data johtaa sovellusten,

kielten ja logiikan sitoutumiseen. Hickey sanookin, että arvot ovat paras rajapinta, mi-

tä API:lla voi olla. Toisin kuin älykäs, metodein kiedottu data, arvot myös aggregoi-

tuvat arvoiksi: lista lukuja on edelleen arvo, siinä missä kuvitteellinen LukuLista ei

ole. Arvoilla on lisäksi vakaa esikuva fysikaalisessa maailmassa, mistä voimme hakea

intuitiota.

Faktat ja “tämänhetkiset asianlaidat” ovat arvoja, koska aikaleimatut faktat eivät muu-

tu. Fakta, joka on pitänyt paikkansa jonain vuonna ja ei ehkä pidä nykypäivänä, on

edelleen samaa tietoa kuin syntyhetkellään – korkeintaan vanhentunutta sellaista. Kun

siirrämme tiedonjyväsen vanhentuneena syrjään ajantasaisemman tiedon edeltä, alam-

me käsitellä vanhaa faktaa historiana. Se ei muutu tai sitä ei poisteta. Tiivistetysti Hic-

keyn mukaan faktat eivät muutu; syntyy vain uusia faktoja.

6.3 Tilanhallinta ja STM

Clojuressa toimitaan pääsääntöisesti ja oletuksena pysyvillä arvoilla, mutta kielen

suunnittelussa on tunnustettu tarve muutokselle. Muutoksen oikeaoppinen ja ekspli-

siittinen hallinta johtaa samalla hyvään, säieturvalliseen ohjelmointiin. (Fogus et al.,

2011)

Clojuressa on useita viitetyyppejä (reference types), jotka vastaavat identiteettejä. Vii-

tetyyppi sekä sen viittaama arvo ovat edelleen pysyviä arvoja; pikemminkin viitteen

seuraaminen (dereferencing) on sidottu kutsuaikaan ollen siten epäpuhdas operaatio.

Erilaisia seurattavia viitetyyppejä Foguksen ja Houserin (2011) esityksen perusteella

Clojuressa ovat esimerkiksi seuraavat:

Atomi (Atom) on atomisesti päivitettävä ja haettava viitetyyppi. Atomia päivitetään

synkronisesti vertaa-ja-vaihda (compare-and-swap; CAS) -semantiikalla kutsu-

vassa säikeessä.

Viite (Ref) on koordinoitu viitetyyppi, eli useita viitteitä voi päivittää yhden tapahtu-

man sisällä siten, että kaikki päivitykset tapahtuvat atomisesti yhtenäisenä.

Agentti (Agent) on asynkroninen päivittäjä. Agentin semantiikka muihin viitetyyp-

peihin verrattuna on enemmän viestinlähetyksellistä ja toisessa prosessissa ta-

46

Page 52: Clojure-kieli - UEF · 2016-01-14 · 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

pahtuvaa. Agenteilla voidaan hallita esimerkiksi IO-rajoitettuja resursseja luon-

tevammin kuin muilla viitetyypeillä.

Muuttuja (Var) on säikeen paikallinen (thread-local) viitetyyppi: säieturvallisuus taa-

taan estämällä viitteen jakaminen muihin säikeisiin. Muuttujia voi tehdä sekä

leksikaalisella että dynaamisella tasolla.

Keskitymme tässä kohdassa käsittelemään atomeita ja viitteitä: nämä kaksi viitetyyp-

piä auttavat hajautettujen muutosten hallinnassa; agentit ovat lähinnä tapa siirtää las-

kentaa muille säikeille ja muuttujat ovat pääasiassa suorituskykyoptimointia varten.

Clojuren käytettävissä on myös kaikki Javan vakiokirjastosta löytyvät hajautetun las-

kennan työkalut, mukaanlukien atomiset lukot ja semaforit. Clojuren vakiokirjastossa

on myös toteutukset futuureille ja lupauksille (promises).

Useasta säikeestä saatavilla olevat viitetyypit toimivat Clojuren toteuttamassa STM

(Software Transactional Memory) -pinossa, joka toteuttaa niinsanotuista ACID-

ominaisuuksista kolme ensimmäistä: atomiset (atomic), yhtenäiset (consistent) ja eris-

tetyt (isolated) päivitykset. Ominaisuuksista neljäs, tiedon pysyvyys, (durability) ei on-

nistu määritelmällisesti vain keskusmuistissa pidettävän tiedon kanssa. (Emerick et al.,

2012)

Tapahtuman atomisuus tarkoittaa, että muutokset menevät läpi joko kaikki tai ei yk-

sikään. Keskeneräisiä tuloksia ei anneta viedä tapahtuman ulkopuolelle. Tapahtuman

yhtenäisyys tarkoittaa sitä, että tapahtuman jälkeinen tila on yhtenäisessä muodossa.

Tapahtuman eristyneisyys liittyy samanaikaisten tapahtumien keskinäiseen tiedonjaka-

mattomuuteen: eristetty tapahtuma suoritetaan kuin se olisi ainut suorituksessa oleva

rutiini. Tiedon pysyvyys takaa sen, että tapahtuman ulostulo on varmistettu pysyvään

muistiin, eikä onnistuneen tapahtuman jälkeen voi enää hävitä. (Harris et al., 2010)

Tapahtumat Clojuren mallissa toimivat ilman viitteiden erityistä lukitsemista, ja tämän

mahdollistamiseksi tapahtumat toimivat MVCC (multiversion concurrency control) -

mallin mukaisesti. MVCC on mekanismi, jolla tapahtuman käyttämät viitteet eriste-

tään muusta maailmasta tapahtuman ajaksi. Malliin kuuluvat tapahtuman lopuksi teh-

tävät jälkiehdot arvojen säilyttämisessä: jos tapahtumassa käytetty viite on muuttunut

toisessa säikeessä tapahtuman aikana, toistetaan tapahtuma uudelleen tuoreilla arvoil-

la. (Fogus et al., 2011, luku 11.1)

Käytettyjen mekanismien ansiosta keskeneräiset tapahtumat eivät koskaan estä luke-

47

Page 53: Clojure-kieli - UEF · 2016-01-14 · 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

function update-atomically(R, f):while forever:cur-value = deref(R)new-value = f(cur-value)if compare-and-set(R, cur-value, new-value) is Success:return

function compare-and-set(R, cur-value, new-value):if deref(R) == cur-value:set R = new-valuereturn Success

else:return Failure

Kuva 13: Perusidea vertaa-ja-vaihda -operaatioiden taustalla.

masta arvoja muilta lukijoilta, ja onnistuneen tapahtuman valmistuttua kaikki sen teke-

mät muutokset ulkomaailmaan ilmestyvät näkyville kerralla atomisesti. Toisaalta Clo-

jure olettaa, että tapahtumat ovat aina puhtaita funktioita. Kaikkia viitetyyppejä päi-

vitetään funktiokutsuilla kannustaen kehittäjiä kirjoittamaan lyhyitä ja atomisia päi-

vityksiä. Myös viitetyyppien viittaamien arvojen on oltava muuttumattomia. (Hickey,

2014)

Atominen päivitys atomille R vertaa-ja-vaihda-operaation avulla on loogisesti esitetty-

nä kuvassa 13. Päivittäminen tapahtuu niinsanotussa nopeassa silmukassa (spin loop),

ja päivitystä yritetään uudelleen niin pitkään, kunnes kaikki säikeet ovat yhtä mieltä

arvoista.

STM on yleisesti ottaen yksinkertainen ja kestävä idea säieturvalliseen ohjelmoin-

tiin. Tärkein yksinkertaistava seikka on tarjota yhtenäinen rajapinta muutoksille si-

ten, että muita idiomaattisia tapoja tehdä muutoksia ei ole. Muutoslogiikka kannuste-

taan kirjoittamaan puhtaisiin funktioihin, jotka mielellään riippuisivat vain annetuista

lokaaleista argumenteistaan, ei suljetuista arvoista. Koska muutoksia tehdään atomi-

sissa tapahtumissa, ja muuttumaton tieto voidaan jakaa säikeiden välillä sellaisenaan,

moni eksplisiittiseen lukitukseen liittyvistä ongelmista katoaa. Clojuren toteuttamassa

STM-ratkaisussa on potentiaalisesti vain kahdenlaista ongelmaa Foguksen ja Houserin

(2011) mukaan: lukittautumista (livelock) ja kirjoitusvääristymää (write skew).

Elävältä lukittautuminen tapahtuu, kun kaksi eri tapahtumaa yrittävät päivittää samaa

48

Page 54: Clojure-kieli - UEF · 2016-01-14 · 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

viitettä. Kumpikaan ei pääse ikinä päivityssilmukasta pois, koska toinen ehtii aina

muuttaa arvon tapahtumien aikana. Clojure määrittelee tapahtumien uudelleenkäyn-

nistymisille rajat, joiden tullessa vastaan annetaan virhe. Toinen, hieman teknisempi

tapa välttää lukittautumista ja virheitä on antaa ensimmäisenä aloitetun tapahtuman

suoriutua rauhassa, ja uudempien tapahtumien kokeilla päivittämistä silmukassa, käy-

tännössä odottaen ensimmäisenä aloittaneen valmistumista.

Kirjoitusvääristymä voi tapahtua MVCC-mallissa erityisesti silloin, kun tapahtuma

käyttää tietoa sellaisesta viitteestä, johon se ei itse kirjoita. MVCC varmistaa itse aina

päivitettävien viitteiden kohdalla, että samanaikaiset päivitykset eivät ole keskenään

ristiriitaisia. Tätä varmistusta ei tehdä automaattisesti viitteille, joista vain luetaan ta-

pahtuman sisällä. Tällöin viitteen muuttuminen tapahtuman aikana voi johtaa päivityk-

seen, joka tehdään vanhentuneen tiedon perusteella. Clojure tarjoaa tapahtuman sisäl-

lä käytettävän ensure-funktion, jolla voidaan varmistaa, että tapahtumassa käytettävä

viite ei muutu toisessa tapahtumassa suorituksen aikana. (Fogus et al., 2011, luku 11.1)

49

Page 55: Clojure-kieli - UEF · 2016-01-14 · 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

7 Yhteenveto

Tässä työssä tarkastelimme Clojure-kieltä ja muutamia kieleen liittyviä käsitteitä ja

ominaisuuksia. Työssä käsittelimme kielen syntaksia ja käyttöä, erilaisia sovellusalus-

toja, funktionaalista ja oliopohjaista ohjelmointia eroineen, ilmaisuongelmaa, makroja

ja sovellusaluekohtaisia kieliä, Clojuren STM-viitteitä, pysyviä tietorakenteita ja muut-

tumattomia arvoja.

Clojure on lisp-kieli, joka käyttää kaarisulkuihin käärittyjä S-lausekkeita syntaksinaan.

Syntaksiin kuuluu verraten vähän erikoistapauksia, joten se on helppo oppia. Nykyai-

kaiset perustietorakenteet – linkitetyt listat, vektorit, kuvaukset ja joukot – ovat hyvin

tuettuja, ja niille on annettu lyhyesti kirjoitettavat syntaksielementit. Clojuren tietora-

kenteet ovat pysyviä, eli ne ovat muuttumattomia ja ne jakavat rakenteensa keskenään

muistia säästääkseen. Clojuren funktiot ovat ensiarvoisia: niitä voidaan käyttää mui-

den arvojen tavoin osana tietorakenteita, muuttujia, muiden funktioiden argumentteja

ja funktioiden paluuarvoja. Clojuren tietorakenteet toteuttavat yhteisen jonoabstrak-

tion, jonka ansiosta kaikkea voidaan käsitellä polymorfisesti samoilla jonofunktioilla,

joita Clojuren vakiokirjastossa on noin sata. Clojuren lisp-kielisyys mahdollistaa myös

makrojen, eli funktioiden ohjelmakoodista ohjelmakoodiin, kirjoittamisen. Näin kieltä

voi laajentaa eteenpäin mielekkäästi omin voimin. Clojure toimii Java Virtual Machi-

nen päällä, ja Java-yhteensopivuus on hyvä.

Välitason sovellusalustat toimivat käyttöjärjestelmän ja korkean tason sovelluskoodin

välissä, ja näiden sovellustasojen hyödyntäminen on nykyisin erittäin yleistä. Sovel-

lusalustat, kuten Java Virtual Machine ja Common Language Runtime, tuovat alusta-

riippumattomuden ja yhtenäisten työkalujen lisäksi hyvät, optimoivat kääntäjät ja tul-

kit: nopeudessa JIT-tulkattu tavukoodi häviää natiivisti käännetylle ohjelmakoodille

useimmissa tapauksissa vain vähän, ja joskus se jopa voittaa natiivikoodin suoritus-

nopeudessa, koska ajonaikainen optimoija pystyy optimoimaan saman rutiinin useita

kertoja erilaisilla optimointiprofiileilla. Välitason alustalle kirjoitettua koodia ei välttä-

mättä tarvitse kääntää kuin kerran, ja tuloksena syntyvä tavukoodi käy kaikille alustan

tukemille ympäristöille sellaisenaan ilman uudelleenkääntöä.

Clojure toimii ensisijaisesti Java Virtual Machinen päällä, kuten moni muu suosittu uu-

den polven kieli, mukaanlukien Scala, Groovy, Jython ja JRuby. Java Virtual Machine

tukee dynaamista tyypitystä, reflektiota ja roskienkeruuta, ja alustan JIT-optimoijaa on

50

Page 56: Clojure-kieli - UEF · 2016-01-14 · 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

pitkään hiottu ja kehitetty. Toisaalta JVM käyttää tavukoodinaan hyvin oliopohjaista

kieltä, joka on suunniteltu lähinnä Javan kannalta. Esimerkiksi itsenäisten funktioiden

tukea, häntärekursion optimointia tai tukea sulkeumille ei ole. Nämä puutteet voi Clo-

juren kääntäjä ohittaa kääntämällä puuttuvat rakenteet sopiviksi oliopohjaisiksi käsit-

teiksi.

Common Language Runtime on hieman uudempi alusta kuin JVM, ja on teknisesti ke-

hittyneempi ja on suunniteltu tukemaan useita kieliparadigmoja. Clojuresta on CLR:lle

oma versionsa, ClojureCLR. Tämä versio on jäänyt vähemmälle kehitykselle kuin pää-

asiallinen JVM-versionsa.

JavaScript on hiljalleen noussut sovellusalustan asemaan verkkoselainten siivittämä-

nä viimevuosina, ja on potentiaalisesti eniten kirjoitettu kieli uusissa projekteissa. Se-

laimet kilpailevat keskenään sivunpiirron nopeudesta, ja sen seurauksena JavaScriptiä

tulkkaavat moottorit ovat muuttuneet nopeiksi, jopa JVM:n ja CLR:n veroisiksi. Ja-

vaScriptissä on kielenä omat ongelmansa, ja onkin syntynyt monia kielen puutteita

korjaavia kieliprojekteja, joiden päämääränä on kääntää omaa kieltään JavaScriptiksi,

jota onkin nyttemmin kutsuttu “webin konekieleksi”. Clojurella on oma ClojureScript-

projekti, joka kääntää Clojuren läheistä sisarkieltä JavaScriptiksi.

Luvussa 4 tarkastelimme funktionaalisen ja oliopohjaisen paradigman määritelmiä ja

paradigmojen lyhyttä historiaa kirjallisuudessa. Oliopohjaisen ohjelmoinnin teoria läh-

ti liikkeelle 50-luvulla, ja ideoita jalostettiin 60-luvulla. Smalltalk ja Simula edustavat

ensimmäisen aallon oliokieliä. Valtavirtaan oliopohjainen ohjelmointi nousi 90-luvun

alussa, kun ensin C++ ja sitten Java alkoivat kerätä suosiota.

Funktionaalinen ohjelmointi sitä vastoin syntyi matemaattisessa hengessä jo 30-luvulla

Alonzo Churchin lambdakalkyylin muodossa. John McCarthy suunnitteli kirjoitetta-

valle matematiikalle 50-luvulla syntaksin, josta syntyivät S-lausekkeet ja tietokoneilla

suoritettava lisp-kieliperhe. Milner suunnitteli ML-kielen 70-luvulla ja 80-luvulla sai-

vat alkunsa ensin kaupallinen Miranda ja sitten Mirandan avoin klooni, Haskell. Has-

kellista on kehittynyt vuosien saatossa vahva ja käytetty funktionaalinen kieli.

Oliopohjaisen ohjelmoinnin ohjenuora on mallintaa kaikki asiat olioina, jotka ovat

“substantiiveja”. Olioiden jäsenmetodit toimivat “verbeinä”. Valtakielissä tavataan

mallintaa metodit luokkien alaisiksi, ei suinkaan itsenäisiksi rutiineiksi. Tähän liittyy

älykkään datan käsite: kukin olio sisältää tarpeeksi toiminnallisuutta, jotta se osaa kä-

51

Page 57: Clojure-kieli - UEF · 2016-01-14 · 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

sitellä itse oman datansa yhtenäisessä ja kapseloidussa muodossa. Tämän vastakohta

on tyhmä data, tai paljas data, jolla ei ole piilotettua tilaa, eikä myöskään omia meto-

deitaan. Oliopohjaiseen paradigmaan kuuluu oleellisesti tiedon kätkentä eli kapselointi

niin, että muut oliot eivät pääse olion tietoon käsiksi. Tämä mahdollistaa kestävämmät

rajapinnat olioiden välillä, mikä johtaa ylläpidettävyyteen. Olioluokilla voi olla hierar-

kioita, ja toiminnallisuutta voidaan periä ylemmistä luokista. Alan Kay kuvaili oliopoh-

jaisen ohjelmoinnin ideoita ensimmäisenä 70-luvulla, ja määritteli, että oliopohjaisuus

on muuttumatonta tietoa ja polymorfismia.

Funktionaalinen paradigma on väljemmin määritelty käsite, ja määritelmä riippuu ko-

vasti määrittelijän koulukunnasta. Yhteisiä tekijöitä eri teoksista kuitenkin löytyy:

funktiot ovat ensiarvoisia entiteettejä ja ovat funktionaalisen kielen pienin rakennus-

osanen. Korkeamman tason funktiot ovat funktionaalisessa kielessä tuettuna. Lisäksi

funktionaalisissa kielissä tyypillisesti käsitellään enemmän arvoja kuin muistipaikkoja

sellaisenaan. Muuttumattomuus ja pysyvyys tulevat usein puhtaiden funktioiden suo-

simisen seurauksena. Puhdas funktionaalinen kieli pakottaa kielen tasolla puhtaiden

funktioiden kirjoittamiseen ja epäpuhdas funktionaalinen kieli vastaavasti ei pakota tä-

hän, vain kannustaa.

Funktionaalisissa kielissä funktioiden yhdisteet ovat luonnollisin tapa uudelleenkäyttää

rutiineja. Muuttumattomuus tuo kieleen arvosemantiikan, jonka edut näkyvät esimer-

kiksi säieturvallisessa ohjelmoinnissa ja ohjelmakoodin analysoinnin helpottumisessa.

Clojure on epäpuhdas funktionaalinen ohjelmointikieli, jossa on ensiarvoiset funktiot,

muuttumaton data ja pysyvät tietorakenteet. Clojure on lisäksi ahkerasti suorittava kie-

li, mutta laiskasti käyttäytyvien tietorakenteiden käyttö on idiomaattista ja läpitunkeva

käytäntö.

Luvussa 5 tutkimme ilmaisuongelmaa; kuinka se syntyy ja joitakin sen ratkaisuja. Il-

maisuongelma on laajenevien vaatimusten ongelma, joka syntyy kun haluamme sovit-

taa uusia tietotyyppejä tarvittavien operaatioiden kanssa yhteen, kun järjestelmässä on

jo toteutettu joukko tyyppejä ja operaatioita. Ilmaisuongelma kysyy, kuinka helppoa

on tuoda uusi operaatio tai uusi tietotyyppi ilman olemassaolevan koodin muokkaa-

mista. Ilmaisuongelman ratkaisun tulisi sallia se, että olemassaolevaa koodia ei muo-

kata lainkaan tai käännetä uudelleen. Kolmannen osapuolen toimittamissa binäärisissä

kirjastoissa ei esimerkiksi ole mahdollisuutta muokkaamiseen tai kääntämiseen.

52

Page 58: Clojure-kieli - UEF · 2016-01-14 · 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

Tietotyyppejä ja operaatioita voidaan kirjoittaa kahdella tavalla järjestelmään niin, että

ilmaisuongelma syntyy herkästi. Ensimmäinen tapa on kirjoittaa tietokeskeinen järjes-

telmä, joka muistuttaa arkkitehtuuriltaan perinteistä oliopohjaista hierarkiaa: meillä on

joukko luokkia, joilla on omat metodinsa. Uusi luokka on helppo tuoda järjestelmään:

riittää toteuttaa kaikki operaatioiden tarvitsemat rajapinnat. Sen sijaan uuden operaa-

tion tuominen aiheuttaa sen, että potentiaalisesti jokaiseen luokkaan tulee kirjoittaa

uusia rajapintatoteutuksia. Toinen tapa tuottaa ilmaisuongelma on kirjoittaa operaatio-

keskeinen järjestelmä, joka on kuin funktionaalinen arkkitehtuuri. Tässä järjestelmässä

meillä on funktioita tai operaatioita, jotka toimivat vapaan datan kanssa. Uusia operaa-

tioita on helppoa tuoda mukaan, mutta uudet tietotyypit voivat aiheuttaa funktioiden

uudelleenkirjoittamista.

Ilmaisuongelman yleisiä ratkaisuja ovat muunmuassa sovitinmalli, paikkakoodi ja ylei-

set funktiot. Sovitinmalli toimii olioparadigman maailmassa niin, että uusi tietotyyppi

kääritään olemassaolevaan, järjestelmän jo tuntemaan luokkaan, joka toteuttaa operaa-

tioiden tukemat rajapinnat. Tämän ongelmana on uuden datan olioidentiteetin rikkou-

tuminen ja koodin toistaminen. Paikkakoodi sen sijaan antaa kirjaston käyttäjän lisätä

uutta toiminnallisuutta ulkoiseen koodiin niin, että se käyttäytyy kuin osa alkuperäis-

tä koodia. Paikkakoodin käyttäminen johtaa kompleksisuuteen ja huonoon ylläpidet-

tävyyteen. Lisäksi päällimmäinen ongelma on nimiavaruuksien mahdolliset konfliktit:

nämä johtavat hienovaraisiin ongelmiin ja voivat olla hankalia todeta.

Kolmas ratkaisu ovat niinsanotut yleiset funktiot, eli funktiot, joille oliojärjestelmä

osaa valita oikean toteutuksen kutsuargumenttiensa perusteella. Tämä vastaa käytän-

nössä samaa asiaa kuin luokkien omat metodit, mutta nyt metodit kirjoitetaankin luo-

kan toteutuksen ulkopuolella, ja yleiset funktiot täten antavat kenen tahansa laajentaa

olemassaolevia operaatioita tukemaan uusia tietotyyppejä.

Ilmaisuongelman ratkaisuiksi Clojure tarjoaa sekä edelläkuvatut yleiset funktiot että

hieman yleisemmän idean, monimetodit. Monimetodit voivat lähettää suorituksen eri

toteutuksille ei pelkästään argumenttien tyyppien mukaan, vaan lisäksi mielivaltaisesti

määritellyn funktion – niinsanotun lähettäjäfunktion – paluuarvon mukaan.

Luvussa 6 tarkastelimme muutamia tapoja välttää liiallista monimutkaisuutta sovel-

lusarkkitehtuurissa. Nämä tekniikat ovat suoraan tuettuna Clojuressa. Monimutkainen

koodi tarkoittaa kietoutunutta koodia, eli koodia, joka riippuu liian paljon muusta koo-

dista ja sen toiminnasta. Monimutkaisen koodin vastakohta on yksinkertainen koodi:

53

Page 59: Clojure-kieli - UEF · 2016-01-14 · 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

komponentit eivät ole keskenään toivottoman kietoutuneita, vaan osaavat toimia ver-

raten itsenäisesti. Yksinkertainen arkkitehtuuri ei ole aina helppoa saavuttaa, mutta

Clojure yrittää tarjota hyviä työkaluja tätä päämäärää varten.

Yksi tapa hajauttaa kietoutunutta koodia yksinkertaisiksi komponenteiksi on kirjoit-

taa sovellusaluekohtaisia kieliä (DSL), jotka soveltuvat ongelman ratkaisujen kuvaile-

miseen varsinaista ohjelmointikieltä paremmin. Sovellusaluekohtaiset kielet ovat siis

käytännössä yksi tai useampi uusi abstraktiokerros. Clojure tukee lisp-kielenä mak-

rofunktioita, joita on perinteisesti käytetty hyvin apuna sovellusaluekohtaisten kielten

toteuttamisessa. Makrofunktiot eli makrot ovat funktioita, jotka kääntäjä suorittaa koo-

din lukemisen aikana käännösvaiheessa: makrot ovat siis funktioita ohjelmakoodista

ohjelmakoodiksi.

Koska makrot katoavat lopullisessa käännöksessä, eivät ne ole Clojuressa funktioi-

den kanssa yhdenvertaisia olioita: yleensä käytäntö on ollut käyttää makroja juuri sen

verran kuin on välttämätöntä. Clojure ei ahkerana kielenä esimerkiksi salli laskennan

viivästyttämistä ilman että käytettäisiin sopivaa makroa, joka käärii suoritettavan ru-

tiinin uudeksi funktioksi. Tyypillisesti makroja käytetään uusien ehtorakenteiden tai

uusien deklaratiivisten lausekkeiden muodostamiseksi. Nämä makrot puretaan kään-

nösvaiheessa olemassaoleviksi ehtolauseiksi tai funktiomääritelmiksi. Toisaalta dekla-

ratiiviset sovellusaluekohtaiset kielet on mahdollista kirjoittaa usein ilman makrojakin,

mikä on toivottava tilanne.

Muuttumaton tieto ja pysyvät tietorakenteet sallivat tietorakenteen jakavan yhteisiä

osia edellisten versioiden kanssa niin, että päivitysoperaatioiden tuloksena saadaan se-

kä pysyviä tietorakenteita että hyviä tila- ja aikavaativuusluokkia operaatioille. Töiden

pohjalla on Chris Okasakin (1999) väitöskirja. Clojure toteuttaa vektorit, kuvaukset

ja joukot 32-haaraisina puina, jotka ovat tyypillisissä käyttötilanteissa erittäin matalia

puita. Vektorit on rakenteellisen jakamisen takia toteutettu puina, mikä johtaa siihen,

että haku vektorista indeksin mukaan ei teknisesti ottaen ole vakioaikaista, mutta ma-

talasta puusta tehtävä haku on käytännössä hyvin rajattu tehtävä.

Muuttumattomuuden laajentaminen tietorakenteisiin sallii arvosemantiikan hyödyntä-

misen laajemmalti, Clojuren tapauksessa koko koodikannassa. Kun ulkopuolisien ta-

hojen, kuten säikeiden, muutoksia ei tarvitse pelätä, on datan käsittely huolettomam-

paa ja koodin pohtiminen analyyttisesti mielekkäämpää. Muuttumatonta dataa uskaltaa

myös jakaa säikeiden välillä.

54

Page 60: Clojure-kieli - UEF · 2016-01-14 · 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

Silloin kun tilaa on välttämättä muutettava, on Clojuressa sisäänrakennettu STM-

moottori, jossa on MVCC-yhteensopivat viitteet (ref) ja CAS-semanttiset atomit. Näin

muutoksen teko on keskitetty tarkkaan rajatulle rajapinnalle, johon on tarjolla säie-

turvalliset operaatiot. Tämä järjestelmä on soveltuvin osin ACI-yhteensopiva, eli ato-

minen, yhtenäinen ja eristetty. Clojure tarjoaa useita viitetyyppejä kuvaamaan jaetun

tiedon identiteettiä. Nämä viitteet osoittavat itsessään muuttumattomaan dataan, mut-

ta viitteen päivitys ja viitteen osoittaman arvon hakeminen ovat aikaan sidottu ope-

raatio. Clojuren käyttämässä MVCC-päivitysmallissa on ref-viitteiden käytössä vain

kaksi isompaa ongelmaa: elävältä lukittautumisen uhka ja kirjoitusvääristymä.

Clojure on vähäisten vuosiensa aikana ehtinyt kasvaa varteenotettavaksi järjestelmäta-

son kieleksi, jolla on helppoa kirjoittaa yksinkertaisia ratkaisuja kaikenlaisiin ongel-

miin. Nopeata kasvua selittävät niin onnistuneesti valitut oletukset ja suunnitteluläh-

tökohdat kuin JVM-integraatiokin, jonka ansiosta uuden kielen ei tarvitse lähteä aivan

puhtaalta pöydältä liikkeelle.

55

Page 61: Clojure-kieli - UEF · 2016-01-14 · 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

Viitteet

Abelson, H., Sussman, G. J. ja Sussman, J. (1996). Structure and Interpretation of

Computer Programs, 2nd Edition. MIT Press, Cambridge, MA, USA.

Atwood, J. (2007). The Principle of Least Power. url: http : / / blog .

codinghorror . com / the - principle - of - least - power/ (viitattu

20. 05. 2014).

Barendregt, H. (1997). The impact of the lambda calculus in logic and computer

science. Bulletin of Symbolic Logic 3 (2), s. 181–215.

Bloch, J. (2008). Effective Java (2nd Edition) (The Java Series). 2. painos. Prentice

Hall PTR, Upper Saddle River, NJ, USA.

Bres, Y., Serpette, B. P. ja Serrano, M. (2004). Compiling Scheme programs to .NET

Common Intermediate Language. Teoksessa: .NET Technologies 2004 Workshop

proceedings, s. 25–32.

Clojure (2014). Clojure-version 1.6.0 lopullisen lähdekoodin tilannevedos. url:

https://github.com/clojure/clojure/tree/clojure-1.6.0 (viitat-

tu 01. 07. 2014).

CoffeeScript (2014). CoffeeScript. url: http : / / coffeescript . org/ (viitattu

26. 05. 2014).

Crockford, D. (2008). JavaScript: The Good Parts. O’Reilly Media Inc., Sebastopol,

CA, USA.

ECMA-262 (2011). ECMAScript language specification. url: http : / / ecma -

international.org/publications/standards/Ecma-262.htm.

Emerick, C., Carper, B. ja Grand, C. (2012). Clojure Programming. O’Reilly Media

Inc., Sebastopol, CA, USA.

Fogus, M. ja Houser, C. (2011). The Joy Of Clojure. Manning Publications Co., Stan-

ford, CT, USA.

Freeman, E. et al. (2004). Head First Design Patterns. O’Reilly Media Inc.

Google (2014). Google Web Toolkit. url: http://www.gwtproject.org/ (viitattu

26. 05. 2014).

Gorschek, T., Tempero, E. ja Angelis, L. (2010). A Large-scale Empirical Study of

Practitioners’ Use of Object-oriented Concepts. Teoksessa: Proceedings of the

32nd ACM/IEEE International Conference on Software Engineering - Volume 1.

ICSE ’10. ACM, Cape Town, South Africa, s. 115–124. url: http://doi.acm.

org/10.1145/1806799.1806820.

56

Page 62: Clojure-kieli - UEF · 2016-01-14 · 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

Graham, P. (1994). On Lisp. Prentice Hall PTR, Upper Saddle River, NJ, USA.

Groovy (2014). Groovy. url: http : / / groovy . codehaus . org/ (viitattu

27. 06. 2014).

Halloway, S. (2009). Programming Clojure. The Pragmatic Bookshelf.

Hanselman, S. (2011). JavaScript is Assembly Language for the

Web. url: http : / / www . hanselman . com / blog /

JavaScriptIsAssemblyLanguageForTheWebPart2MadnessOrJustInsanity.

aspx (viitattu 22. 05. 2014).

Harris, T., Larus, J. ja Rajwar, R. (2010). Transactional Memory, 2nd edition. Morgan

& Claypool.

Haste (2014). The Haste Compiler. url: http : / / haste - lang . org/ (viitattu

27. 05. 2014).

Hickey, R. (2011). Simple Made Easy. Strange Loop -esitys. 20.10.2011.

— (2012). The Value of Values. GOTO Copenhagen -esitys. 22.5.2012.

— (2014). Clojure refs. url: http://clojure.org/refs (viitattu 26. 09. 2015).

Holmevik, J. R. (1994). Compiling SIMULA: A historical study of technological ge-

nesis. Annals of the History of Computing, IEEE 16 (4), s. 25–37.

Houser, C. (2010). Clojure’s Solutions to the Expression Problem. url: http : / /

www.infoq.com/presentations/Clojure-Expression-Problem (viitattu

08. 01. 2015).

Hughes, J. (1989). Why functional programming matters. Computer Journal 32 (2),

s. 98–107.

Hutton, G. (2007). Programming in Haskell. Cambridge University Press Cambridge.

JRuby (2014). JRuby. url: http://jruby.org/ (viitattu 27. 06. 2014).

Jython (2014). The Jython Project. url: http : / / www . jython . org/ (viitattu

27. 06. 2014).

Kay, A. C. (1993). The Early History of Smalltalk. Teoksessa: The Second ACM

SIGPLAN Conference on History of Programming Languages. HOPL-II. ACM,

Cambridge, Massachusetts, USA, s. 69–95. url: http://doi.acm.org/10.

1145/154766.155364.

McNamara, B. ja Smaragdakis, Y. (2000). Functional programming in C++. ACM

SIGPLAN Notices 35 (9), s. 118–129.

Microsoft (2014). TypeScript. url: http://www.typescriptlang.org/ (viitattu

26. 05. 2014).

57

Page 63: Clojure-kieli - UEF · 2016-01-14 · 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

Microsoft (2015). What is .NET Core. url: http://docs.asp.net/en/latest/

conceptual- overview/dotnetcore.html#what- is- net- core (viitattu

05. 09. 2015).

Mono Project (2015). Mono Compatibility. url: http://www.mono-project.com/

docs/about-mono/compatibility/ (viitattu 05. 09. 2015).

Monteiro, M. et al. (2005). Compiling Non-strict Functional Languages for the. NET

Platform. J. Universal Computer Science 11 (7), s. 1255–1274.

Mozilla (2014). Are We Fast Yet. url: http : / / arewefastyet . com (viitattu

01. 09. 2015).

Nierstrasz, O. (1989). A Survey of Object-Oriented Concepts. Teoksessa: Object-

Oriented Concepts, Databases and Applications. ACM Press ja Addison Wesley,

s. 3–21.

Odersky, M. et al. (2014a). Scala Language Specification, version 2.11. url: http:

//www.scala-lang.org/files/archive/spec/2.11/ (viitattu 23. 02. 2015).

Odersky, M. (2014b). What is Scala? url: http://www.scala-lang.org/what-

is-scala.html (viitattu 27. 06. 2014).

Okasaki, C. (1999). Purely Functional Data Structures. Cambridge University Press,

New York, NY, USA.

Oracle (2015). Java 8 java.util.function-paketin referenssidokumentaatio. url: https:

/ / docs . oracle . com / javase / 8 / docs / api / java / util / function /

package-summary.html (viitattu 18. 06. 2015).

Peyton Jones, S. L. ja Wadler, P. (1993). Imperative functional programming. Teokses-

sa: Proceedings of the 20th ACM SIGPLAN-SIGACT symposium on Principles of

programming languages. ACM, s. 71–84.

Sarnak, N. ja Tarjan, R. E. (1986). Planar point location using persistent search trees.

Communications of the ACM 29 (7), s. 669–679.

Schärli, N., Black, A. P. ja Ducasse, S. (2004). Object-oriented Encapsulation for

Dynamically Typed Languages. Teoksessa: Proceedings of the 19th Annual ACM

SIGPLAN Conference on Object-oriented Programming, Systems, Languages, and

Applications. OOPSLA ’04. ACM, Vancouver, BC, Canada, s. 130–149. url:

http://doi.acm.org/10.1145/1028976.1028988.

Seibel, P. (2005). Practical Common Lisp. Apress. url: http://gigamonkeys.com/

book/.

58

Page 64: Clojure-kieli - UEF · 2016-01-14 · 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

Serpette, B. P. ja Serrano, M. (2002). Compiling Scheme to JVM bytecode: a perfor-

mance study. Teoksessa: Proceedings of the 7th International Conference on Func-

tional Programming, s. 259–270.

Singer, J. (2003). JVM versus CLR: a comparative study. Teoksessa: Proceedings of

the 2nd international conference on Principles and practice of programming in

Java. Computer Science Press, Inc., s. 167–169.

Sun Microsystems (1996). JavaSoft Ships Java 1.0. url: http://tech-insider.

org/java/research/1996/0123.html (viitattu 28. 04. 2014).

Torgersen, M. (2004). The expression problem revisited. Teoksessa: ECOOP 2004–

Object-Oriented Programming. Springer, s. 123–146.

Turner, D. A. (1995). Elementary strong functional programming. Teoksessa: Functio-

nal Programming Languages in Education. Springer, s. 1–13.

Weiss, T. (2014). Compiling Lambda Expressions: Scala vs. Java 8. url: http://

www.takipiblog.com/2014/01/16/compiling-lambda-expressions-

scala-vs-java-8/ (viitattu 14. 05. 2014).

Wirfs-Brock, A. (2013). JavaScript: The Machine Language of the Ambient Computing

Era. url: http://www.slideshare.net/allenwb/fronttrends-awb (viitattu

22. 05. 2014).

Yegge, S. (2006). Execution in the Kingdom of Nouns. url: http://www.eecis.

udel . edu / ~decker / courses / 280f07 / paper / KingJava . pdf (viitattu

20. 03. 2014).

59