Clojure, funktionaalinen Lisp murre

Samankaltaiset tiedostot
ELM GROUP 04. Teemu Laakso Henrik Talarmo

TIE Principles of Programming Languages CEYLON

Ohjelmoinnin peruskurssien laaja oppimäärä

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

815338A Ohjelmointikielten periaatteet

Ruby. Tampere University of Technology Department of Pervasive Computing TIE Principles of Programming Languages

Dart. Ryhmä 38. Ville Tahvanainen. Juha Häkli

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

Koka. Ryhmä 11. Juuso Tapaninen, Akseli Karvinen. 1. Taustoja 2. Kielen filosofia ja paradigmat 3. Kielen syntaksia ja vertailua JavaScriptiin Lähteet

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

C++11 lambdat: [](){} Matti Rintala

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Groovy. Niko Jäntti Jesper Haapalinna Group 31

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Taulukot. Jukka Harju, Jukka Juslin

Tieto- ja tallennusrakenteet

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

Common Lisp Object System

Ohjelmoinnin peruskurssien laaja oppimäärä

TIE Principles of Programming Languages. Seminaariesityksen essee. Ryhmä 18: Heidi Vulli, Joni Heikkilä

1. Johdanto. 2. Ominaisuudet Tietotyypit ja tyypitys

Erlang. Miika Heinonen ja Lassi Uosukainen (Group 92) TIE Principles of Programming Languages Seminaariessee. Yleistä

Tie Principles of Programming Languages Seminar Essay. Lua. Group 23 Miikka Koskinen Joose Sainio

15. Ohjelmoinnin tekniikkaa 15.1

4. Luokan testaus ja käyttö olion kautta 4.1

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

Ohjelmoinnin peruskurssien laaja oppimäärä

Haskell ohjelmointikielen tyyppijärjestelmä

Ohjelmoinnin peruskurssien laaja oppimäärä

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

.NET ajoympäristö. Juha Järvensivu 2007

12. Monimuotoisuus 12.1

Ohjelmoinnin peruskurssien laaja oppimäärä

PERL. TIE Principles of Programming Languages. Ryhmä 4: Joonas Lång & Jasmin Laitamäki

Osoitin ja viittaus C++:ssa

Java kahdessa tunnissa. Jyry Suvilehto

9. Periytyminen Javassa 9.1

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

Funktionaalisten kielten oppimisesta ja valinnasta

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

Prolog kielenä Periaatteet Yhteenveto. Prolog. Toni ja Laura Fadjukoff. 9. joulukuuta 2010

Ohjelmoinnin peruskurssien laaja oppimäärä

1. Olio-ohjelmointi 1.1

7/20: Paketti kasassa ensimmäistä kertaa

Groovy. Samuli Haverinen, Aki Hänninen. 19. marraskuuta 2015

D-OHJELMOINTIKIELI. AA-kerho, 33. Antti Uusimäki. Arto Savolainen

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Arttu Kaipiainen Funktionaalinen ohjelmointi web-ohjelmistokehityksessä. Diplomityö

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssien laaja oppimäärä

11/20: Konepelti auki

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Rajapinta (interface)

Ohjelmoinnin peruskurssien laaja oppimäärä

1 Tavoitteet. 2 Periaatteet ja ominaisuudet. 2.1 Tyyppipäättely

Java-kielen perusteet

Tietueet. Tietueiden määrittely

samalla seuraavaan puoliavaruuteen (sukupolveen), jota siivotaan harvemmin.

tietueet eri tyyppisiä tietoja saman muuttujan arvoiksi

Ohjelmoinnin perusteet Y Python

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

7. Oliot ja viitteet 7.1

Ohjelmointiharjoituksia Arduino-ympäristössä

12. Monimuotoisuus 12.1

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Ohjelmoinnin peruskurssien laaja oppimäärä

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2

4. Lausekielinen ohjelmointi 4.1

Ohjelmoinnin peruskurssien laaja oppimäärä

18. Abstraktit tietotyypit 18.1

Käännös, linkitys ja lataus

811120P Diskreetit rakenteet

