wiki:CommonLisp
Last modified 6 years ago Last modified on 2012-01-10 15:38:34

Kurssin esimerkkiohjelmia Common Lisp -kielellä tehtynä

Sivulle lisätään ajoittain Ohjelmointi 2 -kurssin esimerkkejä Common Lisp -ohjelmointikielellä toteutettuna.

Yleistä Common Lispistä

Common Lisp on kuvaus ja määritelmä eräästä Lisp-kieliperheen jäsenestä. Common Lispistä on tehty useita toteutuksia, kuten esimerkiksi Steel Bank Common Lisp (SBCL; avoin, saatavilla useille käyttöjärjestelmille ja laitearkkitehtuureille) ja LispWorks Common Lisp (kaupallinen; ilmainen, rajoitettu versio saatavilla Windows/Linux/Mac?).

Common Lisp on moniparadigmakieli. Vaikka sen juuret ovat vahvasti funktionaalisessa ohjelmoinnissa, voi sillä kirjoittaa ohjelmia monen muunkin tyylin mukaisesti. Olio-ohjelmia varten on Common Lispissä osakokonaisuus nimeltä Common Lisp Object System (CLOS), joka on hyvinkin monipuolinen oliokieli. Perusohjelmat voi tehdä ja nähdä hyvinkin imperatiivisena, joskin vahvasti funktionaalisuuteen taipuvat voivat kirjoittaa ohjelmansa funktionaalisesti. Logiikkaohjelmointi on kuin kotonaan Lispissä.

Common Lisp ohjelmissa ei juurikaan näe tyypinmäärittelyjä, mutta silti jokainen arvo tai olio Common Lispissä on vahvasti tyypitetty. Tyyppimäärittelyjä voi myös lisätä, jos tarvitaan ohjata Common Lisp -toteutusta ohjelmakoodin optimoimisessa.

Common Lispin määritelmä on ANSI-standardi, ja se on luettavissa esimerkiksi osoitteessa http://www.lispworks.com/documentation/HyperSpec/Front/index.htm. Dokumentti on laaja, ja vaatii totuttelua lukea sitä. Mukana on kuitenkin esimerkkikoodia.

Kielitoteutuksille on määrittelyssä sallittu joukko vapauksia, joten erikoisimpia piirteitä sisältävät Common Lisp ohjelmat eivät joissain erikoisemmissa tapauksissa toimi muokkaamatta toteutuksesta toiseen, esimerkiksi määritelmästä puuttuva säikeistyskirjasto on saanut useita toteutuksia, mutta myös yhteisiä toteutuksia. Tämän huomioiminen ohjelmassa on helppoa verrattuna moneen muuhun kieleen, joten se on vain pieni rasite, ei iso ongelma, eikä esimerkiksi haittaa Ohjelmointi 2 -kurssin tasoisissa ohjelmissa, ellet sitten halua käyttää Internet-yhteyksiä tai graafisia käyttöliittymiä ohjelmissasi. Näiden rajapinnoista eivät Common Lisp -standardin tekijät halunneet aikanaan antaaa määräyksiä.

Lisp-kielet käyttävät sulkuja eri tavalla kuin monet muut ohjelmointikielet. Lispissä koko lause on ympäröity suluilla, eli

// Java:
olio.metodi(2, "kissa");

; Lisp (CLOS)
(metodi olio 2 "kissa")

Näin kielen lukeminen, varsinkin konelukeminen, on helpompaa. Varsinainen etu on siinä, että ohjelmakoodi ja data voidaan ilmaista samalla syntaksilla, joten ohjelma voi muokata ja luoda helposti ohjelmakoodia. Tämä onkin Lispille tunnusomaisin piirre, joka hyvin harvoissa kielissä on saatu toteutettua vastaavalla tasolla. Lisää sulkeista Lispissä: LispJaSulkeet

Hello, World!

Seuraavassa hello, world -ohjelma Common Lispillä hieman moipuolisempana, kuin esim. Ohjelmointi 2 -monisteessa:

(defpackage :hello
 (:use :cl)      ; Käytä Common Lispin määritelmiä.
 (:export main)) ; Tarjoa main-funktio.

(in-package :hello)


(defun main ()
  "Tulostetaan Hello World!"
  (format t "Hello World!~%"))

(main)

Alun package-määrittely ei ole pakollinen, mutta suotava, ja vastaa monisteen esimerkin Java-paketointia. Eli defpackage luo paketin hello, jossa käytetään Common Lisp -määrityksiä ja josta tuodaan ulkopuoliseen käyttöön main-funktio. Paketin luomisen jälkeen pitää vielä siirtyä paketin sisään, eli ottaa se käyttöön. Samassa tiedostossa voidaan määritellä siten tavaraa usean eri paketin sisälle.

