Common Lisp

ohjelmointikieli

Common Lisp on Lisp-ohjelmointikieli, jolle on ANSI-standardi X3.226-1994. Koska Common Lisp kehitettiin standardisoimaan sitä edeltävien Lisp-murteiden ominaisuudet, se on ennemminkin kielen määritelmä kuin toteutus. Standardinmukaisia Common Lisp -toteutuksia (kääntäjiä ja tulkkeja) on monia, niin yksityisiä kuin avoimen lähdekoodin toteutuksia.

Common Lisp
Paradigma proseduraalinen, funktionaalinen, olio, meta
Tyypitys dynaaminen, vahva
Muistinhallinta automaattinen
Julkaistu 1984, ANSI-standardisoitu vuonna 1994
Kehittäjä X3J13
Merkittävimmät toteutukset SBCL, ECL, Clozure CL, CMUCL
Vaikutteet Lisp, Interlisp, Scheme
Vaikuttanut Clojure, Julia, R
Käyttöjärjestelmä alustariippumaton
Verkkosivu http://common-lisp.net/
Uutisryhmä comp.lang.lisp

Common Lisp on yleiskäyttöinen ohjelmointikieli, toisin kuin jotkin Lisp-murteet, kuten Emacs Lisp ja AutoLISP, jotka ovat vain tietylle ohjelmalle kehitettyjä laajennoskieliä. Toisin kuin monet aikaisemmat Lisp-murteet, Common Lisp (kuten myös Scheme) käyttää muuttujien staattista näkyvyyttä.

Common Lisp on monta ohjelmointiparadigmaa tukeva kieli, joka:

  • Tukee imperatiivista, funktionaalista ja olio-ohjelmointia.
  • On dynaamisesti tyypitetty, mutta antaa mahdollisuuden julistaa muuttuja tietyn tyyppiseksi, mikä mahdollistaa kääntäjän suorittamat optimoinnit.
  • On laajennettava standardein keinoin, kuten Lisp-makroilla (aliohjelmilla, jotka ajetaan käännösaikana, ja jotka tuottavat Lisp-koodia) ja lukijamakroilla (syntaksilaajennoksilla, jotka antavat erityismerkityksen tietyille käyttäjälle varatuille merkeille).

Syntaksi muokkaa

Common Lisp on Lispin murre, joten Lispin tapaan se käyttää S-lausekkeita sekä koodin että datan esittämiseen. Funktioiden ja makrojen kutsut kirjoitetaan listoina siten, että funktion nimi on listan ensimmäinen alkio. Esimerkkejä:

 (+ 2 2)           ; laskee yhteen luvut 2 ja 2, tuloksena 4.

 (setf pii 3.1415)   ; asettaa muuttujan "pii" arvoksi 3.1415

 ; Funktio, joka laskee luvun neliön:
 (defun nelio (x) 
   (* x x))

 ; Suoritetaan funktio:
 (nelio 3)        ; Palauttaa luvun 9

 ; let on Common Lispin operaattori, joka sitoo muuttujia oman runkonsa suorituksen ajaksi.
 ; Jos a ja b oli jo määritelty ennen tätä lauseketta, vanhat arvot palautuvat, kun
 ; let-lauseke on suoritettu.
 (let ((a 6) (b 4)) 
   (+ a b)) ; Palauttaa luvun 10

Datatyypit muokkaa

Common Lispissä on runsaasti datatyyppejä, enemmän kuin monissa muissa ohjelmointikielissä.

Skalaarityypit muokkaa

Numeeriset tyypit sisältävät kokonaisluvut, rationaaliluvut, liukuluvut ja kompleksiluvut. Common Lisp sisältää myös bignum-tyypin esittämään mielivaltaisen suuria ja tarkkoja lukuja. ratio-tyyppi esittää rationaaliluvut tarkkoina arvoina, mikä on mahdotonta suuressa osassa kieliä. Tarvittaessa Common Lisp suorittaa automaattisen tyyppimuunnoksen numeeristen tyyppien välillä.