ITKP102 Ohjelmointi 1 (6 op)

- Komposiittityypit - Object (Mukaanlukien funktiot) - Array. - Erikoisdatatyypit - null - undefined

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Harjoitustyö: virtuaalikone

Sisällys. 18. Abstraktit tietotyypit. Johdanto. Johdanto

Sisältö. 22. Taulukot. Yleistä. Yleistä

Älysopimusten kehittäminen. Sopimus suuntautunut ohjelmointi

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

Javan perusteita. Janne Käki

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

A TIETORAKENTEET JA ALGORITMIT

Ohjelmoinnin peruskurssien laaja oppimäärä

Loppukurssin järjestelyt C:n edistyneet piirteet

Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö C-ohjelmassa

815338A Ohjelmointikielten periaatteet

7. Näytölle tulostaminen 7.1

Sisältö. 2. Taulukot. Yleistä. Yleistä

Verilogvs. VHDL. Janne Koljonen University of Vaasa

Metodien tekeminen Javalla

Transkriptio:

Clojure, funktionaalinen Lisp murre Principles of Programming Languages, S2015 Jukka Pekka Venttola & Pietari Heino Taustaa Clojuren pohjana on käytetty Lisp ohjelmointikieltä, jonka historia ulottuu 1950 luvulle asti. Lisp on monen lähestymistavan ohjelmointikieli, jossa pääasiallisena tietorakenteena toimivat linkitetyt listat. Eräs toinen Lispin erikoisemmista ominaisuuksista, varsinkin C/C++ taustaiselle ohjelmoijalle, on mahdollisuus käsitellä ohjelmakoodia datana. Lisäksi Lisp on hyvin helposti laajennettavissa ja muokattavissa, mikä johtaa siihen, että Lispiä pystyy muokkaamaan ohjelmointitarpeen mukaan sekä rakentamaan sisäkkäisiää ohjelmointikieliä. Perusajatukseltaan Lisp perustuu Alonzo Churchin lamdakalkyyliin. Suunnitteluperiaatteet Clojuren ensimmäinen versio julkaistiin vuonna 2007, pääkehittäjänään Rich Hickey. Omien sanojensa mukaan hän alkoi kehittää Clojurea, sillä hän halusi funktionaalisen Lisp murteen, joka on kiinteästi yhteydessä toteutusalustaan ja on suunniteltu vahvasti rinnakkaisuutta varten. Yleisimmistä Lisp murteista (Common Lisp ja Scheme) puuttui standardoitu tuki rinnakkaisuudelle ja tietorakenteet ovat lähtökohtaisesti muuttuvia. Funktionaalisuuden perusajatukseen kuuluu kuitenkin tietorakenteiden muuttumattomuus sekä sivuvaikutuksettomat funktiot. Funktionaalisuutta olisi ollut mahdollista noudattaa Lispissä käytäntöjen ja sopimusten kautta, mutta tietorakenteita olisi silti ollut mahdollista muuttaa. Clojuressa tietorakenteet ovat lähtökohtaisesti muuttumattomia, jolloin rinnakkaisuuden toteuttaminen muuttuu likipitäen triviaaliksi. Jos dataa ei pysty muuttamaan, ei ole mitään väliä sillä, koska ja kuka siihen pääse käsiksi. Muuttumattomuuden lisäksi tiukka yhteensovitus Javan virtuaalikoneen kanssa johtaa siihen, että Clojure ei ole suoraan taaksepäin yhteensopiva, mikä vapauttaa Clojuren useista standardoitujen Lisp murteiden asettamista rajoitteista. Puhtaat funktionaaliset kielet (esim Haskell) ovat usein vahvasti tyypitettyjä, mutta Hickey loi Clojuren tyypitykseltään dynaamiseksi. Clojurea käytetään pääsääntöisesti yhdessä Javan virtuaalikoneen (JVM) kanssa kääntämällä lähdekoodi Javan tavukoodiksi. Käännöksestä huolimatta Clojure pysyy täysin dynaamisena(tähän lisäinfoa tai tarkennusta) Mahdollista on lisäksi Java koodin kutsumisen suoraan lähdekoodista mahdollisten tyyppivinkkien avulla. JVM:n lisäksi Clojurelle löytyy JavaScript kääntäjä ClojureScript, joka tuottaa Google Closure kääntäjälle sopivaa koodia optimointia varten. Myös Microsoftin.Net Frameworkin Common Language Runtimelle löytyy toteutus nimeltä ClojureCLR.

