Kurs Programowania w Języku Common Lisp. Lisp jest to skrót od LISt Processing. Common Lisp jest wynikiem próby ustandaryzowania różnych dialektów języka Lisp, przez instytucje ANSI.

Kurs Programowania w Jezyku Common Lisp
Wprowadzenie

UWAGA: jeśli nie miałeś jeszcze doczynienia z językiem lisp a chciałeś go poznać polecam, dialekt scheme, który jest łatwiejszy w nauce. Proponuje rozpoczęcie od znakomitych wykładów "Structure and Interpretation of Computer Programs" prowadzonych na MIT przez Hala Abelsona a Gerald Jay Sussmana w 1986 roku dla pracowników HP. Wykładom towrzyszy książka dostępna w języku polskim pt. "Struktura i Interpretacja Programów Komputerowych". Jedyna pozycja na temat języka lisp po polsku. Książka dotyczy programowania a język scheme jest tylko narzędziem. Polecam kążdemu kto che zostać lepszym programitą.


Lisp jest to skrót od LISt Processing. Został opracowany przez Johna McCarthiego w latach 50-tych. Został zainspirowany "Rachunkiem Lambda" Alonzo Churcha, od tej nazwy pochodzi charakterystyczne dla lispa wyrzenie lambda, które toworzy nową funkcję anonimową. Jest drugim najstarszym językiem, który jest nadal w użyciu.

Common Lisp jest wynikiem próby ustandaryzowania różnych dialektów języka Lisp, przez instytucje ANSI.

Każde wyrażenie w Lispie składa się z wyrażeń (tzw. S-Wyrażenia), które zapisane są za pomocą list. Listę zapisujemy w ten sposób

(+ 1 2 3 4 5)

Lisp używa notacji prefiksowej tzn że funkcje są zapisywane jako pierwszy element listy, natomiast parametry jako następne elementy listy

Wyrażenia można zagnieżdżać

(* 1 (+ 2 (- 4 5)))

Powyższe wyrażenie może być zapiane w postaci algebraicznej jako

1 * (2 + (4 - 5))

Zmienne w Lispie zapisujemy za pomocą funkcji setq.

(setq x 10)
=> 10
x
=> 10
(setq y (* x 20))
=> 200

Wartość po znakach => określa wartość zwracaną przez intepreter Lispa.

Zmienne lokalne

Zmienne lokalne zpisujemy za pomocą konstrukcji let.

(setq x 10)
(let ((x 20))
  (* x 30))
=> 600

(let ((zmienna1 wartość)
      (zmienna2 wartość))
   wyrażenie)

W powyższym kodzie zmienna x została przesłonięta przez konstrukcje let dlatego w jej wnętrzu zmienna x ma wartość 20 nie 10. Po wyjściu z konstrukcji let zmienna nie jest już widoczna.