Seuraavaksi defun-muodolla määritellään funktio main. Ensin on dokumentaatio, kuten JavaDoc-merkkijonotkin ovat. Sitten format-lauseella tulostetaan tervehdysmerkkijono. Format on yleiskäyttöinen, eli sillä voisi tuottaa merkkijonon mihin tahansa tietovirtaan, konsolille (kuten esimerkissä) tai vain tyytyä käyttämään sen paluuarvonaan palauttamaa merkkijonoa (t tilalla nil). Tuo ~% lopussa tarkoittaa rivinvaihtoa.

Lopuksi kutsutaan main-funktiota. REPListä kutsuttaessa merkkijono näyttää tulostuvan kahteen kertaan, mutta jälkimmäinen on vain main-funktion paluuarvo, eli format-lauseen paluuarvo, eli muotoiltu merkkijono.

Lumiukkoja Emacsilla

Seuraavaksi hieman jotain muuta, eli hyppäämmen Common Lispin maailmasta Emacs-lispin maailmaan piirtämään lumiukkoja Ohjelmointi 1 -tyyliin.

Aloitamme ensin tekemällä kaiken perinteisesti käsin Emacsilla. Käytännössä voit siis avata itsellesi emacs-puskurin nimellä lumiukko.el ja aloittaa ohjelmoinnin. Ai miten puskuri avataan? No, näppäile C-x f, eli Control-nappi pohjassa paina x, sitten vapauta molemmat napit ja paina f. Nyt voit antaa tiedostonimen, josta puskuri luodaan. Jos tiedostoa ei ole, se syntyy tiedoston kirjoituksen yhteydessä (C-x s). Luodaan ensin piirtoalue. Annetaan sille nimi:

    (defvar bufname "kangas")

Ja luodaan bufferi

    (get-buffer-create bufname)

Voit ajaa nuo koodipätkät siirtämällä kursorin pätkän loppuun ja näppäilemällä C-M-x. Avaa sitten toinen kehys (frame, tuttavallisemmin ja yleisemmällä terminologialla ikkuna) ja laita sen aktiiviseksi puskuriksi (buffer) *bufname*, eli kangas. Jaa, nopeasti tuo hoituu näppäilemällä C-x 5 b kangas RET. Control-näppäimen painaminen on hankalaa samalla kädellä kuin x-näppäimen. Tähän on kaksi helpotusta: käytä toista kättä ja oikeanpuoleista Ctrl-näppäintä, tai sitten vaihda Caps Lock-näppäimen ja vasemman Ctrl-näppäimen paikkaa[fn:1] käyttö- tai ikkunointijärjestelmän asetuksista. Google auttaa. [fn:1] Ctrl-näppäin oli aikoinaan näppäimistössä nykyisen Caps Lock-näppäimen paikalla. Toisaalta joskus näppäimistössä, varsinkin Emacsin kehittäjien käyttämissä näppäimistöissä, on ollut myös näppäimet Super, Hyper ja Meta, ja joukko muitakin muokkausnäppäimiä. On myös hauska verrata englanninkielistä näppäimistöasettelua ja suomenkielistä toisiinsa ohjelmointikielten näkökulmasta. Arvaa, kumpi sopii ohjelmointiin paremmin? Miksi? Onneksi näppäimistöasettelun voi muokata mieleisekseen :)

    (save-excursion
      (set-buffer bufname)                        ; vaihdetaan kankaalle
      (artist-mode t)                             ; artisti-tila päälle
      (artist-erase-rect (artist-draw-rect 0 0 40 40) 0 0 40 40) ;puhdistetaan kangas
      (setq artist-aspect-ratio 2) ; pyritään pitämään ympyrät ympyröinä, vaihda jos tarvis
      (artist-draw-circle 20 7 24 7)
      (artist-draw-circle 20 12 26 12)
      (artist-draw-circle 20 20 30 20))

Tuo save-excursion on nimensä mukainen ja hieno funktio. Se tallettaa pisteen (point) ja merkin (mark), eli kursoreiden paikat, sekä tämänhetkisen bufferin tiedon, ja palauttaa nuo kun sille annetut funktiot on suoritettu. Aja taas koodipätkä siirtämällä kursori sen loppuun ja näppäilemällä C-M-x. Hiano lumiukko, eikö? Alla on siitä kopio.

            -----                  
           /     \                 
          (       )                
           \     /                 
           -------                 
        --/       \--              
       /             \             
       |             |             
       \             /             
        --\       /--              
          ---------                
       --/         \--             
     -/               \-           
    /                   \          
   /                     \         
   |                     |         
   \                     /         
    \                   /          
     -\               /-           
       --\         /--             
          ---------

Tuo artisti-tilan ympyränpiirtely on kuitenkin aika hankalaa, sillä komennon kaksi ensimmäistä parametria ovat keskipisteen x ja y, mutta seuraavat kaksi ovat jonkin ympyrän reunalla olevan pisteen x ja y. Mukava ominaisuus, jos hiirellä ympyröitä piirtää, mutta ohjelmoidessa hiukka hankala. Onneksi ympyrän säde on sama kaikissa pisteissä, joten piste, jolla on sama y-koordinaatti kuin ympyrän keskipisteellä riittää.