Ominaisuudet Malli ja identiteetti Siinä missä C++:n Object Oriented Programming (OOP) mallissa on olio, jolla on jäsenmuuttujia, jotka muuttuvat, Clojuressa on malli ja identiteetti. OOP:ssa olion jäsenmuuttuja on jokin muistiosoite, jonka kuvastama data muuttuu, esimerkiksi ihmisen ikä kasvaa. Se kuitenkaan mielletään samaksi jäsenmuuttujaksi, joka vain kokee mutaation ja muuttuu ohjelman suorituksen edetessä. Clojuressa ajattelu on toisenlainen: on identiteetti (esim. luennoitsija), jolla on tila (esim. nimi), joka on arvo (esim. Matti ), joka vaihtuu (ei muutu) ajan kuluessa toiseen arvoon (esim. Bitti ). Toisin sanoen siis identiteetillä on (=> identiteetti omistaa) tila, joka voi vaihtua toiseksi tilaksi tilanteesta riippuen, mutta vaihdos ei muuta aiemmin omistetun tilan arvoa. Nämä tilat Clojuressa on samanlaisia arvoja kuin imperatiivisten kielten perusarvot. Siinä missä 42 on kaikissa ohjelmointikielissä numero, joka itsessään ei muutu, mutta joka voidaan korvata toisella numerolla, jos numero on liitetty muuttujaan, ovat Clojuren tilat samalla tavalla muuttumattomia arvoja. Nimet Bitti ja Matti ovat tiloja, jotka joko ovat tai eivät ole identiteetillä luennoitsija, mutta itsessään ne eivät koskaan muutu niiden luomisen jälkeen. Jos Clojuressa vaikuttaa siltä, että identiteetti muuttuu tavalla tai toisella, niin se on seurausta identiteetin tilojen muutoksista (vaihdoksista). Clojure siis haluaa, että voidaan puhua identiteetistä Yhdysvallat, mutta se voi tarkoittaa eri asiaa eri tilojen vuoksi ihmisille, jotka ovat eläneet eri vuosikymmenillä ja kokeneet eri Yhdysvallat. Ihmisten kokemukset eivät toki muuta Yhdysvaltoja tai eri historian tapahtumat toisiaan. Samaa logiikkaa noudattaen myös muuttujien arvot ovat muuttumattomia funktionaaliseen tapaan. Var ja muutokset muuttujissa Clojuressa on mahdollista käyttää viitettä muuttujaan, jonka tila vaihtuu toiseksi. Se tehdään Var tyyppisen muuttujan avulla, jolle alustetaan jokin alkuarvo. Alkuarvo (eli tila) ei itsessään koskaan muutu, mutta Var voidaan muuttaa osoittamaan toiseen arvoon (tilaan), joka toimii käytännössä samoin kuin jos arvo olisi muuttunut toiseksi. Jokaisella Varilla on globaali kiinnitys (binding) johonkin alkuarvoon, mutta ohjelman elinkaaren aikana jokaisella säikeellä voi olla oma kiinnitys samalle Varille. Toisin sanoen eri säikeet voivat alkaa käsitellä samaa aluksi tietyllä tavalla alustettua Varia, mutta myöhemmin tehdä muutoksia siihen kiinnittäen Varin osoituksen säikeelle sopivaan paikkaan. Vain se säie, joka on tehnyt kiinnityksen Varille (ja niin muuttanut sen muun laiseksi kuin alkuperäinen tila), näkee muokkaamansa Varin. Toiset säikeet näkevät vain omat versionsa tai alkuperäisen globaalin Varin. Viittaukset ja agentit Kun usean säikeen halutaan käyttävän samaa dataa, voidaan sitä käsitellä Refs viittauksen avulla. Refs tyypin muuttujaa käsitellään tietokantajärjestelmistä tutuin transaktioin, joissa varmistetaan, että jos jotakin muutosta yritetään, niin se joko onnistuu täydellisesti tai mitään muutosta ei tapahdu. Clojure pitää tästä huolen eikä ohjelmoijan tarvitse välittää transaktioista tai kirjoittaa niitä ohjelmakoodiin mitenkään. Refs itsessään on viite tiettyyn