Common Lispin merkki (character-tyyppi) ei ole rajoittunut vain ASCII-merkkeihin – onhan Lisp on kehitetty ennen ASCII:ta. Jotkut modernit toteutukset tuntevat Unicode-merkistön.

Symboli (symbol-tyyppi) on yleinen Lisp-murteissa, mutta suurelta osin tuntematon muissa kielissä. Symboli on uniikki, nimetty data-arvo. Symbolit Lispissä vastaavat muiden kielten nimiä, kuten funktioiden ja muuttujien nimiä. Lispissä ne ovat kuitenkin yleiskäyttöisempiä ja niitä voi käyttää myös sellaisenaan. Normaalisti, kun symboli evaluoidaan, sitä vastaavan muuttujan arvo palautetaan. Avainsanasymbolit, kuten :foo kuitenkin evaluoituvat takaisin itsekseen, ja totuusarvot esitetään Common Lispissä niille varatuilla symboleilla t ja nil.

Tietorakenteet muokkaa

Common Lispin sekvenssi-tyyppeihin kuuluvat listat, vektorit, bittivektorit ja merkkijonot. Common Lispissä on monia funktioita, jotka toimivat mille tahansa sekvenssille.

Kuten muissakin Lisp-murteissa, Common Lispissä listat rakentuvat pareista (cons). Pari on tietorakenne, jossa on kaksi alkiota, ensimmäinen on car ja toinen on cdr. Lista on linkitetty ketju pareja siten, että jokaisen parin car osoittaa listan alkioon ja cdr osoittaa seuraavaan pariin – paitsi listan viimeisellä parilla, jonka cdr-alkio on nil-arvo. Pareja voi helposti käyttää myös esittämään esimerkiksi puita ja muita muita monimutkaisia tietorakenteita.

Common Lisp tukee moniulotteisia taulukoita, joiden kokoa voi tarvittaessa muuttaa dynaamisesti. Moniulotteisia taulukoita voi käyttää esimerkiksi matriisilaskennassa. Vektori on yksiulotteinen taulukko. Taulukot voivat sisältää minkä tahansa tyyppistä dataa (myös siten että taulukon eri alkiot ovat erityyppisiä), tai ne voi erikoistaa sisältämään vain yhden tyyppisiä alkioita. Monet Common Lisp -toteutukset pystyvät optimoimaan taulukko-operaatioita, kun taulukko on erikoistettu yhdelle tyypille. Kahdelle tietylle tyypille erikoistetulle taulukolle on oma nimi: Merkkijono on vektori, joka sisältää merkkejä, ja bittivektori on vektori joka sisältää bittejä.

Hajautustauluihin talletetaan data-arvojen välisiä assosiaatioita. Mitä tahansa objektia voi käyttää avaimena tai arvona. Hajautustaulujen koko muuttuu automaattisesti tarvittaessa.

Paketit ovat symbolikokoelmia. Niitä käytetään pääasiassa erottamaan ohjelman eri osat nimiavaruuksiksi. Paketti voi julkistaa (export) symboleita merkiten ne osaksi paketin julkista rajapintaa.

Tietueet esittävät mielivaltaisen monimutkaisia tietorakenteita, joissa on alkioita. Ne vastaavat C:n structeja tai Pascalin recordeja.

Luokkien instanssit ovat samanlaisia kuin tietueet, mutta liittyvät Common Lispin oliolaajennukseen, CLOS:iin.

Funktiot muokkaa

Common Lispissä funktio on datatyyppi. On esimerkiksi mahdollista kirjoittaa funktioita, jotka ottavat parametreikseen toisia funktiota tai palauttavat funktioita. Tämä mahdollistaa erittäin yleiskäyttöisten operaatioiden toteuttamisen.

