Spis treści 1 Historia i opis języka
2 Co jest takiego niesamowitego w Common Lispie?
2.1 Skoro Lisp jest taki dobry, dlaczego nikt go nie używa?
3 Kompilator/interpreter oraz IDE
4 Yin i yang, czyli atom i lista - podstawowe typy danych
5 Składnia i podstawowe funkcje
5.1 cons
5.2 list
5.3 car i cdr
5.4 defun - definicja funkcji
5.5 eval
5.6 quote
5.7 print
5.8 if
5.9 cond
5.10 Funkcje matematyczne i relacyjne.
5.11 Równość
5.12 eq
5.13 eql
5.14 =
5.15 equal
5.15.1 Cons
5.15.2 Tablice
5.15.3 Pathnames (ścieżki)
5.15.4 Inne (struktury, hash tablice, instancje, ...
5.16 equalp
5.17 append
5.18 First,second,third, cadar, nth i nthcdr
6 Zmienne
6.1 defparameter - definicja zmiennej dynamicznej
6.2 setf - ustawianie wartości zmiennej
7 Argumenty opcjonalne i słownikowe
7.1 Opcjonalne
7.2 NieskoÅ„czona ilość argumentów
7.3 Argumenty słownikowe
8 Dynamiczne i statyczne typowanie oraz Garbage Collector
9 Inne typy danych
9.1 String
9.1.1 format
9.2 Tablica
9.2.1 make-array
9.2.2 array-rank
9.2.3 aref
9.3 Hash tablica
9.3.1 make-hash-table
9.3.2 gethash
9.4 Instancje obiektów
9.5 Funkcje
9.5.1 lambda
9.5.2 function
9.5.3 #'
10 Namespace (środowisko)
10.1 apply
10.2 funcall
10.1 Generatory
11 Iteracja i mapowanie
11.1 loop
11.2 count-if
11.3 map
11.4 mapcar
11.5 mapc
12 Makra
12.1 when
12.1 progn
12.1.1 kiedy
12.2 list*
12.2.1 macroexpand-1
12.3 Backquote
12.3.1 string-case
13 O czym nie powiedziałem i o czym nie jest ten kurs
13.1 O czym nie powiedziałem, a co jest ważne
13.2 Czym nie jest ten kurs
14 Linki
14.1 Successfull Common Lisp
14.2 Practical Common Lisp
14.3 On Lisp
Historia i opis języka
Common Lisp jest najpopularniejszym dialektem Lispa, języka stworzonego
w 1958 roku przez Johna McCarthy'ego.
Pierwotnie język powstał jako narzędzie opisu rekursywnych funkcji
wyrażeń symbolicznych ("Recursive Functions of Symbolic Expressions
and Their Computation by Machine, Part I") i w zamyśle nie miał w
ogóle zostać zaimplementowany na komputerze.
DokÅ‚adniejszy opis wczesnej historii Lispa i szczegóÅ‚y jego
implementacji można przeczytać (po angielsku)
na stronie Paula
Grahama.
Co jest takiego niesamowitego w Common Lispie?
Makra. Możliwość tworzenia mini-jÄ™zyków najlepszych do danego
problemu. Możliwość abstrakcji najczęściej wpisywanych rzeczy - nie
wszystko da się załatwić klasą, metodą czy funkcją.
Wydajność. Zarówno ta programisty (można znaleźć wiele ludzi relacji,
którzy przepisali program napisany do Lispa - z 100k linii robiÅ‚o sie
10k) jak i obliczeniowÄ… (generalnie Common Lisp ma najlepsze
zarzÄ…dzanie pamiÄ™ciÄ… ze wszystkich jÄ™zyków, a CMUCL czy Allegro Common
Lisp bardzo często pokonuje program w C pod względem wydajności
numerycznej).
Spójność. Kod praktycznie wszÄ™dzie wyglÄ…da tak samo, wszyscy tak samo
indentujÄ… kod (brak jakichkolwiek odpowiedników wojen oto, gdzie
stawiać '{' itd) - bardzo dobra czytelność.
Skoro Lisp jest taki dobry, dlaczego nikt go nie używa?
Używa go NASA przy obsłudze misji kosmicznych, AMD przy budowie
procesorów, YahooStore, Boeing, zostaÅ‚ użyty przy produkcji animacji do WÅ‚adcy
Pierścieni, ....
Większą listę można znaleźć
tu i
tu.
Gra MMORPG
Vendetta jest napisana z użyciem języka Common Lisp razem z Erlang.
Kompilator/interpreter oraz IDE
Najpopularniejszym IDE do Lispa jest edytor Emacs w połączeniu ze
SLIME.
Kompilatorów i interpreterów jest dość dużo - jednak na sam poczÄ…tek polecam Å›ciÄ…gnąć
gotowÄ… dystrybucjÄ™
LispBox z interpreterem CLISP dla twojego systemu.
Uwaga: chociaż można odnieść wrażenie, że Lisp jest jedynie
interpretowanym jÄ™zykiem, ze sprawdzaniem błędów jedynie w runtime, nie
jest to prawdÄ… - wiÄ™kszość błędów zostanie wykryta już podczas
kompilacji, przerywajÄ…c jÄ… - tak samo jak w C/C++, Delphi, ...
Kompilator to np. Allegro Common Lisp (płatny - zdecydowanie najlepszy
ze wszystkich wymienionych), LispWorks, SBCL, CMUCL, Embedded
Common Lisp, Corman Lisp, Gnu Common Lisp, ...
Po uruchomieniu Å›rodowiska zobaczysz liniÄ™ komend REPL (skrót od
"Read-Eval-Print-Loop" czyli "Wczytaj-Wykonaj-Wypisz-Powtórz"), w
której możesz bezpoÅ›rednio wpisywać polecenia.
W przykładach dodałem
"=>" żeby zaznaczyć wartość zwracaną - nie jest
on rzeczywiście wypisywany w SLIME.
Kilka użytecznych skrótów klawiszowych: (C - ctrl; M (meta) - alt)
- M-p (albo C-strzaÅ‚ka w górÄ™) - poprzednia komenda
- M-n (albo C-strzaÅ‚ka w dóÅ‚) - nastÄ™pna komenda w historii
- C-M-p (albo C-M-strzaÅ‚ka w górÄ™) - wraca do nawiasu otwierajÄ…cego '('
- C-M-n (albo C-M-strzaÅ‚ka w dóÅ‚) - przeskakuje do nawiasu zamykajÄ…cego ')'
- C-M-@ - podświetla całą forme (kursor musi byc na nawiasie otwierającym)
- C-M-k - wycina całą forme (do schowka)
- C-y (yank) - wkleja zawartość schowka.
(standardowe ctrl-c ctrl-v ctrl-x można uzyskąć w tzw. CUA-mode, w
emacsie 22 dostępnym domyślnie, w emacsie 21 trzeba zainstalować to
samemu)
Dobrym pomysłem jest przeczytanie
wprowadzenia do Emacsa. Yin i yang, czyli atom i lista - podstawowe typy danych
Kod i dane zbudowane sÄ… w Lispie z tej samej struktury danych: listy.
Znak '(' oznacza poczÄ…tek listy, znak ')' koniec.
Zawartością listy może być inna lista, albo atom.
Wszystko jest albo listÄ…, albo atomem.
(1 2 3)
Lista złożona z trzech liczb.
(1 (2 3) 4)
Lista zÅ‚ożona z trzech elementów - liczby 1, dwu elementowej listy oraz
liczby 4.
Podstawowym budulcem listy jest struktura zwana
cons.
Cons skÅ‚ada siÄ™ z dwóch pól: car i cdr (sÄ… to nazwy dwóch
rejestrów komputera IBM 704, na którym zostaÅ‚a zaimplementowana
pierwsza wersja Lispa).
Jako struktura zapisana w języku C można to zapisać tak:
Każda komórka listy to jeden
cons - w polu car znajduje siÄ™
dana, w polu cdr - adres nastÄ™pnej komórki cons.
NULL (NIL) w polu cdr oznacza koniec listy.
Składnia i podstawowe funkcje
Wywołanie funkcji
f z argumentem
arg jest zapisywane jako:
(f arg)
Jest to odpowiednik zapisu
f(arg)
w innych językach programowania.
Znając już zapis, możemy poznać podstawowe funkcje:
cons
(cons car cdr)
Funkcja
cons zwraca komórkÄ™ cons.
CL-USER>; (cons 10 23)
=>; (10 . 23)
Komórki cons, które nie sÄ… lista (cdr nie jest prawidÅ‚owym
wskaźnikiem) są wypisywane z '.'.
list
(list arg1 arg2 arg3 ...)
Funkcja
list przyjmuje dowolnÄ… liczbÄ™ argumentów, i zwraca
listÄ™ z nimi.
CL-USER>; (list 1 2 3 4 5)
=>; (1 2 3 4 5)
Każda lista jest zbudowana z połączonych komórek
cons, więc
zapis
(list 1 2 3)
jest równoznaczny
(cons 1 (cons 2 (cons 3 nil)))
Można to łatwo sprawdzić w REPL:
CL-USER>; (cons 1 (cons 2 (cons 3 nil)))
=>; (1 2 3)
CL-USER>; (list 1 2 3)
=>; (1 2 3)
car i cdr
(car arg)
i
(cdr arg)
Funkcja
car zwraca car komórki
cons podanej w
argumencie.
CL-USER>; (car (cons 2 3))
=>; 2
Funkcja
cdr przeciwnie - zwraca komórkÄ™ cdr.
CL-USER>; (cdr (cons 2 3))
=>; 3
PamiÄ™tajÄ…c o tym, że listy sÄ… zbudowane z połączonych komórek
cons, możemy użyc
car i
cdr żeby dostać pierwszy
element listy oraz listÄ™ bez pierwszego elementu.
CL-USER>; (car (list 1 2 3))
=>; 1
CL-USER>; (cdr (list 1 2 3))
=>; (2 3)
defun - definicja funkcji
(defun nazwa-funcji argumenty kod)
defun jest makrem służacym do definicji funkcji.
CL-USER>; (defun dwie-listy (a b)
(list (list a)
(list b)))
=>; DWIE-LISTY
Zadeklarowaliśmy właśnie funkcję
dwie-listy, zwracajÄ…cÄ…
dwu-elementowÄ… listÄ™ z listami zawierajÄ…cymi argumenty.
Możemy jÄ… wywoÅ‚ać w ten sam sposób jak funkcje wbudowane:
CL-USER>; (dwie-listy 10 20)
=>; ((10) (20))
eval
(eval forma)
Funkcja eval zwraca wynik wykonanej formy.
CL-USER>; (list 'cons 50 60)
=>; (CONS 50 60)
CL-USER>; (eval (list 'cons 50 60))
=>; (50 . 60)
quote
(quote argument)
Funkcja quote zwraca nie ewaluowany argument.
CL-USER>; (quote (list 1 2 3))
=>; (LIST 1 2 3)
(quote cos)
można zapisać w krótszej formie jako
'cos
'
jest to tzw. reader's macro (makro funkcji read)
print
(print co)
Funkcja print wypisuje swój argument na standardowe wyjÅ›cie.
if
(if test jeśli-tak jeśli-nie)
Instrukcja warunkowa. Wykonuje test - jeÅ›li zwróci prawdÄ™, wykonuje
kod jeśli-tak, w przeciwnym wypadku jeśli-nie.
W jÄ™zyku Common Lisp prawdÄ… jest wszystko oprócz symbolu
nil (który
jest jednocześnie pustą listą).
Podstawowym symbolem reprezentujÄ…cym prawdÄ™ jest symbol
t CL-USER>; (if t
"tak"
"nie")
=>; "tak"
CL-USER>; (if nil
"tak"
"nie")
=>; "nie"
CL-USER>; (if '()
"tak"
"nie")
=>; "nie"
CL-USER>; (if (list)
"tak"
"nie")
=>; "nie"
cond
(cond form1 form2 ...)
Cond jest makrem wykorzystujÄ…cym funkcjÄ™ if - jest to odpowiednik
if...else if...else w innych językach.
Car każdej formy jest ewaluowany - jeśli wynik jest prawdą, kod
będący
cdr jest wykonywany, w przeciwnym cond przechodzi do
następnej formy.
CL-USER>; (cond ((= 1 2) (print "1=2"))
((= 2 3) (print "2=3"))
(t (print "t jest prawda")))
"t jest prawda"
=>; "t jest prawda"
CL-USER>; (cond ((= 1 2) (print "1=2"))
((<; 2 3) (print "2<3"))
(t (print "t jest prawda")))
"2<3"
=> "2<3"
Dlaczego dwa razy zostało wypisane to samo? Jedna wartość jest tekstem
wypisanych przez print na standardowe wyjście - druga to wartość
zwrócona, która jest z kolei wypisywana przez REPL.
Funkcje matematyczne i relacyjne.
Wyrażenia matematyczne są w Lispie zapisywane w Notacji Polskiej (prefixowej) -
najpierw jest operator, później argumenty.
Wyrażenie
(+ 2 2)
jest równoznaczne "2+2".
CL-USER>; (+ 2 3 4)
=>; 9
CL-USER>; (* 2 3 4)
=>; 24
CL-USER>; (/ 2 3 4)
=>; 1/6
CL-USER>; (- 2 3 4)
=>; -5
CL-USER>; (< 2 3 4)
=> T
CL-USER>; (> 2 3 4)
=>; NIL
Część ludzi uważa tą notację za lepszą, część woli jednak tradycyjny
zapis.
Jeśli należysz do drugiej grupy, można ściągnąć makro readera do
zapisu infixowego z
CMU CL AI Repository.
Po jego załadowaniu można pisać:
CL-USER>; #I(2+2)
=>; 4
Powstrzymaj siÄ™ jednak z instalacjÄ… na poczÄ…tku nauki.
Równość
Równość w CL niejedno ma imiÄ™...
eq
(eq a b)
Zwraca t, jeśli a i b to dokładnie ten sam obiekt.
Na 99% jest to zaimplementowane jako porównanie adresu obiektu.
eql
(eql a b)
Eql zwraca t jeśli:
- (eq a b) zwraca t
- a i b są podtypem typu number, są tego samego typu i mają tą samą wartość.
- a i b są znakami i są tym znakiem. (wielkość liter ma znaczenie)
(= a b ...)
=
Wszystkie argumenty muszą być podtypem typu number.
Zwraca t, jeśli a = b (matematycznie)
Przykład:
CL-USER>; (eql 1.0 1)
=>; NIL
CL-USER>; (= 1.0 1)
=>; T
CL-USER>; (eql #C(1.0 0) 1.0)
NIL
CL-USER>; (= #C(1.0 0) 1.0)
T
#C(część_rzeczywista część_urojona) - zapis liczb
zespolonych.
(można także użyc funkcji complex)
equal
(equal a b)
Symbole, numery i znaki
equal zwraca t jeÅ›li argumenty sÄ… symbolami które eq, liczbami które
eql, albo znakami które eql.
Cons
Dla cons, equal jest zdefiniowany rekursywnie - t jeśli dwa car equal
i dwa cdr equal.
Oznacza to, że można używać equal do porównywania list.
Tablice
Dwie tablice sÄ… równe tylko jeÅ›li eq zwróci t, z jednym wyjÄ…tkiem -
stringi i bit-wektory sÄ… porównywane po kolei dla wszystkich elementów
używając eql. Jeśli jedna z tablic ma fill pointer (wskaźnik
wypeÅ‚nienia), limituje on maksymalnÄ… ilość porównanych elementów.
Podczas porównywania stringów, wielkość liter ma znaczenie.
Pathnames (ścieżki)
Dwie ścieżki equal wtedy i tylko wtedy gdy wszystkie elementy (host,
urządzenie, i tak dalej) są takie same. To, czy wielkość liter w
ścieżce ma znaczenie zależy od implementacji. Jeśli obie ścieżki equal
ich działanie powinno być identyczne.
Inne (struktury, hash tablice, instancje, ...
Dwa obiekty equal tylko wtedy gdy eq zwróci t.
equalp
Typ Zachowanie
number używa =
character używa char-equal - wielkość znaków nie ma znaczenia
cons zdefiniowane rekursywnie
bit vector porównuje elementy
string porównuje elementy
pathname używa equal
struktura struktury muszą miec ten sam typ i każdy musi być equalp
inna tablica porównuje elementy
hash table test i ilość elementów musi być ta sama; jeÅ›li jest, porównuje elementy
Inne obiekty używa eq
append
(append lista1 lista2 lista3 ...)
Append łączy wszystkie listy w jedną i zwraca ją.
CL-USER>; (append (list 1 2 3) (list 4 5 6) (list 7 8 9))
=>; (1 2 3 4 5 6 7 8 9)
Wszystkie listy sÄ… kopiowane, oprócz ostatniej.
Zatrzymaj siÄ™ w tym miejscu i spróbuj, jako ćwiczenie, napisać funkcjÄ™
zwracająca połączone listy bez ich pierwszego elementu.
CL-USER>; (polacz-sublisty (list 1 2 3 4) (list 9 8 7 6))
=>; (2 3 4 8 7 6)
CL-USER>; (polacz-sublisty (list "a" "b" "c") (list "d" "e" "f"))
=>; ("b" "c" "e" "f")
First,second,third, cadar, nth i nthcdr
Zamiast pisać car można napisać first. W podobny sposób:
(car(cdr obj)) = (second obj)
(car(cdr(cdr obj)) = (third obj)
Jeśli chcemy dostać n-ty car, najlepiej napisać:
(nth n lista)
Należy pamiętać, że pierwszy element ma indeks 0.
Tak samo dla cdr i nthcdr.
Zamiast pisać (car (car arg)) możemy napisać (caar arg). Literka 'd'
oznacza że w tym miejscu jest cdr, a 'a' - car.
(cadar arg) = (car (cdr (car arg)))
Lepiej jednak unikać tych funkcji i zamiast nich używac first...tenth
oraz nth/nthcdr.
Wyjątkiem jest tutaj caar (i caaar) - jasno widać, że pobieramy wartość z
zagnieżdżonej listy.
Odpowiednikiem funkcji cdr jest rest, jednak nie robi to wielkiej różnicy.
Zmienne
IstniejÄ… dwa oddzielne typy zmiennych - leksykalne i dynamiczne
(nazywane także specjalnymi).
Zmienna dynamiczna zawsze ma tylko jedną wartość - jej zmiana w
jakimkolwiek miejscu w programie zmienia jej wartość globalną.
defparameter - definicja zmiennej dynamicznej
(defparameter zmienna wartosc)
definiuje zmiennÄ…
specjalnÄ….
setf - ustawianie wartości zmiennej
(setf zmienna wartosc)
- ustawia wartość zmiennej.
CL-USER>; (defparameter a 333)
=>; A
CL-USER>; (defun funkcja1 () (setf a 90))
=>; FUNKCJA1
CL-USER>; (defun funkcja (a)
(print a)
(funkcja1)
(print a))
=>; FUNKCJA
CL-USER>; (funkcja 20)
20
90
=>; 90
CL-USER>; a
=>; 333
CL-USER>; (funkcja1)
90
CL-USER>; a
90
Zmienna dynamiczna
a zawsze istnieje ma tą samą wartość, w
każdym miejscu programu.
Funkcja z zadeklarowanym argumentem o takiej samej nazwie jak zmienna
specjalna zapisuje obecną wartość zmiennej i przywraca ją na końcu, a
następnie zmienia globalną wartość zmiennej a - w całym środowisku.
Zmienna leksykalna istnieje tylko lokalnie - ta sama nazwa zmiennej
może dać inną wartość w zależności od 'położenia'.
Zmienne lokalne tworzÄ… siÄ™ automatycznie jako argumenty funkcji, albo
przy użyciu let:
CL-USER>; (let ((b 50) (d 10))
(print b)
(print d))
50
10
=>; 10
CL-USER>; (let ((d 10))
(defun f1 ()
(setf d 90)))
=>; F1
CL-USER>; (defun f2 (d)
(print d)
(f1)
(print d))
=>; F2
CL-USER>; (f2 55)
55
55
=>; 55
Jak widać, zmiana wartości zmiennej d nie zmieniła wartości d w innym
środowisku.
Zmienne leksykalne pozwalajÄ… uniknąć wielu błędów, dlatego najlepiej
używać ich jak najczęściej.
Aby 'ostrzec' innych programistów (i samego siebie) że dana zmienna
jest dynamiczna, powinno się otaczać jej nazwę gwiazdkami:
(defparameter *a* 23)
Środowiska leksykalne istnieją tak długo, jak długo istnieje do nich
dostęp (np. przez wywołanie funkcji) - można ich używać do
przechowywania danych:
CL-USER>; (let ((zmienna 0))
(defun zwieksz ()
(setf zmienna (+ zmienna 1))))
=>; ZWIEKSZ
CL-USER>; (zwieksz)
=>; 1
CL-USER>; (zwieksz)
=>; 2
CL-USER>; (zwieksz)
=>; 3
Zamiast pisać
(setf zmienna (+ zmienna 1))
można napisać
(incf zmienna)
.
Argumenty opcjonalne i słownikowe
Opcjonalne
Argumenty opcjonalne oznacza się używając &optional.
Przykład:
CL-USER>; (defun funkcja (&optional (x 666))
(print x))
FUNKCJA
CL-USER>; (funkcja)
666
=>; 666
CL-USER>; (funkcja 20)
20
=>; 20
Nie ma ograniczenia (oprócz ograniczeÅ„ pamiÄ™ci itd) na ilość zmiennych
opcjonalnych.
(defun funkcja (&;optional (arg1 "arg1") (arg2 40) (arg3 90) arg4))
Zdefiniuje 4 argumenty z wartościa domyślną - w przypadku arg4 jest to
wartość nil.
NieskoÅ„czona ilość argumentów
Argumenty tego typu oznacza siÄ™ poprzez &rest. Wszystkie podane
argumenty zostaną zwinięte w jedną listę.
CL-USER>; (defun fun-rest (&rest a)
(print a))
FUN-REST
CL-USER>; (fun-rest 1 2 3 4 5 6)
(1 2 3 4 5 6)
=>; (1 2 3 4 5 6)
Argumenty słownikowe
Argumenty opcjonalne, podawane jako :index - można je wpisywać w
dowolnej kolejności. Oznaczane poprzez &key.
CL-USER>; (defun fun-key (&key (a "a") (b "b") c)
(print a)
(print b)
(print c))
=>; FUN-KEY
CL-USER>; (fun-key)
"a"
"b"
NIL
=>; NIL
CL-USER>; (fun-key :a "pawel" :c "gawel")
"pawel"
"b"
"gawel"
=>; "gawel"
CL-USER>; (fun-key :a "lisp" :b "rzadzi")
"lisp"
"rzadzi"
NIL
=>; NIL
Dynamiczne i statyczne typowanie oraz Garbage Collector
Dynamiczne typowanie oznacza, że wartość ma typ, ale zmienna nie -
można przypisać do niej obiekt dowolnego typu.
Dynamiczne typowanie bardzo upraszcza i skraca programowanie.
Statyczne typowanie jest jednak czasami przydatne, jak nie chcemy
jakiegoÅ› typu w danym miejscu.
Można robić to 'rÄ™cznie' - przy pomocy testów typu i ew. wyrzucenia
błędu, ale lepiej powiedzieć jakiego typu oczekujemy:
CL-USER>; (defun x (a)
(declare (number a))
(print a))
=>; X
CL-USER>; (x 23)
23
=>; 23
CL-USER>; (x "a")
The value "a" is not of type NUMBER.
[Condition of type TYPE-ERROR]
Restarts:
0: [ABORT-REQUEST] Abort handling SLIME request.
1: [ABORT] Exit debugger, returning to top level.
Backtrace:
0: (X "a")
1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (X "a") #<NULL-LEXENV>)
Moim zdaniem domyślne dynamiczne typowanie i statyczne na żądanie jest
najlepszym możliwym wyjściem.
Inne typy danych
Wszystko, co nie jest listą, jest atomem - inne typy danych także.
String
Napis. Podtyp typu vector, który jest podtypem typu array.
Zapisywany przy użyciu " ".
(format stream format-string argumenty)
Potężny odpowiednik scanf z języka C. Faktem wartym zauważenia jest
to, że wewnętrzny język format jest kompletny w sensie turinga.
Opis wszystkich możliwości
tu.
~A
- wypisuje argument "ładnie".
~%
- znak nowej linii.
strumień t oznacza standardowe wyjście
CL-USER>; (format t "~A~%" (list 1 2 3))
(1 2 3)
=>; NIL
Tablica
make-array
(make-array dimensions &;key element-type initial-element initial-contents adjustable fill-pointer displaced-to displaced-index-offset)
Tworzy tablicÄ™.
CL-USER>; (make-array 20 :initial-element "lisp")
=>; #("lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp"
"lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp" "lisp")
Tworzy jedno-wymiarową tablicę z początkową wartością każdego elementu
ustawionÄ… na string "lisp".
CL-USER>; (make-array '(5 5) :initial-element "comp.lang.lisp")
=>; #2A(("comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp"
"comp.lang.lisp")
("comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp"
"comp.lang.lisp")
("comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp"
"comp.lang.lisp")
("comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp"
"comp.lang.lisp")
("comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp" "comp.lang.lisp"
"comp.lang.lisp"))
Tworzy dwu-wymiarową tablicę wypełnioną stringiem "comp.lang.lisp".
array-rank
Ilość wymiarów tablicy można sprawdzić przy pomocy funkcji
array-rank.
Wielkość tablicy typu adjustable może być dynamicznie zwiększana:
CL-USER>; (defparameter *tablica* (make-array 5 :adjustable t))
=>; *TABLICA*
CL-USER>; (array-total-size *tablica*)
=>; 5
CL-USER>; (adjust-array *tablica* 20)
=>; #(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
CL-USER>; (array-total-size *tablica*)
=>; 20
Właściwości fill-pointer można użyć razem z właściwością adjustable do
stworzenia dynamicznego stringa:
CL-USER>; (defparameter *napis* (make-array 0 :element-type 'character :adjustable t :fill-pointer t))
=>; *NAPIS*
CL-USER>; *napis*
=>; ""
CL-USER>; (format *napis* "ala ma kota")
=>; NIL
CL-USER>; *napis*
=>; "ala ma kota"
CL-USER>; (format *napis* "~%~A~%" (list 1 2 3))
=>; NIL
CL-USER>; *napis*
=>; "ala ma kota
(1 2 3)
"
aref
(aref tablica wymiary)
Zwraca element z tablicy.
(aref tablica 23) = tablica[23]
(w innych językach programowania).
Hash tablica
make-hash-table
(make-hash-table &;key test size rehash-size
rehash-threshold)
Tworzy hash tablicÄ™. Test oznacza rodzaj testu (eq,eql,equal albo
equalp) jakim porównywane bÄ™dÄ… elementów.
Niektóre implementacje, np. LispWorks, umożliwiajÄ… definicjÄ™
caÅ‚kowicie wÅ‚asnych testów.
Większość implementacji dodaje argument słownikowy
weak albo
weakness - jeśli jest ustawiony na t, tworzy to "słabą" hash tablicę - klucze obecne w
niej nie będą liczone przez Garbage Collector jako odnośniki do
obiektu. Pozwala to uniknąć bardzo częstego (w językach z GC) wycieku
pamięci.
gethash
(gethash klucz hash-tablica)
Zwraca wartość elementu
klucz z hash-tablica
hash-tablica.
Instancje obiektów
Typem instancji jest jej klasa. Są one tworzone bezpośrednio przy użyciu
funkcji allocate-instance albo pośrednio, przy użyciu make-instance.
Opis CLOS (Common Lisp Object System) oraz MOP (Meta Object Protocol)
mógÅ‚by zająć dwie grube książki, wiÄ™c w ogóle pominÄ™ ich temat w tym
krótkim wprowadzeniu.
Funkcje
Lisp byÅ‚ pierwszym jÄ™zykiem programowania, w którym funkcje byÅ‚y
obiektami pierwszej klasy - można ja byÅ‚o zwrócić, przekazać w
argumencie itd.
Funkcja jest tworzona przy użyciu specjalnej formy - lambda:
lambda
(lambda (argumenty) kod)
Zwraca anonimowÄ… funkcjÄ™.
CL-USER>; (lambda () (print "x"))
=>; #<FUNCTION (LAMBDA ()) {AAD421D}>
function
Specjalna forma
(function funkcja)
Zwraca nazwaną funkcję w środowisku zmiennych (o tym za chwilę):
CL-USER>; (function print)
=>; #<FUNCTION PRINT>
CL-USER>; #'print
=>; #<FUNCTION PRINT>
#'
#' jest makrem readera, umożliwiajÄ…cym krótszy zapis tego samego.
Namespace (środowisko)
Common Lisp, w przeciwieństwie do Scheme, posiada kilka środowisk -
jedno dla funkcji, jedno dla zmiennych, jedno dla bloków (pominÄ™ w tym
wprowadzeniu czym to jest).
Symbol będący pierwszym elementem w obecnie ewaluowanej liście oznacza
funkcję istniejącą w środowisku funkcji - dlatego aby wywołać funkcję
istniejącą w środowisku zmiennych, należy użyć innej funkcji
'wywołującej':
apply
(apply funkcja argument1 argument2 lista-argumentow
Wywołuje funkcję ze swoimi argumentami, z tym że ostatni argument musi
być listą - jest on 'rozwijany' na normalne argumenty dla funkcji:
CL-USER>; (defun funkcja (a b c d)
(format t "a: ~A b: ~A c: ~A d: ~A~%" a b c d))
=>; FUNKCJA
CL-USER>; (apply #'funkcja 1 2 3 '(4))
a: 1 b: 2 c: 3 d: 4
=>; NIL
CL-USER>; (apply #'funkcja '(1 2 3 4))
a: 1 b: 2 c: 3 d: 4
=>; NIL
CL-USER>; (apply #'funkcja 1 2 3 4)
attempt to use VALUES-LIST on a dotted list: 4
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [ABORT-REQUEST] Abort handling SLIME request.
1: [ABORT] Exit debugger, returning to top level.
Backtrace:
0: (APPLY #<FUNCTION FUNKCJA> 1)
1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (APPLY (FUNCTION FUNKCJA) 1 2 3 4) #<NULL-LEXENV>)
funcall
(funcall funkcja argument1 argument2 argument3
Wywołuje funkcję z swoimi argumentami.
CL-USER>; (funcall #'funkcja 1 2 3 4)
a: 1 b: 2 c: 3 d: 4
=>; NIL
Możemy w ten sposób wykonać anonimowÄ… funkcjÄ™:
CL-USER>; (funcall (lambda (arg) (print arg))
"ala ma kota")
"ala ma kota"
=>; "ala ma kota"
Generatory
Używając lambdy, możemy bardzo łatwo zaimplementować prosty generator:
CL-USER>; (defun generator-inc (start end)
(lambda () (if (>; end start)
(incf start)
end)))
GENERATOR-INC
CL-USER>; (defparameter *gen* (generator-inc 2 6))
*GEN*
CL-USER>; (funcall *gen*)
3
CL-USER>; (funcall *gen*)
4
CL-USER>; (funcall *gen*)
5
CL-USER>; (funcall *gen*)
6
CL-USER>; (funcall *gen*)
6
Każde wywołanie funkcji
generator-inc tworzy nowÄ…
closure - o ile nie zadeklarujemy zmiennej start i end jako
specjalne, możemy mieć na raz kilkanaÅ›cie generatorów i wszystko
będzie działać.
Iteracja i mapowanie
Common Lisp ma naprawdÄ™ ogromnÄ… liczbÄ™ funkcji/makr do zrealizowania
pętli. Opiszę tylko kilka (moim zdaniem) najużyteczniejszych i
najczęściej używanych.
loop
Loop jest kolejnym przykładem kompletnego w sensie Turinga mini-języka
w cl (obok format).
Jak nazwa wskazuje, służy do pętli.
Jego składnia jest dość intuicyjna - przedstawię ją przez przykłady.
CL-USER>; (defparameter *lista* '(1 2 3 4 5 6 7))
=>; *LISTA*
CL-USER>; (loop for obj in *lista* collecting obj)
=>; (1 2 3 4 5 6 7)
CL-USER>; (loop for obj in *lista* summing obj)
=>; 28
CL-USER>; (loop
for i from 0 to 10
and j from 0
collect (list i j) into wynik
finally (return wynik))
=>; ((0 0) (1 1) (2 2) (3 3) (4 4) (5 5) (6 6) (7 7) (8 8) (9 9) (10
10))
CL-USER>; (loop
for i from 5 downto 0
when (oddp i) collect (1+ i))
=>; (6 4 2)
Ten przykład policzy ilość liczb nieparzytnych - counting oznacza
'zwiÄ™ksz licznik o 1 jeÅ›li test zwróci prawdÄ™'.
Funkcja oddp zwraca prawdę jeśli liczba jest nieparzysta, w przeciwnym
wypadku nil.
CL-USER>; (loop for obj in *lista* counting (oddp obj))
4
CL-USER>; (loop
for i from 0 to 5 do
(print i))
0
1
2
3
4
5
=>; NIL
count-if
Powyższy kod można również zapisać jako:
CL-USER>; (count-if #'oddp *lista*)
=>; 4
map
(map result-value-type function sequence1 sequence2 ...)
Funkcja map wywołuje funkcję podaną w drugim argumencie po kolei dla wszystkich
elementów sekwencji podanych jako nastÄ™pne argumenty.
Element ze sekwencji pierwszej to pierwszy argument, drugiej drugi,
itd.
Gdy funkcja powróci, map dodaje element zwrócony do wyniku o żądanym typie.
Gdy jakakolwiek sekwencja siÄ™ skoÅ„czy, map zwróci dotychczas zebrane
obiekty.
CL-USER>; (map 'string
(lambda (arg) (char-downcase arg))
"QWERTYUIOP")
=>; "qwertyuiop"
CL-USER>; (map 'vector #'+ '(1 2 3 4) '(98 034 92340 93))
=>; #(99 36 92343 97)
mapcar
(mapcar funkcja sequence1 sequence2 ...) = (map 'list funkcja
sequence1 sequence2 ...)
mapc
(mapc function sequence1 sequence2 ...)
Mapc zwraca sequence1 - nie zbiera wyników. Użycie tej funkcji ma
sens, gdy funkcja podana w pierwszym argumencie ma efekty poboczne -
modyfikuje listÄ™ albo jakÄ…Å› innÄ… strukturÄ™.
Makra
Aby zdefiniować makro, należy użyć makra
defmacro:
(defmacro argumenty kod)
Na pierwszy rzut oka nie widać wielkiej różnicy miÄ™dzy deklaracjÄ…
funkcji (defun).
Różnice sÄ… jednak ogromne:
- Makra sÄ… wykonywane tylko raz, podczas kompilacji.
- Argumenty do makra nie sÄ… ewaluowane.
- Makra zwracajÄ… formÄ™ - czyli po prostu normalny kod źródÅ‚owy.
Zdefiniujmy na poczÄ…tek proste makro - zaimplementujemy makro
when.
when
(when test kod)
Po prostu - kiedy. Kiedy test zwróci prawdÄ™, wykonaj kod, w przeciwnym
wypadku zwróć nil.
Takie makro już istnieje w standardzie, nazwiemy więc nasze makro
kiedy.
Najpierw pomyślmy, jak napisać to ręcznie.
Najlepiej użyć if i progn.
progn
progn wykonuje wszystkie formy zawarte w niej, i zwraca wynik
ostatniej.
CL-USER>; (progn 23 1 45)
45
CL-USER>; (prog1 23 1 45)
23
CL-USER>; (prog2 23 1 45)
1
prog1 i
prog2 zwracajÄ…, odpowiednio, pierwszÄ… i drugÄ…
wartość.
Progn jest nam potrzebne, gdyż if przyjmuje tylko jedną formę do
wykonania.
Kiedy
(<; 2 4)
(2 jest mniejsze od 4) chcemy aby został
wykonany kod
(print "2") (print "jest") (print "mniejsze") (print "od") (print
"4")
CL-USER>; (if (< 2 4)
(progn (print "2")
(print "jest")
(print "mniejsze")
(print "od")
(print "4")))
"2"
"jest"
"mniejsze"
"od"
"4"
=> "4"
Działa.
kiedy
Nasz zapis używając
kiedy ma wyglądać tak:
(kiedy (<; 2 4)
(print "2")
(print "jest")
(print "mniejsze")
(print "od")
(print "4"))
Piszemy makro:
CL-USER>; (defmacro kiedy (test &rest kod)
(list 'if test (list* 'progn
kod)))
=>; KIEDY
list*
Tworzy listÄ™ z wszystkich argumentów i dołącza do ostatniego
argumentu.
(list* a b) = (cons a b)
macroexpand-1
Możemy sprawdzić wynik wykonania samego makra, używając funkcji
macroexpand-1:
CL-USER>; (macroexpand-1 '(kiedy (< 2 4) (print "tak")))
=> (IF (< 2 4) (PROGN (PRINT "tak")))
=> T
Tak - forma wygląda tak, jak chcieliśmy.
Druga wartość zwrócona przez macroexpand-1 (funkcja może zwracać
dowolną ilość wartości, nie tylko jedną) oznacza że jakieś makro
zostało wykonane.
Funkcja
macroexpand wykonuje całkowite rozwinięcie makra,
korzystajÄ…c z funkcji macroexpand-1, która rozwija tylko jeden raz.
(jeśli korzystasz z SLIME, możesz uzyć C-c enter do wywołania
macroexpand-1, i C-c M-m do wywołania macroexpand. Kursor musi być na
otwierajÄ…cym nawiasie formy z makrem).
Sprawdźmy nasze makro na kodzie powyżej:
CL-USER>; (kiedy (< 2 4)
(print "2")
(print "jest")
(print "mniejsze")
(print "od")
(print "4"))
"2"
"jest"
"mniejsze"
"od"
"4"
=> "4"
Backquote
Tworząc makra trzeba selektywnie ewaluowac formy - używanie ' (quote)
i funkcji list szybko staje się męczące.
` - działa tak samo jak ', ale "," ma dla takiej formy specjalne
znaczenie
W środku formy ` :
"," wyewaluuj formÄ™ po przecinku
",@" wyewaluuj formę po przecinku i przyłącz ją bezpośrednio do
listy
Jako przykład, makro
kiedy można zapisać w ten sposób:
(defmacro kiedy (test &;rest kod)
`(if ,test (progn ,@kod)))
test jest ewaluowany (inaczej byłby w wynikowej formie zostałby symbol
"test" a nie wartość argumentu!), kod jest ewaluowany i przyłączany do
listy.
Na koniec: makro string-case, do przeanalizowania.
(string-case co
(a1 kod)
(a2 kod)
(a3 kod)
(otherwise kod))
Jeśli (equal
co a_n) = t), wykonuje kod. W przeciwnym
przypadku idzie dalej. Otherwise - w przeciwnym przypadku - jest
wykonywane, gdy żaden test nie powiódÅ‚ siÄ™. Gdy nie ma warunku
otherwise, a żaden test siÄ™ nie powiódÅ‚, zwraca nil.
string-case
(defmacro string-case (co &;rest forms)
(let* ((otherwise nil)
(wynik (mapcan (lambda (form)
(cond ((eq (car form) 'otherwise)
(setf otherwise (cdr form))
'())
(t
`(((equal ,co ,(car form))
,@(cdr form))))))
forms)))
`(cond ,@(append wynik (when otherwise `((t
,@otherwise)))))))
let* tym siÄ™ różni od let, że binduje symbole po kolei, podczas
gdy let wszystkie na raz.
O czym nie powiedziałem i o czym nie jest ten kurs
O czym nie powiedziałem, a co jest ważne
Nie powiedziaÅ‚em o CLOS, systemie wyjÄ…tków, MOP, strukturach, listach asocjacyjnych, strumieniach,
modułach, asdf i asdf-install, pathnames i series.
JeÅ›li zaczÄ…Å‚eÅ›/masz zamiar uczyć siÄ™ CL, sÄ… to tematy którymi
powinienieś się (ale dopiero po pewnym czasie) zainteresować.
Czym nie jest ten kurs
Przeprowadzeniem kogoś kto nigdy nie widział języka na oczy do kodera
CL. Do tego są książki - 20x dłuższe (adresy poniżej).
Ten kurs ma na celu zapoznanie jedynie z podstawami języka i nie
powinien być używany jako podstawa nauki.
Linki
Successfull Common Lisp
Successfull Common Lisp - Bardzo dobra i rozległa książka Practical Common Lisp
Practical Common Lisp - podejście praktyczne. Moim zdaniem dobra książka razem z poprzednią
On Lisp
On Lisp - książka o zaawansowanych technikach programowania funkcyjnego w Common Lispie
ŹródÅ‚o: 4programmers.net. Treść udostÄ™pniona na zasadach licencji Creative Commons Attribution