paikkaan ohjelman sisällä, jossa on käsiteltävää dataa. Refs voidaan muuttaa transaktion avulla osoittamaan toiseen paikkaan, jolloin näennäisesti myös data muuttuu. Näin voidaan usealle säikeelle antaa käsiteltävä data, jonka muutokset nähdään aina uutena kirjoitus /lukualueena ja vanhan hylkäämisenä. Agentti puolestaan on viite tiettyyn paikkaan ohjelman sisällä, mutta eroaa Refsistä siten, että sen paikka ei muutu Agentin muuttuessa. Refsin muutos aiheutti transaktion ja osoituksen uuteen paikkaan, jossa uusi, muutettu data sijaitsee. Agentin muutos taas aiheuttaa alkuperäisen data alueen sisällön korvaamisen uudella datalla. Kaikki Agentin muutokset tehdään toimintojen (Actions) avulla, jotka on määritelty Agenteille. Niiden paluuarvo on Agentin uusi tila, joka kirjoitetaan vanhan päälle. Roskienkeruu Clojuressa on käytössä Javan roskienkeruu, koska Clojure ohjelma ajetaan käännetystä tavukoodista Javan virtuaalikoneella (JVM). Makrot Clojure tukee makroja, joilla voi lisätä uusia toiminnallisuuksia ohjelmaan ajonaikaisesti. Yleinen esimerkki on Clojuren varattu sana when, joka on oikeasti vain do :sta ja if: istä koostuva makro. Myös unless on oikeasti makro, joka perustuu if: iin. (macroexpand '(when boolean expression expression 1 expression 2 expression 3)) ; => (if boolean expression (do expression 1 expression 2 expression 3)) Rinnakkaisuus Clojuressa rinnakkaisuus on toteutettu säikeiden avulla. Datan jakaminen säikeiden kesken on melko vaivatonta datan muuttumattomuudesta johtuen.tämän lisäksi Clojure tarjoaa kehittäjän käyttööön työkaluja säikeiden ja niiden datan synkronoimiseksi. Multimetodit Multimetodit ovat tapa toteuttaa ajonaikaista polymorfismia funktionaalisissa ohjelmointikielissä. Clojuren tapauksessa. Se muistuttaa hieman olio ohjelmoinnissa käytettyä funktioiden ylikuormitusta, joka tapahtuu käännösaikana. Multimetodeja ovat sellaiset funktiot, joiden paluuarvon ja parametrien tyypit sidotaan vasta suoritusvaiheessa riippuen metodin kutsutavasta. Clojuren laajennukset Lispiin Clojure laajentaa hieman Lispin ajatusta ohjelmakoodista datana laajentamalla code as data systeemiä käsittämään listojen lisäksi vektorit ja mapit.

Tietotyypit Kuten mainittua, Clojure on dynaamisesti tyypitetty kieli. Vaikka suurin osa funktionaalisista kielistä on vahvasti tyypitettyjä, Clojure on dynaaminen siinä missä muutkin Lisp kielet. Dynaamisuutensa ansiosta Clojure koodia on mahdollista muokata ajon aikana luomalla esimerkiksi uusia funktioita ja makroja. Clojuresta löytyy yleisimmät tietotyypit, kuten int, float, vector, set ja map, mutta myös rationaaliluku, rational. {:foo "bar" 3 4} // map, jossa avaimina :foo ja 3 (/ 7 49) // rational, evaluoituu 7/49 = 1/7 (/ 7.0 49) // float, evaluoituu 0.14285714285714285 Suoritustehokriittisissä ohjelmakohdissa on mahdollista käyttää tyyppivihjeitä (type hints), joiden avulla kääntäjälle kerrotaan käsiteltävän muuttujan tyyppi. Käännösaikana kääntäjä käy läpi (resolve) ne kaikki kohdat, joissa on käytetty tyyppivihjeitä, eikä jätä toimenpidettä ajonaikaiseksi. Käytännössä pienellä määrällä tyyppihvijeitä ohjelmasta saadaan helposti lähes kokonaan tyypitetty, koska kääntäjän on ratkaistava myös kaikki ne ohjelmakoodin kohdat, joissa viitataan tyyppivihjeitä käyttäviin osioihin. Tyyppivihjeet annetaan [tägeinä] ohjelmakoodin seassa. Dynaaminen vs. vihjeistetty tyypitys (defn len [x] (.length x)) (defn len2 [^String x] (.length x)) // funktion määrittely // määrittely tyyppivihjeellä user=> (time (reduce + (map len (repeat 1000000 "asdf")))) "Elapsed time: 3007.198 msecs" 4000000 user=> (time (reduce + (map len2 (repeat 1000000 "asdf")))) "Elapsed time: 308.045 msecs" 4000000 Yllä olevat funktiot yhdistelevät merkkijonoja, laittavat niitä tietorakenteisiin ja lopulta laskevat merkkien määrää. Alempi versio, jossa on käytetty tyyppivihjettä, on suoritusajaltaan 10 % tyyppivihjeettömästä versiosta.

Syntaksi Clojuressa kaikki koostuu listoista. Listojen alussa kerrotaan, mikä operaatio halutaan suorittaa, minkä jälkeen listassa annetaan parametrit. Listoja voi myös olla sisäkkäin ja ne voivat olla mielivaltaisen pituisia. ( * 4 5 ) laskee 4*5 ( + 4 5 6 7 8 9 ) laskee 4+5+6+7+8+9 ( 4 ( * 2 2 ) ) laskee 4 (2*2) Myös funktiot määritellään Clojuressa listojen avulla. Neliöjuuren laskeminen sq nimisellä funktiolla: ( defn sq [ x ] ( * x x ) ) sq kertoo parametrinsa itsellään ( sq 4 ) laskee 4*4 Clojuressa on myös anonyymejä eli nimettömiä funktioita. Kun määrittelee anonyymin funktion, Clojure suorittaa sen välittömästi tulostamalla funktion sisällön. ( fn [ x ] ( * x x ) ) $eval30463$fn 30464@86a8fa1> Nimetöntä funktiota voi kutsua Clojuren listaesitystapaa käyttäen: ( ( fn [ x ] ( * x x ) ) 10 ) suorittaa: ( operaatio argumentti ) eli 10 * 10 Aiemmin käytetty defn funktion määrittelyssä on oikeasti lumetta ja konepellin alla suoritetaan def anonyymille funktiolle. Toisin sanoen: ( defn sq [ x ] ( * x x ) ) <==> ( def sq ( fn [ x ] ( * x x ) ) ) def määrittelee siis nimen sq ja yhdistää sen listaan, joka tässä tapauksessa on anonyymi funktio, joka laskee neliön annetulle parametrille. Silmukassa operointi: ( dotimes [ i 3 ] ( println i ) ) // 0 // 1 // 2

Sovelluskohteita Clojure ei ole lainkaan käyttöjärjestelmäriippuvainen, johtuen sen tiukasta integraatiosta JVM:n kanssa. Käytännössä mikä tahansa ympäristö, jossa JVM on mahdollistaa saada toimimaan, mahdollistaa Clojurella toteutetun ohjelmiston ajamisen. Lisää käyttömahdollisuuksia saadaan ClojureScriptin sekä ClojureCLR:n kautta. Vahvan rinnakkaisuustukensa takia Clojure soveltuu hyvin myös suurten datamäärien käsittelemiseen sekä analysointiin. Nykyisin Clojurea käyteyään lisäksi jonkin verran myös webbipuolella sekä back end että front end kehityksessä.

Lähteet: http://clojure.org http://www.braveclojure.com/writing macros/