Common Lispin kirjastofunktiot tukeutuvat tällaisiin korkeamman asteen funktioihin. Esimerkiksi sekvenssin järjestävälle sort-funktiolle annetaan parametrina vertailufunktio. Tämä mahdollistaa minkä tahansa tyyppisen datan järjestämisen.

 (sort (list 5 2 6 3 1 4) #'>)
 ; Järjestää listan käyttämällä > -funktiota.
 ; Palauttaa (6 5 4 3 2 1).

 (sort (list '(9 a) '(3 b) '(4 c))
       #'(lambda (x y) (< (car x) (car y))))
 ; Järjestää listan jokaisen sisäkkäisen listan ensimmäisen elementin (car) perusteella.
 ; Palauttaa ((3 b) (4 c) (9 a)).

Funktioiden evaluointimalli on hyvin yksinkertainen. Kun kohdataan kaava (F A1 A2 ... An), oletetaan että F on jokin seuraavista:

  1. Erityinen sisäänrakennettu operaattori (helposti tarkistettavissa staattisesta listasta).
  2. Aikaisemmin määritellyn makron nimi.
  3. Funktion nimi (oletus), joka voi olla joko symboli tai lista, joka alkaa symbolilla lambda.

Jos F on funktion nimi, argumentit A1, A2, ..., An evaluoidaan vasemmalta oikealle, ja funktio suoritetaan näillä argumenteilla.

Funktioiden määrittely muokkaa

Makrolla defun määritellään funktioita.

 (defun nelio (x)
   (* x x))

Funktion määrittely voi sisältää julistuksia (declaration), jotka antavat kääntäjälle vihjeitä optimointiasetuksista tai argumenttien tyypeistä. Ne voivat myös sisältää dokumentaatiomerkkijonoja (docstring), joita voidaan käyttää interaktiiviseen dokumentaatioon.

 (defun nelio (x)
    (declare (number x) (optimize (speed 3) (debug 0) (safety 1)))
    "Laskee luvun x neliön."
    (* x x))

Nimettömiä funktioita voi määritellä käyttämällä operaattoria lambda. Lisp-ohjelmointityyli käyttää usein korkeamman asteen funktioita, joille on hyödyllistä antaa nimettömiä funktioita argumentteina.

Funktioiden määrittelyyn ja käsittelyyn on myös muita operaattoreita. Funktion voi esimerkiksi kääntää (tai uudelleenkääntää) käyttäen operaattoria compile. (Jotkin Lisp-toteutukset ajavat funktioita oletuksena tulkissa ja kääntävät vasta kehotuksesta; toiset kääntävät kaikki funktiot lennossa.)

Funktioiden nimiavaruus muokkaa

Funktioiden nimiavaruus on erillinen muuttujien nimiavaruudesta. Tämä on suurin ero Common Lispin ja Schemen välillä. Joitain operaattoreita, jotka määrittelevät nimiä funktioiden nimiavaruuteen, ovat defun, flet, ja labels.

Jotta funktiolle voisi antaa argumentiksi toisen funktion nimen perusteella, täytyy käyttää erityisoperaattoria function, jonka voi lyhentää #'. Ensimmäinen sort-esimerkki viittaa funktion, jonka nimi on > funktioiden nimiavaruudessa, lausekkeella #'>.

Schemen evaluointimalli on yksinkertaisempi: On vain yksi nimiavaruus, ja kaikki listalausekkeen alkiot evaluoidaan – ei ainoastaan argumentteja. Siksi toisella murteella kirjoitettu koodi voi olla harhaanjohtavaa toista murretta osaavalle: Esimerkiksi moni Common Lisp -ohjelmoija käyttää mielellään kuvaavia muuttujien nimiä kuten list tai string, mikä aiheuttaisi ongelmia Schemessä, koska muuttujat peittäisivät paikallisesti samannimiset funktiot.

Muut tyypit muokkaa

Common Lispissä on muitakin datatyyppejä, kuten:

  • Tiedostopolut (pathname) kuvaavat tiedostoja ja hakemistoja tiedostojärjestelmässä. Common Lispin tiedostopolut ovat geneerisempiä kuin useiden käyttöjärjestelmien tiedostojennimeämissäännöt, joten Lisp-ohjelmien tiedostojenkäsittely on siirrettävissä monen eri järjestelmän välillä.
  • Luku- ja kirjoitusvirrat kuvaavat tekstuaalisen tai binäärisen datan lähteitä ja kohteita, kuten avoimia tiedostoja tai terminaaleja.
  • Common Lispissä on sisäänrakennettu näennäissatunnaislukugeneraattori. Satunnaistila-arvot (random state) esittävät uudelleenkäytettäviä näennäisesti satunnaisten lukujen lähteitä ja antavat mahdollisuuden tuottaa sama satunnaislukusekvenssi moneen kertaan.
  • Tilat (condition) ovat tyyppejä, joilla esitetään virheitä, poikkeuksia ja muita "mielenkiintoisia" tapahtumia, joihin ohjelma voi vastata.
  • Luokat ovat ensimmäisen asteen objekteja, ja yksinään jo metaluokkien olioita.

Common Lispissä on myös työkalut olio-ohjelmointiin, Common Lisp Object System eli CLOS, joka on yksi kaikkein voimakkaimmista oliomalleista.

Makrot muokkaa

Lispin makrot muistuttavat ulkoisesti funktioita. Kuitenkin siinä missä funktio esittää evaluoitavaa lauseketta, makro esittää ohjelman lähdekoodin muuntamista muodosta toiseen.

Makrot mahdollistavat uusien syntaktisten muotojen luomisen kieleen. Esimerkiksi seuraava makro tarjoaa until-silmukan, joka voi olla tuttu muista kielistä, kuten Perlistä:

 (defmacro until (test &body body)
   `(do ()
        (,test)
      ,@body))
 
 ; esimerkki
 (until (= (random 10) 0)
   (write-line "Hello"))

Kaikki makrot avataan ennen kuin niitä sisältävä koodi suoritetaan tai käännetään. Makroja voi ajatella funktioina, jotka ottavat parametreina ja palauttavat jäsennyspuita (Lispin S-lausekkeita). Nämä funktiot suoritetaan ennen koodin suorittamista tai kääntämistä, ja ne tuottavat varsinaisen lähdekoodin. Makrot kirjoitetaan tavallisella Common Lispillä, ja ne voivat käyttää mitä tahansa tarjolla olevaa funktiota. `-merkintä (lainausmerkki "väärin päin") on Common Lispin erityisesti makroja varten tarkoitettu merkintä, joka yksinkertaistaa tavallista tapausta, jossa makro korvataan tietyn malliseksi koodiksi.

Toteutukset muokkaa

Common Lisp on määritelty paperilla (kuten Ada tai C) eikä yhdessä toteutuksessa (kuten Perl tai Python). Common Lispille on monia toteutuksia, ja standardi ilmaisee alueet, joissa ne voivat oikeaoppisesti erota toisistaan.

Lisäksi toteutusten mukana on tapana tulla myös funktiokirjastoja, jotka tarjoavat toiminnallisuuksia, joita ei ole kuvattu standardissa. Avoimen lähdekoodin kirjastoja on luotu tukemaan näitä ominaisuuksia siirrettävällä tavalla, mm. Common-Lisp.net .

Suurin osa Common Lisp -toteutuksista kääntää funktiot natiiviksi konekieleksi. Toiset kääntävät tavukoodiksi, mikä vähentää nopeutta mutta parantaa binäärikoodin siirrettävyyttä. Se harhaluulo, että Lisp on puhtaasti tulkattu kieli, johtuu luultavasti siitä, että Common Lisp -ympäristö tarjoaa interaktiivisen kehotteen, johon voi kirjoittaa koodia, ja siitä, että funktiot käännetään yksi kerrallaan.

Aiheesta muualla muokkaa