Aby usunąć zmienną używamy funkcji (makunbound 'zmienna).

Kostrukcja let* działa tak jak let z tym że zmienne są przypisywane sekwencyjnie tzn. zmienne mogą się odwoływać do już przypisanych zmiennych.

(let* ((x 10)
       (y (+ x 10)))
    (* x y))
Stale

Aby przypisać stałą należy użyć funkcji defconstant. Przyjeło się, że stałe zapisuje się ze znakiem +.

(defconstant +pi+ 3.14159265358979)
Zmienne globalne (dynamiczne)

Zmienne gloalne przypisuje się za pomocą funkcji defvar lub defparameter. Przyjeło się zmienne globalne zapisywać ze znakami *.

(defvar *lista* '(1 2 3 4))
(defparameter *zmienna* 10)

Funkcja defvar w odróżnieniu od defparameter przypisuje zmiennej wartość tylko raz na samym początku.

Wszystkie zmienne są zmiennymi leksykalnymi, tzn. widocznymi tylko wewnątrz struktury, w której są definiowane, chyba że zapisano innaczej. Za pomocą funkcji special mamy możliwość zadeklarowania zmiennych jako dynamiczne.

(defun foo (x)
  (declare (special x))
  (bar))

(defun bar ()
  (+ x 3))

W powyższym kodzie jeśli wywołamy funkcje (foo 3) zostanie zwrócona wartość 6, chociaż w funkcji bar nie zdefiniowano zmiennej x, zmienna przyjmie wartość 3.

Konstrukcje sterujace
Instrukcje warunkowe

Instrukcja if.

(if (warunek) 
     wyrażenie-dla-prawdy
     wyrażenie-dla-fałszu)

Jęśli warunek jest spełniony wywołane zostanie pierwsze wyrażenie w przeciwnym wypadku zostanie wywołane drugie wyrażenie. Wyrżenia w instrukcji if mogą być tylko pojedyncze. Jęśli chcemy użyć kilku wyrażeń należy użyć konstrukcji progn która zwraca ostatnie wyrażenie.

Instrukcja when.

(when (warunek) wyrażenie ...)

Wyrażenie w insturkcji when jest wywoływane jeśli warunek jest spełniony. Jest równoważne poniższemu wyrażeniu if.

(if (warunek)
  (progn
    wyrażenie
    ...))

Wyrażenie unless.

(unless (warunek) wyrażenie ...)

Wyrażenie w instrukcji unless jest wywoływane jeśli warunek jest niespełniony. Jest równoważne poniższemu wyrażeniu if:

(if (not (wyrażenie))
  (progn
    wyrażenie
    ...))

Intrukcja cond.

(cond ((warunek1) wyrażenie ...)
      ((warunek2) wyrażenie ...)
      (t wyrażenie ...))

Warunki w instrukcji cond sprawdzane są sekwencyjnie. Zwracane jest to wyrażenie dla którego warunek jest spełniony. Dodatkowo można na końcu zastosować warunek t który zawsze będzie spełniony (jako alternatywa).

Instrukcja case.

(case zmienna
  (wartość1 wyrażenie ...)
  (wartość2 wyrażenie ...)
  (otherwise wyrażenie ...))

Wywoływane jest wyrażenie dla którego zmienna przyjmuje odpowiednią wartość lub wyrażenie w klauzuli otherwise.

Petle

Pętla dotimes.

(dotimes (zmienna n opcjionalna-zwracana-wartość)
  wyrażenie
  ...)

Pętla dotimes wywoła wyrażenie n razy. W każdej iteracji pętli zmienna przyjmie kolejną wartość (od zera do n-1). Opcjionalnie można w instrukcji dotimes określić wartość zwracaną przez pętle. Jeśli nie określimy tej wartość pętla zwróci wartość nil.

(dotimes (i 10)
  (print i))
0
1
2
3
4
5
6
7
8
9
=> NIL

Powyższy kod wyświetli liczby od 0 do 9 i zwróci wartość nil.

Pętla dolist.

(dolist (e '(1 2 3 4 5 6) opcjonalna-zwracana-wartość)
  wyrażenie
  ...)

Pętla dolist wywoła się tyle razy ile jest elementów listy. W każdej iteracji zmienna e przyjmie kolejną wartość z listy. Opcjionalnie można określić wartość zwracaną przez pętle. Jęsli nie określimy tej wartość to pętla zwróci wartość nil.

Pętla do.

(do ((zmienna1 wartość-początkowa step)
     (zmienna2 wartość-początkowa step))
    (warunek-zakończenia wartość-zwracan)
  wyrażenia
  ...)

Pętla do może iterować po kilku zmiennych. Dla każdej z nich określa się wartość początkową oraz wyrażenie step, które zostanie wywołane po każdej iteracji. Zakończenie pętli następuje wtedy, gdy zostanie spełnony warunek. Pętla zwróci określoną wartość.

(do ((i 0 (incf i))
     (j 10 (decf j)))
    ((zerop j) 'done)
    (print (+ i j)))

Powyższy kod iteruje po dwóch zmiennych, z których jedna jest zwiększana w każdej iteracji a druga zmniejszana. Pętla zostanie przerwana jesli zmienna j dojdzie do zera. Zwrócona zostanie wtedy wartość 'done. W każdej iteracji pętli zostanie wyświetlona suma dwóch liczników "i" i "j" (zostanie wyświetlone 10 razy liczba 10). Makra incf i decf odpowiednio zwiększają i zmnieją wartość swojego argumentu o 1.

Pętla loop w postaci prostej przyjmuje postać:

(loop
  wyrażenie
  ...)

jest to pętla nieskończona.

(let ((i 10))
  (loop
   (when (zerop i) (return))
   (print (decf i))))
9
8
7
6
5
4
3
2
1
0
=> NIL

Za pomocą makra return można przerwać pętle.

Istnieje pożliwość zastosowania rozszeżonej pętli loop. Poniżej przedstawiono jej możliwości.

Aby iterować po kolejnych liczbach od 1 do n:

(loop
  for i from 1 to n)

Aby iterować po liczbach od 0 do n-1:

(loop
  for i from 1 below n)

Aby iterować po liście wartości:

(loop
  for element in (list 1 2 3 4 5 6) do wyrażenie ...)

Wyrażenie do określa co ma zostać wywołane w każdej iteracji.

Aby iterować po tablicy, wektorze lub ciągu znaków :

(loop
  for c across string collect c)

collect c tworzy listę z kolejnych wartości c.

Aby iterować po kilku listach używamy wyrażenie and:

(loop
  for i in list1 and j in list2 collect (list i j))

Aby iterować po tablicy asocjacyjnej używamu notacji kropki:

(loop
  for (k . v) in (pairlis '(a b c) '(1 2 3)) do
  (format t "~a => ~a~%" k v))
C => 3
B => 2
A => 1

Fukcje format omówiono tutaj.

Aby iterować po parach cons używamy wyrażenia on:

(loop
  for para on (list 1 2 3 4 5 6) do
  (format t "~a => ~a~%" (car para) (cadr para)))
1 => 2
2 => 3
3 => 4
4 => 5
5 => 6
6 => NIL

Funkcja cadr zwraca drugi element listy. Funkcje format omówiono w sekcji.

Aby iterować po kluczach tablicy haszującej można użyć:

(loop 
  for k being the hash-keys of tablica-haszująca collect k)

Aby iterować po wartościach tablicy hashującej należy użyć poniższego kodu:

(loop
  for v being the hash-values of tablica-haszująca collect v)

Poniższy kod wyświetla wszytkie klucze i wartości tablicy hashującej.

(loop
  for k being the hash-keys of tablica-haszująca do
  (format t "~a => ~a~%" k (gethash k tablica-haszująca)))

Tworzenie listy jeśli warunek jest spełniony:

(loop
  for i in (list 0 1 2 3 4 5 6)
  when (evenp i) collect i)
=> (0 2 4 6)

Powyższy kod utworzy listę wszystkich liczb parzystych (funkcja evenp).

Wykonywanie operacji jeśli warunek jest spełniony.

(loop
  for i from 0 while (< i 10) collect i)
=> (0 1 2 3 4 5 6 7 8 9)

W powyższym kodzie za pomocą collect tworzymy listę od 0 do 9.

Operacje Logiczne

Wartość prawdy zapisywana jest jako t. Natomiast wartość fałszu jako pusta lista () lub wartość nil.

Instrukcja and (jest to specjalny operator).

(and wyrażenie1 wyrażenie2 wyrażenie3 ...)

Powyższy kod zwróci wartość prawdy (t) jeśli wszystkie wyrażenia zwrócą wartość prawdy. W przypadku kiedy którekolwiek wyrażenie zwróci wartość nil.

Instrukcja or (jest to specjalny operator).

(or wyrażenie1 wyrażenie2 wyrażenie3 ...)

Powyższy kod zwróci wartość prawdy jeśli którekolwiek z wyrażeń zwróci wartość prawdy. Jęśli to nastąpi następne wyrażenia nie będą przetwarzane.

Instrukcja not.

(not wyrażenie)

Instrukcja not zwróci wartość prawdy jęsli wyrażenie zwróci wartość fałszu, natomiast wartość fałszu jeśli wyrażenie zwróci wartość prawdy.

Typy danych
Symbole

Symbole definujemy za pomocą funkcji quote lub specjalnego znaku apostrofu '.

(setq x (quote foo))
x
=> FOO
(setq y 'foo)
y
=> FOO

Symbole zapisywane są dużymi literami, bez zależności od tego jak zostały zdefiniowane.

Listy

Podstawowym typem danych w Lispie są listy, listy składają się z par. Aby utworzyć pare używamy konstrukcji cons. Pierwszy element pary jest to bierzący element, listy natomiast drugi element listy jest to następna para tzn. reszta listy. Ostani element listy musi być listą pustą () lub nil.

(cons 1 2)
=> (1 . 2)

Aby utworzyć listę należy użyć konstrukcji:

(cons 1 (cons 2 (cons 3 nil)))
=> (1 2 3)
'(1 . (2 . (3 . nil)))
=> (1 2 3)

Lub użyć funkcji list lub cytowania listy - znaku apostrofu '.

(list 1 2 3 4)
=> (1 2 3 4)

'(1 2 3 4)
=> (1 2 3 4)

Aby odwołać się do elementów listy należy używamy funkcji car lub first która zwraca pierwszy element listy lub cdr lub rest zwracająca reszte listy.

(car '(1 2 3 4 5))
=> 1
(cdr '(1 2 3 4 5))
=> (2 3 4 5)

Można też użyć jednej z funkcji: second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, które zwracają odpowiednio elementy od 2 do 10 lub użyć funkcji (nth numer lista), która zwraca n-ty element listy. Trzeba pamietać że listy przetwarzane są sekwencyjnie.

Aby skopiwać listę użwamy funcji copy-list.

(copy-list lista)

Funkcja null zwraca wartość fałszu jeśli argument jest nie jest listą.

Funkcja listp zwraca prawdę jeśli argument jest listą.

Funkcja endp zwraca wartość prawdy jeśli jest to koniec listy.

Uzycie listy jak stosu

Za pomocą funkcji push oraz pop możemy odpowiedznio odkłożyć i zdjąć elementy ze stosu (listy).

(setq stos nil)
(push 10 stos)
(push 20 stos)
(push 30 stos)
(pop stos)
stos
=> (20 10)
Wektory

Wektory tworzymy za pomocą funkcji vector, znaku # lub funkcji make-sequance. Wektory są indeksowane od zera.

(vector 1 2 3 4)
=> #(1 2 3 4)
#(1 2 3 4)
=> #(1 2 3 4)
(make-sequence 'vector 10 :initial-element 0)
=> #(0 0 0 0 0 0 0 0 0 0)

Funkcja make-sequence przyjmuje argument kluczowy (keyword argument) :initial-element, którego wartością jest element jakim zostanie wypełniony wektor.

Aby odwołać się do elementów wektora używamy funkcji aref lub elt, aby przypisać jakąć wartość do elementów wektora używamy funkcji setf która działa jak setq, z tym że jako drugi argument przyjmuje miejsce gdzie zostanie zapisana wartość przekazana jako drugi argument.

(setq v #(1 2 3 4))
(setf (elt v 1) 10)
v
=> #(1 10 3 4)
Ciagi znaków

Ciągi znaków zapisujemy używając podwójnego cudzysłowu " lub funkcji make-string.

"jakiś napis"
=> "jakiś napis"
(make-string 10)
=> ""

Aby odwołać się do elementu ciągu należy użyć funkcji elt lub aref jak w przypadku wektorów.

Aby przekonwertować ciąg znaków na liczbę można użyć funkcji parse-integer.

(parse-integer "256")
=> 256

Funkcja char-code zwraca kod ASCII dla danego znaku.

(char-code #\a)
=> 97

Poszczególene znaki zapisujemy stosując znaki #\

Funkcja code-char zwraca znak dla danej wartości liczbowej.

(code-char 100)
=> #\d
Tablice

Aby utworzyć tablicę nalerzy uzyć funkcji make-array.

(setq array (make-array '(4 4) :initial-element 0))
=> #2A((0 0 0 0) (0 0 0 0) (0 0 0 0) (0 0 0 0))

Powyższy kod utworzy talicę (macierz) cztery na cztery.

Aby odwołać się do elementów tablicy (macierzy) należy użyć funkcji aref.

(setf (aref array 1 1) 1)
array
=> #2A((0 0 0 0) (0 1 0 0) (0 0 0 0) (0 0 0 0))

Tablice mogą mieć zmieną liczbe elementów, aby utworzyć taką tablicę należy użyć parametru kluczowego :adjustable z wartością t.

(setq aa (make-array 10 :adjustable t :fill-pointer 0))

Aby dodać element do takiej tablicy należy użyć funkcji (vector-push-extend wartość tablica).

Tablice asocjacyjne (alisty)

Listy asocjacyjne składają się z par klucz wartość.

(setq alist '((a . 1) (b . 2) (c . 3)))

Funkcja assoc zwraca pare klucz wartość aby odwołać się do wartości należy użyć funkcji cdr.

(cdr (assoc 'a alist))
=> 1

Dodawanie elementów do list asocjacyjnej.

(setf alist (acons klucz wartość alist))

Aby utworzyć talbicę asocjacyną można użyć funkcji pairlis.

(setq alist (pairlis '(a b c) '(1 2 3)))
=> ((a . 1) (b . 2) (c . 3))

Aby można było użyć łańcuchów znaków jako kluczy należy funkcji assoc przekazać parametr kluczowy :test z wartością #'equal. Funkcja asooc standardowo do porównań używa funkcji eq.

Listy własciwości (Plisty)

Listy właściwości są zapisywane za pomocą słów kluczowych.

(setq plist '(:a 1 :b 2 :c 3))

Aby odwołać się do elementów listy właściwości używamy funkcji getf.

(setf (getf plist :a) 10)

Aby usunąć element z listy używamy funkcji remf.

(remf plist :b)
Tablice haszujące

Są podobne do tablic asocjacyjnych tylko są bardziej efektywne. Aby utworzyć tablicę haszującą należy użyć funkcji make-hash-table.

(setq hash (make-hash-table :test #'equal))

Funkcja make-hash-table przyjmuje dodatkowy parametr kluczowy test, za pomocą którego sprawdzane będą klucze, użycie funkcji #'equal umożliwi użycie napisów jako kluczy.

Aby odwołać się do w tablicy używamy funkcji gethash.

(setf (gethash 'foo hash) "Napis")
(setf (gethash 'bar hash) "text")
(gethash 'bar hash)
=> "text"

Aby usunąć element z listy nalezy użyć funkcji remhash.

(remhash 'foo hash)

Wyczyszczenie całej tablicy następuje po wywołaniu funkcji (clrhash tablica).

Funkcja (hash-table-count tablica) zwraca liczbę wpisów w tablicy haszującej.

Sekwencje

Sekwencja jest to grupa typów danych takich jak listy, wektory, tablice, ciągi znaków.

Poniżej przedstawiono funkcje działające na sekwencjach:

make-sequance

(make-sequance typ rozmiar)

Funkcja tworzy sewencje o podanym typie (list, array, string, vector) o danym rozmiarze. Przyczym można tworzyć tylko jednowymiarowe tablice.

concatenate

(concatenate typ sekwecja sekwencja)

Funkcja łączy dwie sekwecje w jedną, typ może przyjmować jedną z wartości: list, vector, string.

elt

(elt sekwecja n)

Funkcja zwraca n-ty element sekwecji.

aref

(aref sekwecja n)

Działa tak jak elt z tym że może być używana dla tablic wielowymiarowych.

subseq

(subseq sekwencja pocątek koniec)

Funkcja zwraca sekwecję zaczynającą się od elementu początek a kończącą się na wartości koniec.

copy-seq

(copy-seq sekwencja)

Zwraca kopie sekwencji.

reverse

(reverse sekwencja)

Odwraca kolejność elementów w sekwecji.

nreverse

(nreverse sekwencja)

Tak jak reverse z tym że może modyfikować swój argument (jest destrukcyjna).

length

(length sekwencja)

Zwraca liczbę elementów w sekwencji.

count

(count 3 '(2 3 4 5 2 3))

Funcja zwraca liczbę elementów występujących w sekwencji, w tym przypadku 2.

count-if

(count-if #'oddp '(1 2 3 4))

Funkcja zlicza ile razy funkcja przekazana jako drugi parametr zwróci wartość t czyli prawdę.

count-if-not

(count-if-not #'funkcja sekwencja)

Odwrotna wersja funkcji count-if.

remove

(remove element sekwencja)

Funkcja usuwa wszystkie wystąpienia danego elementu z sekwencji.

remove-if

(remove-if #'funkcja sekwencja)

Usuwa wszystkie elementy dla których podana funkcja zwróci wartość prawdy (t).

remove-if-not

(remove-if-not #'funkcja sekwencja)

Odwrotna wersja funkcji remove-if.

substitute

(substitute zastąpiony zastępowany sekwencja)

Funkcja zastępuje każdy element zastąpiny zastępownym.

substitute-if

(substitute-if zastępowany #'funkcja sekwencja)

Zastępuje wszystkie wystąpienia elementu zastępowany dla których #'funkcja zwróci wartość prawdy.

substitute-if-not

(substitute-if-not element #'funkcja sekwencja

Odwrotan wesja fukcji substitute-if.

remove-duplicates

(remove-duplicates sekwecja)

Usuwa wszystkie powtarzające się elementy z sekwencji.

merge

(merge 'typ (1 2 3) (4 5 6) #'>)

Funkcja łączy dwie sekwecje o pdanym typie ustalając kolejność za pomocą funkcji przekazywanej jako czwarty parametr.

position

(position element sekwencja)

Funkcja zwraca pozycje elementu w sekwencji lub wartość nil.

find

(find element sekwencja)

Funkcja zwraca znaleziony element w sekwencji lub wartość nil.

search

(search sekwecja1 sekwencja2)

Funcja zwraca posycje wystąpienia sekwencji1 w sekwencji2.

sort

(sort sekwencja #'>)

Funkcja srtuje sekwencje za pomocą funkcji przekazywanej jako drugi parametr.

Struktury

Struktura jest to typ danych który zawiera pola którym można przypisać odpowiednią wartość.

Aby użyć struktury należy ją najpierw zadeklarować, robimy to za pomocą makra defstruct.

(defstruct struktura pole1 pole2 pole3)

Makro defstruct automatycznie utworzy funkcje make-struktura tworzący nowy obiekt strukturalny (rekord) oraz funkcje odwołujące się do poszczególnych pól struktura-pole1, struktura-pole2 i struktura-pole3.

(setq foo (make-struktura :pole1 1 :pole2 2 :pole3 3))
(setf (struktura-pole1 foo) "Lorem Ipsum")
(print (struktura-pole1 foo))

Powyższy kod tworzy jeden rekord o nazwie foo następnie przypisuje polu pole1 wartość "Lorem Ipsum", następnie wyświetla jego zawartość. Jeśli nie przypiszemy wartości pola przy tworzeniu rekordu, będzie on miał wartość nil.

Instnieje możliwość zastosowania wartości domyślnych dla pól aby nie trzeba było określać ich przy tworzeniu rekordu.

(defstruct struktura
  (text "domyślny")
  (liczba 0)
  foo)

Istnieje możliwość tworzenia rekordów bez użycia prametrów kluczowych za pomocą zdefiniowanego konstruktora.

(defstruct (struktura
	    (:constructor
	     create-struktura (foo bar baz)))
  foo bar baz)

(setq x (create-struktura "Lorem" "Ipsum" "Dolor"))
(print (struktura-foo x))

Isnieje możliwość dziedziczenia tzn. że pola z struktury, która jest potomkiem zostaną przekazane od rodzica.

(defstruct (struktura2
	    (:include struktura))
  quux)

(setq x (make-struktura2 :foo "Lorem" :quux "Amet"))
(print (struktura2-foo x))
=> "Lorem"
(print (struktura2-quux x))
=> "Amet"

Istnieje możliwość zdefiniowania własnej funkcji wyświetlającej rekord.

(defstruct (struktura
	    (:print-function
	     (lambda (struct stream depth)
	       (format t "#<struktura foo: ~a bar: ~a baz: ~a>"
		       (struktura-foo struct)
		       (struktura-bar struct)
		       (struktura-baz struct)))))
  foo bar baz)

(setq foo (make-struktura :foo "Lorem" :bar "Ipsum" :baz "Dolor"))
(print foo)
#<struktura foo: Lorem bar: Ipsum baz: Dolor>
Objekty - CLOS

Klasy i objekty

Aby utworzyć nowy typ obiektu czyli klasę należy użyć makra defclass.

(defclass klasa ()
  (foo bar baz))

Aby znależć objekt klasy należy użyć funkcji find-class.

(find-class 'klasa)
=> #<STANDARD-CLASS KLASA>

Aby utworzyć nowy obiekt danej klasy nalezy użyć funkcji make-instance.

(setq objekt (make-instance 'klasa))

Aby odwołać się do poszczególnych pól obiektu mależy użyć funkcji slot-value.

(setf (slot-value objekt 'foo) "Lorem")
(setf (slot-value objekt 'bar) "Ipsum")
(setf (slot-value objekt 'baz) "Dolor")
(print (slot-value objekt 'foo))
=> "Lorem"

Jęśli nastąpi odwołanie do pola, za pomocą funkcji slot-value, zanim zostanie przypisana do niego wartość Common Lisp zgłosi błąd.

Zamiast używać funkcji slot-value można użyć specjalnej funkcji dostępowej za pomocą parametru kluczowego :accessor.

(defclass klasa ()
  ((foo :accessor klasa-foo)))

(setq objekt (make-instance 'klasa))
(setf (klasa-foo objekt) "Lorem Ipsum")
(print (klasa-foo objekt))
=> "Lorem Ipsum"

Można także użyć osobnej funcji do zapisu i odczytu wartości pola za pomocą odpowiednio :writer i :reader.

(defclass klasa ()
  ((foo :reader klasa-get-foo :writer klasa-set-foo)))

(setq objekt (make-instance 'klasa))
(klasa-set-foo "Lorem Ipsum" objekt)
(print (klasa-get-foo objekt))
=> "Lorem Ipsum"

Dodatkowymi opcjami dla pól sią: :documentation określające dokumentacje pola. :type określająca typ pola (może być jeden z: real, integer, fixnum, float, bignum, ratinal lub complex) :initform określa wartość domyślną dla pola klasy.

Istnieje także możliwość określenia pól statycznych tzn. takich, dla których wartość jest przypisywana dla klasy, wartość dla każdego objektu będzie taka sama. Za pomocą parametru kluczowego :allocation z przypisaną wartością :class.

Dziedziczenie

Tak jak w przypadku struktur klaay mogą dziedziczyć pola po swoich rodzicach.

(defclass klasa ()
  ((foo :accessor klasa-foo)
   (bar :accessor klasa-bar)
   (baz :accessor klasa-baz)))

(defclass klasa2 (klasa)
  ((quux :accessor klasa2-quux :initarg :quux)))

(setq objekt (make-instance 'klasa2 :quux "Lorem Ipsum"))
(setf (klasa-foo objekt) "Dolor")
(print (klasa-foo objekt))
=> "Dolor"

W powyższym kodzie użyto opcji dla pola o nazwie :initarg, dzięki której jest możliwość przypisania polu wartości przy konstruowaniu objektu za pomocą funkcji make-instance.

Można także użyć opcji dla pola :initform określającą wartość domyślną dla danego pola.

Istnieje możliwość dziedziczenia po kilku klasach tzw. dziedziczenie wielkorotne.

Funkcje ogolne (generic functions)

Funkcje ogólne defiuje się podobnie tak jak zwykłe funkcje za pomocą makra defgeneric. Funkcje ogólne nie posiadają kodu (ciała). Funkcje ogólne są podobne do funkcji wirtualnych z języka C++.

(defgeneric metoda (parametr1 parametr2))

Funkcji ogólnej opcjionalnie można przypisać dokumentacje.

Metody

Metody definuje się za pomocą makra defmethod.

(defmethod metoda ((self klasa2) (value string))
  (setf (slot-value self 'quux) value))

(setq objekt (make-instance 'klasa2))
(metoda objekt "Dolor Sit Amet")
(print (klasa2-quux objekt))
=> "Dolor Sit Amet"

Powyższa metoda przypisuje wartość pola quux klasa klasa2.

Metody mogą być "przypisane" do kilku klas na raz, jak w powyższym przypadku do klasy klasa2 i klasy string.

Metody :before i :after są wywoływane odpowiednio przed i wywołaniu metody. Funkcja :around może być wywoływana zamiast metody, jest wywoływana przed wszystkimi metodami.

(defmethod metoda :before ((self klasa2) (value string))
  (print "wywołanie :before metoda"))
(defmethod metoda :after ((self klasa2) (value string))
  (print "wywołanie :after metoda"))
(defmethod metoda :around ((self klasa2) (value string))
  (print "wywołanie :around metoda")
  (call-next-method self value))

(metoda objekt "Foo Bar")
"wywołanie :around metoda"
"wywołanie :before metoda"
"wywołanie :after metoda"

Funkcja :around musi wywoływać funkcję call-next-method aby wywołać następną metodę.

Aby usunąc metodę należy użyć poniższego kodu.

(remove-method #'metoda (find-method #'methoda () 
				     (list (find-class 'klasa2) 
					   (find-class 'sttring))))

Pierwszym argumentem remove-method jest funkcja ogólna a drugim metoda.

Funkcje

Funkcje definiujemy za pomocą makra defun. Funkcje istnieją w odzielnej przestrzeni nazw, dzięki czemu możemy tworzyć zmienne i funkcje o takich samych nazwach.

(defun nazwa (parametr1 parametr2)
  "Dokumentacja dla funkcji."
  wyrażenie
  ...)
; wywołanie
(nazwa 1 2)

Funkcje mogą mieć parametry opcjinalne:

(defun nazwa (parametr &optional (param2 default param2-p))
  wyrażenie
  ..)

(defun foo (a &optinal (b 0))
  (format t "a:~a b:~b~%"))
(foo 1)
"a:1 b:0"
(foo 1 2)
"a:1 b:2"

Wartość default, w parametrze opcjinalnym, określa wartość jaka zostanie przypisana jesli funkcja zostanie wywołana z jednym parametrem. param2-p określa czy parametr został przypisany.

Istnieje możliwość aby funkcje przyjmowały różną liczbę argumentów, za pomocą &rest.

(defun foo (a &rest rest)
  (format t "a:~a rest:~a~%" a rest))

(foo 1)
"a:1 rest:NIL"
(foo 1 2)
"a:1 rest:(2)"
(foo 1 2 3)
"a:1 rest:(2 3)"

Aby określić parametry kluczowe dla danej funkcji używamu &key.

(defun foo (a &key b (c 10 c-p))
  (format t "a:~a b:~a c:~a c-p:~a" a b c c-p))
(foo 1)
"a:1 b:nil c:10 c-p:NIL"
(foo 1 :b 20)
"a:1 b:20 c:10 c-p:NIL"
(foo 1 :b 20 :c 30)
"a:1 b :20 c:30 c-p:T"

Zmienna c-p określa czy parametr został przypisany.

Funkcje lokalne

Funkcje lokalne tworzymy za pomocą wyrażenia flet.

(flet ((nazwa1 () wyrażenie ...)
       (nazwa2 (param) wyrażenie ...))
  wyrażenie
  ...)

Aby zastosować wywołania rekurenyjne należy użyć wyrażenia labels.

(labels ((nazwa1 () wyrażenie ...)
         (nazwa2 (param) wyrażenie ...))
  wyrażenie
  ...)


(labels ((foo () 2)
         (bar () (foo)))
  (bar))

W powyższym kodzie funkcja foo zwraca wartość 2 funkcja bar wywołuje funkcję foo. Wewnątrz funkcji labels wywoływana jest funkcja bar.

Rekurencja

Funkcja rekurencyjna to taka funkcja, w której wnętrzu następuje odwołanie do niej samej tzw. wywołanie rekurencyjne.

Poniżej przedsawiono funkcje rekurencyjną obliczającą silnie.

(defun factorial (n)
  (if (zerop n) 1
    (* n (factorial (- n 1)))))
Rekurencja ogonowa

Jeżeli w funkcji wywołanie rekurenyjne występuje na końcu wyrażenia, to taka fukcja wykorzystuje tzw. rekurencje ogonową (ang. tail recusion), takie wywołanie rekurencyjne zapisane zostanie jak iteracja za pomocą instrukcji skoku, dzięki czemu funkcja nie będzie wykorzystywać stosu (stanie się bardziej efektywna).

Poniżej przedsawiono funkcję obliczającą silnie z rekurencją ogonową.

(defun factorial (n)
  (labels ((f (n product)
	      (if (zerop n) product
		(f (- n 1) (* product n))))) ; wywołanie rekurencyjne ogonowe
	  (f n 1)))
Funkcje anonimowe

Funckje anonimowe zapisujemy za pomocą lambda abstrackji.

(lambda (x)
  (* x x))

Aby wowołać funkcje anonimową używamy funkcji funcall lub apply.

(funcall (lambda (x) (* x x)) 10)
=> 100

(setq square (lambda (x) (* x x)))
(funcall square 20)
=> 400

(apply #'+ (list 1 2 3 4))
=> 10

Funkcja apply przyjmuje jako drugi argument liste parametrów. Przekazując funkcję jako parametr do innej funkcji należy ją cytować za pomocą znaków #'.

Fukcje wyrzego rzędu

Funkcje mogą być przekazywane jako parametry, mogą też być zwracane przez funkcję.

(defun complement (fun)
  (lambda (x)
    (not (funcall fun x))))

Funkcja ta jest zdefiniowana w standardze Common Lisp.

Aby przekazać funkcję jako parametr nalerzy użyć cytowania funkcji - znaków przed nazwą funcji #'.

(mapcar #'sqrt '(1 2 3 4))
Funkcje mapujące

Funkcje mapujących używamy jeśli chcemy iterować po jakiejś liście lub sekwencji i przypisać do każdego elementu wartość zwracaną przez funkcje.

(defun square (x)
  (* x x))

(mapcar #'square '(1 2 3 4 5 6))
=> (1 4 9 16 25 36)

(mapcar (lambda (x) (* x x)) '(1 2 3 4 5))
=> (1 4 9 16 25)

Funkcje mapujące:

mapcar

(mapcar #'funkcja lista1 lista2 ...)

Funkcja przekazywana jako drugi parametr musi mieć tyle argumentów ile przekazano list.

mapcan

(mapcan #'funkcja lista2 lista2 ...)

Destrukcyjna wersja mapcar.

map

(map typ funkcja lista1 lista2 ...)

Funkcja działa tak jak mapcar z tym że paramter typ może przyjmować wartości np.: list, array, vector, string.

maplist

(maplist #'funkcja lista1 lista2 ...)

Funkcja działa tak jak mapcar z tym że do funkcji przekazywane są pary zamiast elementów list.

maphash

(maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) hash-table)

Funkcja iteruje po elementach talibcy haszującej, zwraca wartość nil.

(defun maph (fun hash)
  (let (result)
    (maphash (lambda (k v) (push (funcall fun k v) result)) hash)
    (nreverse result)))

Powyższa funkcja zwraca listę utworzoną z kolejnych wywołań funkcji fun do elementów tablicy haszującej.

Domkniecia leksykalne
(let ((x 0))
  (defun foo ()
    (setq x (incf x))
    x))

(foo)
=> 1
(foo)
=> 2
(foo)
=> 3

W powyższej funkcji zmienna x jest zamknięta wewnątrz funkcji foo (funkcja może korzystać ze zmiennej x) chociaż zmienna jest już niedostępna poza konstrukcjią let.

Makra

Makra w odróżnieniu od funkcji nie ewaluują swoich argumntów, zamiast tego do makra przekazywane jest całe s-wyrażenia które nastepnie zostaną przetworzone w celu utworzenia innego s-wyrażenia. Przy wywoływaniu makra początkowe wyrażenie przekazywane jest do makra, które po przetworzeniu zwraca nowe wyrażenie. Takie wyrażenie następnie zostaje wywołane. Makro definujemy za pomocą makra defmacro. W odróżnieniu od funkcji makr nie możemy przekazyać jako parametry do innych funkcji.

(defmacro def (args &body body)
  `(defun ,args ,@body))

Powyższy kod tworzy makro tworzące nową funkcje, używa ono znacznika &body dla określenia ciała definjowanej funkcji. Powyższe makro używa składni tylnego apostrofu (backquote) `. Tylny apostrof działa podobnie do normalnego apostrofu, który cytuje swój argument. Wszystkie wyrażenia poprzedzone przecinkiem zostaną wywołane, z wyrażeń poprzedzonych znakiem przecinka i małpy ,@ zostaną usunięte nawiasy np.: `(print 1 2 3 ,(+ 1 2 (* 3 4))) zostanie ewoluowane przez interpreter lispa jako (PRINT 1 2 3 15).

Poniżej przedstawiono makro definiujące pętle while znaną z innych języków programowania.

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
       ,@body))

; użycie powyższego makra
(let ((x 0))
  (while (< x 10)
    (print x)
    (setq x (1+ x))))

0
1
2
3
4
5
6
7
8
9
=> NIL

Poniższa makro definuje pętle for.

(defmacro for ((var start stop &optional return-value) &body body)
  (let ((gstop (gensym)))
    `(progn
      (do ((,var ,start (1+ ,var))
	    (,gstop ,stop))
	 ((> ,var ,gstop) ,return-value)
       ,@body))))

(for (i 10 20) (print i))
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
=> NIL

(reverse (let (result)
	   (for (i 1 10 result)
		(push i result))))
=> (1 2 3 4 5 6 7 8 9 10)

Powyższe makro używa funkcji gensym, która generuje unikalną nazwę dla zmiennej, stosuje się to z powodu tego, że wewnątrz pętli for użytkownik może zdefiniować taką samą zmienną jaką użyliśmy wewnąrz makra, co doprowadziłoby do błędnego działania. Znacznik &optional definiuje argument opcjionalny tak jak w przypadku funkcji.

Za pomocą funkcji macroexpand można zobaczyć jak wygląda wygenerowany przez makro kod. Jest to przydatne w czasie debugowania makr. Funkcja macroexpand generuje kod rekurencyjnie aż do napotkania tylko funkcji. Aby wyświetlić tylko jeden poziom makra należy użyć funkcji macroexpand-1.

(macroexpand '(for (i 10 20) (print i)))
(BLOCK NIL
 (LET ((I 10) (#:G8442 20))
  (TAGBODY #:G8443 (IF (> I #:G8442) (GO #:G8444)) (PRINT I)
   (PSETQ I (1+ I)) (GO #:G8443) #:G8444 (RETURN-FROM NIL (PROGN)))))
=> T
(macroexpand-1 '(for (i 10 20) (print i)))
(DO ((I 10 (1+ I)) (#:G8445 20)) ((> I #:G8445)) (PRINT I))
=> T

Nazwa zmiennej #:G8445 zystała wygenerowana przez funkcje gensym.

Formatowanie tekstu - funkcja format

Funkcja format działa podobnie do funkcji prinf języka C. Pierwszy prametr przyjmuje wartość nil, t lub strumień, jeśli zostanie przekazana wartość nil wynikowy ciąg znaków zostanie zwrócony przez funkcje format jeśli przekazana zostanie wartość t wynikowy ciąg znaków zostanie wypisany na standardowe wyjście, może być także przekazany strumień wyjściowy. Drugim parametr musi być ciąg znaków zawierający odpowiednie znaczniki (poprzdzone znakiem tyldy ~). Kolejne parametry zostaną wstawione w odpowiednie miejca w ciągu znaków.

Poniżej przedstawiono listę znaczników (wielkość liter nie ma znaczenia):

~C - wyświetla znak

~R - słowna reprezentacja liczby (w języku angielskim)

~@R - słowna reprezentacja rzymska

~A - reprezentacja jako ciąg znaków

~S - reprezentacja jako ciąg znaków który może być wczytany za pomocą funkcji read

~(znaczniki~) - wyświetla parametr małymi literami

~:@(znaczniki~) - wyświetla parametr dużymi literami

~@(znaczniki~) - pierwszy znak zostanie wyświetlony dużymi titerami

~:(znaczniki~) - każde słowo wielką literą

~% - nowa linia

~5% - 5 nowych lini

~~ - znak tyldy

~D - liczba dziesiętna

~10D - liczba dziesiętna wyrównana do 10

~X - liczba szestnastkowa

~O - liczba ósemkowa

~B - liczba binarna

~F - liczba float z przecinkiem

~,5F - liczba float z 5 miejscami po przecinku

~{znaczkiki~} - iteracja - parametr musi być listą

(format t "~{~a ~}~%" (list 1 2 3 4 5))
1 2 3 4 5 
=> NIL

~@{znaczniki~} - iteracja - przetwarza parametry jak listę

(format t "~{~a ~}~%" 1 2 3 4 5)
1 2 3 4 5 
=> NIL

~[forma1~;forma2~;..~;N~] - parametr wybiera forme

(format t "~[zero~;jeden~;dwa~]~%" 1)
"jeden"
=> NIL

~:[forma-fałsz~;forma-prawda~] - wybiera formę w zalężności czy parametr przyjmuje wartość t lub nil

(let ((x 10))
  (format nil "~:[fałsz~;prawda~]" (zerop x)))
=> "fałsz"

~* - omija jeden parametr

~:* - cofa się o jeden parametr (przetwarza jeden paramatr dwa razy)

V - wstawia wartość parametru

(format nil "~V%" 5)
=> "




"
Strumienie - osluga wejscia/wyjscia

Aby otworzyć plik należy użyć funkcji open.

(setq uchwyt (open "nazwa-pliku" :direction :output))

parametr kluczowy :direction może przyjmować jedną z dwóch wartości :input lub output dla których funkcja odpowiednio otwiera plik wejściowy (z którego można czytać) i wyjściowy (do którego można zapisywać).

Funkcji open można przekazać parametr kluczowy :if-exist któremu można przekazać wartość :supersede - zastępującą zawartość, :append - dodająca zawartość na końcu pliku.

Za pomocą funkcji read, read-line, read-char i write-byte można odpowiednio przeczytać wyrażenie lispowe ze strumienia, przeczytać linie, przeczytać pojedynczy znak oraz przeczyać bajt danych. Funkcje przyjmują jako drugi opcjionalny paramer strumień wejściowy.

Analogicznie za pomocą funkcji write, write-line, write-char i write-byte można zapisać wyrażenie do strumienia, zapisać linie, zapisać znak oraz zapisać bajt danych. Funkcja przyjmuje jako drugi opcjionalny parametr strumień wyjściowy. Można użyć także funkcji print.

(let ((file (open "plik" :direction :input)))
  (write-line (read-line file))
  (close file))

Po zakończeniu przetwarzania strumienia należy wywołać fukcję close która zamyka strumień. Strumienie mogą być buforowane, dlatego zapis na dysk może nastąpić dopiero po zamknięciu strumienia.

Funkcja listen sprawdza czy jest jakiś znak do odczytania ze strumienia.

Makro with-open-file zamyka plik automatycznie (także gdy nastąpi błąd).

(with-open-file (file "plik" :direction :input)
  (write-line (read-line file)))

Poniżej przedstawiono funkcję która zwraca listę lini z pliku.

(defun read-lines (filename)
  "Function read file and return list of lines."
  (with-open-file (file filename :direction :input)
		  (loop while (listen file)
			collect (read-line file))))

Funkcja file-length zwraca wielkość pliku.

Funkcja delete-file usuwa plik.

Funkcja rename-file zmienia nazwę pliku.

(rename-file "old-name" "new-name")

Strumienie w pamięci.

Za pomocą makra with-input-from-string można utworzyć strumień z ciągu znaków.

(with-input-from-string (stream "\"Lorem Ipsum Dolor Sit Amet\"")
			(write (read stream)))
"Lorem Ipsum Dolor Sit Amet"
=> "Lorem Ipsum Dolor Sit Amet"

Za pomocą makra with-output-to-string można zapisywać łańcuchy znaków do strumienia.

(with-output-to-string (stream)
                       (princ "Lorem Ipsum Dolor Sit Amet" stream))
=> "Lorem Ipsum Dolor Sit Amet"
Obsluga bledow

Za pomocą funkcji unwind-protect można wywołać określony kod nawet w przypadku jeśli wystąpił błąd.

(let (file)
  (unwind-protect
      (progn
	(setq file (open "file" :direction :input))
	(loop while (listen file) do
	      (write-line (read-line file))))
    (when file (close file))))

Makro with-open-file jest zdefinowane właśnie za pomocą unwind-protect.

Błąd można prosto zasygnalizować za pomocą funkcji error. Błąd w programie przerywa jego działanie. Jeśli kożystamy z interpretera Lispu w czase wystąpienia błędu nastąpi wejście do Debugera.

(error "Wystąpił błąd w programie!")

Istnieje także możliwość defiowania własnych błędów (ang. conditions) za pomocą makra define-condition. Błędy działają podobnie do klas, istnieje możliwość dziedziczenia. Wszystkie defiowane błędy powinny dziedziczyć po error. Błędy działają podobnie do wyjątków w takich językach jak C++, Java czy Python.

(define-condition Foo(error)
  ((co :initarg :co :initform "Błąd Foo" :reader co))
  (:report (lambda (condition stream)
	     (format stream "Nastąpił błąd: ~@(~A~)." (co condition))))
  (:documentation "Podstawowy Błąd Foo"))

W powyższym kodzie użyto znacznika :report na określenie funkcji za pomocą której zostanie wyświetlona informacja o błędzie. Funkcja ta przyjmuje dwa argumenty - objekt błędu oraz strumień. Znacznik :documentation określa dokumentacje do błędu. Pola w błędach definiujemy tak jak w przypadku klas.

Aby wywołać błąd należy użyć funkcji error tak jak w prostym przypadku z ciągiem znaków.

(error 'Foo :co "Jakiś błąd w programie")
*** - Nastąpił błąd: Jakiś błąd w programie.

Aby użyć dziedziczenia błędów należy zastosować poniższy kod.

(define-condition Bar(Foo)
  ((dlaczego :initarg :dlaczego :reader dlaczego))
  (:report (lambda (condition stream)
	     (format stream "Error: ~(~A~) jest błędny. Dlaczego? ~@(~A~)."
		     (co condition)
		     (dlaczego condition)))))

(error 'Bar :co "paskudny błąd" :dlaczego "nieznany")
*** - Error: paskudny błąd jest błędny. Dlaczego? nieznany.

Za pomocą makra ignore-errors można ignorować błędy np:

(ignore-errors (error "Ten błąd zostanie zignorowany."))
=> NIL
Pakietowanie

Programy napisane w Common Lispie można zapisywać w plikach. Aby wczytać plik należy użyć funkcji load.

(load "nazwa_pliku.lisp")

Aby utworzyć nowy pakiet należy użyć makra defpackage.

(defpackage :Pakiet
  (:documentation "To jest dokumentacja tego Pakietu.")
  (:use :common-lisp)
  (:export :range :factorial))

(provide "Pakiet")

(defun factorial (n)
  "Return factorial of n (n!)."
  (labels ((f (n product)
	      (if (zerop n) product
		(f (- n 1) (* product n)))))
	  (f n 1)))

(defun range (n)
  "return list of n integers."
  (let (result)
    (dotimes (i n)
      (push i result))
    (nreverse result)))

Powyższy pakiet definuje dwie funkcje factorial oraz range które są zadeklarowane w pakiecie za pomocą parametru kluczowego :export. Znacznik :use określa z których pakietów będziemy korzystać. Znacznik :documentation określa dokumentacje pakietu. Powyższy kod należy umieścić w pliku Pakiet.lisp

Aby móc używać tak zdefinowanego pakietu należy użyć funkcji require, należy także określić za pomocą funkcji in-package, że będziemy kożystać z danego pakietu. W przypadku nie wywołania funkcji in-package musielibyśmy używać pełnej nazwy każdej z funkcji np. Pakiet:factorial.

(require :Pakiet)
(in-package :Pakiet)

(factorial 10)
=> 3628800

(reduce #'* (mapcar #'1+ (range 10)))
=> 3628800

Zamiast funkcji require moglibyśmy także użyć funkcji load.