lisp-tut(5) Wskazówki dla Common LISP

OPIS

Uwaga: to wprowadzenie do Common Lisp zostało napisane dla środowiska CMU, więc niektóre fragmenty pracy z lispem mogą się trochę różnić w różnych miejscach.

Inne źródła informacji

Najlepsza książka o LISP, którą znam, to

Guy L. Steele Jr. _Common LISP: the Language_. Digital Press. 1984.

Pierwsza edycja jest łatwiejsza do czytania; druga opisuje trochę nowszy standard. (Różnice między standardami nie powinny sprawiać różnic zwykłym programistom).

Polecano mi również książkę Dave'a Touretsky'ego, choć nie czytałem jej i nie mogę o niej nic powiedzieć.

Symbole

Symbol to łańcuch znaków. Istnieją ograniczenia co do tego, co można wstawić do symbolu i co może być pierwszym znakiem, lecz tak długo jak używasz prostych liter, cyfr i myślników, nic się nie stanie. (Poza tym, że jeśli używasz tylko cyfr i prawdopodobnie początkowego myślnika, to LISP może pomyśleć, że to liczba całkowita, a nie symbol.) Przykłady symboli:


        a
        b
        c1
        foo
        bar
        baaz-quux-garply

Dalej przedstawiamy kilka rzeczy, które można zrobić z symbolami. (Rzeczy po znaku zachęty ">" są tym, co należy wstukać do interpretera LISP, podczas gdy inne rzeczy są odpowiedziami, drukowanymi przez LISP. Znak ";" jest LISP-owym znakiem komentarza: wszystko od ";" do końca linii jest ignorowane.


> (setq a 5) ;zachowaj liczbę jako wartość symbolu 5 > a ;pobierz wartość symbolu 5 > (let ((a 6)) a) ;przywiąż tymczasowo wartość symbolu do 6 6 > a ;po zakończeniu let, wartość wraca do 5 5 > (+ a 6) ;użyj wartości symbolu jako argumentu funkcji 11 > b ;spróbuj pobrać wartość symbolu, który nie ma wartości Error: Attempt to take the value of the unbound symbol B

Istnieją dwa symbole specjalne, `t' i `nil'. Wartość `t' jest zdefiniowana zawsze jako `t', a wartość `nil' jest zdefiniowana zawsze jako `nil'. LISP używa `t' i `nil' do reprezentowania prawdy i fałszu. Przykładowe użycie, opisane dokładniej dalej:


> (if t 5 6) 5 > (if nil 5 6) 6 > (if 4 5 6) 5

Ostatni przykład jest cudaczny, lecz prawidłowy: `nil' oznacza fałsz, a wszystko inne oznacza prawdę. (O ile nie mamy potrzeby robić inaczej, używamy t do oznaczania prawdy, tak dla czystej prostoty.)

Symbole takie jak `t' i `nil' są nazywane samorozwijającymi, ponieważ rozwijają się same na siebie. Istnieje cała klasa samorozwijających się symboli, zwanych słowami kluczowymi; dowolny symbol, którego nazwa rozpoczyna się od dwukropka jest słowem kluczowym. (Zobacz niżej przykłady zastosowań słów kluczowych.) Przykłady:


> :this-is-a-keyword :THIS-IS-A-KEYWORD > :so-is-this :SO-IS-THIS > :me-too :ME-TOO

Liczby

Liczba całkowita jest łańcuchem cyfr, opcjonalnie poprzedzonych + lub -. Liczba rzeczywista wygląda jak liczba całkowita, lecz ma kropkę dziesiętną i może być zapisana w notacji naukowej. Liczba wymierna wygląda jak dwie liczby całkowite z kreską ułamkową / między nimi. LISP obsługuje też liczbyzespolone, które są zapisywane jako #c(r i) (gdzie r jest częścią rzeczywistą, a i jest częścią urojoną). Liczbą jest dowolny zapis z powyższych. Przykłady:


        5
        17
        -34
        +6
        3.1415
        1.722e-15
        #c(1.722e-15 0.75)

Standardowe funkcje arytmetyczne są wciąż dostępne: +, -, *, /, floor, ceiling, mod, sin, cos, tan, sqrt, exp, expt, itd. Wszystkie one przyjmują dowolny rodzaj argumentu. +, -, * i / zwracają liczbę zgodnie z następującymi zasadami: liczba całkowita plus wymierna daje wymierną, wymierna plus rzeczywistą daje rzeczywistą, a rzeczywista plus zespolona daje zespoloną. Oto przykłady:


> (+ 3 3/4) ;determinacja typu wynikowego 15/4 > (exp 1) ;e 2.7182817 > (exp 3) ;e*e*e 20.085537 > (expt 3 4.2) ;potęga o podstawie innej niż e 100.90418 > (+ 5 6 7 (* 8 9 10)) ;funkcje +-*/ przyjmują argumenty wielokrotne

Nie ma ograniczeń w absolutnej wartości liczby całkowitej, poza rozmiarem pamięci komputera. Ostrzegam jednak, że obliczenia na dużych liczbach mogą być wolne. (Podobnie jak obliczenia na liczbach wymiernych, szczególnie w porównaniu do obliczeń na małych liczbach całkowitych lub rzeczywistych.)

Wagoniki (ang. conses)

Wagonik jest zwykłym dwuelementowym rekordem. Z przyczyn historycznych jego pola są nazywane "car" i "cdr". (Na pierwszej maszynie, na której zaimplementowano LISP istniały dwie instrukcje, CAR i CDR, które oznaczały "contents of address register" i "contents of decrement register". Wagoniki były zaimplementowane przy użyciu tych rejestrów.)

Wagoniki są łatwe w użyciu:


> (cons 4 5)            ;Zaalokuj wagonik. Ustaw car na 4 a cdr na 5.
(4 . 5)
> (cons (cons 4 5) 6)
((4 . 5) . 6)
> (car (cons 4 5))
4
> (cdr (cons 4 5))
5

Listy

Z wagoników można budować wiele struktur. Pewnie najprostszą jest lista powiązana: car każdego wagonika wskazuje na jeden z elementów listy, a cdr albo na następny element, albo na nil. Możesz stworzyć taką powiązaną listę przy użyciu funkcji list:


> (list 4 5 6)
(4 5 6)

Zauważ, że LISP drukuje powiązane listy w szczególny sposób: pomija niektóre kropki i nawiasy. Zasada jest taka: jeśli cdr wagonika to nil, LISP nie drukuje kropki, ani nil; jeśli cdr wagonika A to wagonik B, to nie drukuje kropki dla wagonika A, ani nawiasów dla wagonika B. Więc:


> (cons 4 nil) (4) > (cons 4 (cons 5 6)) (4 5 . 6) > (cons 4 (cons 5 (cons 6 nil))) (4 5 6)

Ostatni przykład jest równoważny dokładnie wywołaniu (list 4 5 6). Zauważ, że nil oznacza teraz listę bez elementów: cdr listy 2 elementowej (a b) to (b), lista o jednym elemencie; cdr 1 elementowego (b) to nil, który jest listą bez elementów.

Pusty (nil) car i cdr jest definiowany jako `nil'.

Jeśli zapisujesz swoją listę w zmiennej, możesz spowodować, że będzie działać jak stos:


> (setq a nil) NIL > (push 4 a) (4) > (push 5 a) (5 4) > (pop a) 5 > a (4) > (pop a) 4 > (pop a) NIL > a NIL

Funkcje

Powyżej był przykład funkcji. Oto kilka innych:


> (+ 3 4 5 6)                  ;ta funkcja pobiera dowolną liczbę argumentów
18
> (+ (+ 3 4) (+ (+ 4 5) 6))    ;czy notacja przedrostkowa nie jest super?
22
> (defun foo (x y) (+ x y 5))  ;definiowanie funkcji
FOO
> (foo 5 0)                    ;wołanie funkcji
10
> (defun fact (x)              ;funkcja rekursywna
    (if (> x 0) 
      (* x (fact (- x 1)))
      1
  ) )
FACT
> (fact 5)
120
> (defun a (x) (if (= x 0) t (b (- x))))    ;funkcje wzajemnie rekurencyjne
A
> (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1))))
B
> (a 5)
T
> (defun bar (x)                ;funkcja o wielu instrukcjach w swoim
    (setq x (* x 3))            ;ciele -- zwróci wartość, zwracaną przez
    (setq x (/ x 2))            ;jej ostatnią instrukcję
    (+ x 4)
  )
BAR
> (bar 6)
13

Gdy zdefiniowaliśmy `foo', nadaliśmy mu dwa argumenty, `x' i `y'. Teraz, gdy wołamy `foo', musimy podać dokładnie dwa argumenty: pierwszy stanie się na czas wywołania wartością `x', a drugi stanie się wartością `y'. W lispie większość zmiennych jest zawężona leksykalnie; to znaczy jeśli `foo' woła `bar', a `bar' próbuje odnieść się do `x', to nie jest to możliwe.

Proces przyznawania symbolowi wartości na czas zawężeń leksykalnych jest nazywany wiązaniem (binding).

Dla swoich funkcji możesz podawać argumenty dodatkowe. Każdy argument po symbolu &optional jest opcjonalny:


> (defun bar (x &optional y) (if y x 0)) BAR > (defun baaz (&optional (x 3) (z 10)) (+ x z)) BAAZ > (bar 5) 0 > (bar 5 t) 5 > (baaz 5) 15 > (baaz 5 6) 11 > (baaz) 13

Funkcję `bar' można wywołać z jednym lub dwoma argumentami. Po wywołaniu z jednym argumentem, wartość `x' jest ustawiana normalnie, natomiast `y' jest ustawiane na `nil'. Po wywołaniu z dwoma argumentami, wartość `x' i `y' zostanie odpowiednio nimi zainicjalizowana.

Funkcja `baaz' ma dwa argumenty opcjonalne. Dla każdego z nich daje wartość domyślną: jeśli wołający poda tylko jeden argument, `z' zostanie związane nie z `nil', lecz z 10, a gdy wołający w ogóle nie poda argumentów, `x' zostanie powiązany z 3, a `z' z 10.

Możesz spowodować, że funkcja przyjmuje dowolną liczbę argumentów, kończąc jej argument parametrem &rest. LISP wtedy zbierze wszystkie argumenty do listy i przywiąże ją do parametru &rest.


> (defun foo (x &rest y) y) FOO > (foo 3) NIL > (foo 4 5 6) (5 6)

W końcu, możesz podać swojej funkcji dowolny rodzaj argumentu opcjonalnego, zwanego argumentem słowa kluczowego. Wołający może podawać takie argumenty w dowolnej kolejności--są one etykietowane słowami kluczowymi.


> (defun foo (&key x y) (cons x y)) FOO > (foo :x 5 :y 3) (5 . 3) > (foo :y 3 :x 5) (5 . 3) > (foo :y 3) (NIL . 3) > (foo) (NIL)

Parametr &key może mieć również wartość domyślną:


> (defun foo (&key (x 5)) x) FOO > (foo :x 7) 7 > (foo) 5

Drukowanie

Niektóre funkcje mogą przesyłać dane na wyjście. Najprostszą jest print, która drukuje swój argument i zwraca go.


> (print 3)
3
3

Pierwsze 3 zostało wydrukowane, drugie zwrócone.

Jeśli oczekujesz bardziej złożonego wyjścia, musisz użyć formatu. Oto przykład:


> (format t "An atom: ~S~%and a list: ~S~%and an integer: ~D~%" nil (list 5) 6) An atom: NIL and a list: (5) and an integer: 6

Pierwszy argument instrukcji format to `t', `nil' lub strumień. `T' wskazuje na wyjście na terminal. `Nil' oznacza, że niczego nie można drukować, a należy zamiast tego powrócić do łańcucha zawierającego wyjście. Strumienie są ogólnymi miejscami przepływu wyjścia: wskazują na pliki, terminale lub inne programy. Ten podręcznik nie będzie się dokładniej zajmował strumieniami.

Kolejnym argumentem jest matryca formatująca, która jest łańcuchem opcjonalnie zawierającym dyrektywy formatujące.

Wszystkie pozostałe argumenty mogą być używane przez dyrektywy formatujące. LISP zamieni dyrektywy na odpowiednie znaki, w oparciu argumenty, do których (dyrektywy) się one odnoszą. Następnie łańcuch zostanie wydrukowany.

Format zawsze zwraca `nil', chyba że jego pierwszym argumentem jest `nil'--wtedy nic nie drukuje i zwraca łańcuch.

W powyższym przykładzie istnieją trzy różne dyrektywy: ~S, ~D i ~%. Pierwsza przyjmuje dowolny obiekt LISP i jest zamieniana drukowalną reprezentacją tego obiektu (taką samą, jak produkowana przez print). Druga przyjmuje tylko liczby całkowite. Ostatnia nie odnosi się do argumentu. Jest zawsze zamieniana na powrót karetki.

Inną przydatną dyrektywą jest ~~, która jest zamieniana na pojedynczą `~'.

Zajrzyj do podręcznika LISP, są tam różne inne dyrektywy formatujące.

Formy i pętla główna

Wszystko, co wpisujesz do interpretera LISP określane jest mianem form; interpreter odczytuje taką formę, analizuje ją i drukuje wynik. Procedura ta jest nazywana pętlą odczytu-analizy-drukowania.

Niektóre formy mogą dawać błędy. Po błędzie, LISP przerzuci cię do debuggera, abyś mógł znaleźć błąd. Każdy debugger LISP jest inny; jednak większość z nich odpowiada na komendę "help" lub ":help".

Ogólnie, formą jest zarówno atom (np. symbol, liczba całkowita, łańcuch) jak lista. Jeśli forma jest atomem, LISP analizuje ją natychmiast. Jeśli jest listą, to jej pierwszy element jest traktowany jak nazwa funkcji; pozostałe elementy analizowane są rekurencyjnie i wywołuje się tę funkcję z wartościami pozostałych elementów jako argumentami.

Na przykład, jeśli LISP widzi formę (+ 3 4), traktuje `+' jako nazwę funkcji. Potem analizuje 3 aby pobrać 3 i 4 aby pobrać 4; w końcu woła `+' z argumentami 3 i 4. Funkcja `+' zwraca 7, co jest drukowane jako wynik.

Pętla główna daje inne udogodnienia; szczególnie wygodną rzeczą jest możliwość mówienia o wynikach poprzednio wpisanych form. LISP zawsze udostępnia swoje trzy najświeższe wyniki; kryją się one pod symbolami *, ** i ***. Na przykład:


> 3
3
> 4
4
> 5
5
> ***
3
> ***
4
> ***
5
> **
4
> *
4

Formy specjalne

Istnieje pewna liczba form specjalnych, które wyglądają jak wywołania funkcji, lecz nimi nie są. Zaliczają się do nich konstrukcje sterujące, takie jak instrukcje if i pętle do; przypisania takie jak setq, setf, push i pop; definicje takie, jak defunidefstruct; a także konstrukcje wiążące, takie jak let. (Nie wszystkie te formy zostały już wymienione. Patrz niżej.)

Jedną przydatną formą specjalną jest forma cytowania (quote): chroni ona argument przed zanalizowaniem. Na przykład:


> (setq a 3)
3
> a
3
> (quote a)
A
> 'a                    ;'a jest skrótem dla (quote a)
A

Inną prostą formą specjalną jest forma funkcji (function): powoduje, że jej argument jest interpretowany jako funkcja:


> (setq + 3) 3 > + 3 > '+ + > (function +) #<Function + @ #x-fbef9de> > #'+ ;#'+ jest skrótem dla (function +) #<Function + @ #x-fbef9de>

Ta forma specjalna jest przydatna gdy chcesz przekazać funkcję jako argument do innej funkcji. Niżej są przykłady funkcji, biorących argumenty funkcyjne.

Wiązanie

Wiązanie to zawężone leksykalnie przypisanie. Zachodzi na zmiennych w liście parametrów funkcji za każdym wywołaniem funkcji: formalne parametry są wiązane do rzeczywistych parametrów na czas wywołania funkcji. Zmienne można wiązać w dowolnym miejscu programu, używając specjalnej formy let, która wygląda tak:


        (let ((var1 val1)
              (var2 val2)
              ...
             )
          body
        )

`Let' wiąże `var1' do `val1' i `var2' do `val2' (i tak dalej); potem wykonuje instrukcje swojego ciała. Ciało `let' podpada pod dokładnie te same reguły, co ciało funkcji. Przykłady:


> (let ((a 3)) (+ a 1)) 4 > (let ((a 2) (b 3) (c 0)) (setq c (+ a b)) c ) 5 > (setq c 4) 4 > (let ((c 5)) c) 5 > c 4

Zamiast (let ((a nil) (b nil)) ...), możesz napisać (let (a b) ...).

`Val1', `val2', itd. wewnątrz `let' nie mogą odnosić się do zmiennych `var1', `var2', itd., które są wiązane. Na przykład


> (let ((x 1) (y (+ x 1))) y ) Error: Attempt to take the value of the unbound symbol X

Jeśli symbol `x' już ma wartość globalną, wynikiem będą dziwne zdarzenia:


> (setq x 7) 7 > (let ((x 1) (y (+ x 1))) y ) 8

Forma specjalna let* jest podobna do `let', lecz zezwala wartościom na wskazywanie na zmienne, zdefiniowane w `let' wcześniej. Na przykład:


> (setq x 7) 7 > (let* ((x 1) (y (+ x 1))) y ) 2

Forma


(let* ((x a) (y b)) ... )

jest równoważna


(let ((x a)) (let ((y b)) ... ) )

Zakresy dynamiczne

Formy `let' i `let*' dają zawężanie leksykalne, które jest rzeczą, do której mogłeś się przyzwyczaić w C lub Pascalu. Zakresy (zawężanie) dynamiczne są popularne w BASIC-u: jeśli przypiszesz wartość zmiennej dynamicznej, każde wspomnienie tej zmiennej zwraca nadaną wartość, aż nie przyznasz nowej.

W LISP-ie, zmienne dynamicznego zakresu są nazywane zmiennymi specjalnymi. Możesz je deklarować w formie defvar. Oto przykłady zmiennych dynamicznie i leksykalnie zawężanych.

W tym przykładzie, funkcja `check-regular' odnosi się do zmiennej `regular' (np. leksykalnej). Ponieważ `check-regular' jest leksykalnie poza `let', który wiąże `regular', `check-regular' zwraca globalną wartość zmiennej.


> (setq regular 5)
5 
> (defun check-regular () regular)
CHECK-REGULAR 
> (check-regular)
5 
> (let ((regular 6)) (check-regular))
5 

W tym przykładzie, funkcja `check-special' odnosi się do zmiennej `special' (np. dynamicznie zawężonej). Ponieważ wywołanie `check-special' jest tymczasowo wewnątrz `let', który wiąże `special', `check-special' zwraca lokalną wartość zmiennej.


> (defvar *special* 5) *SPECIAL* > (defun check-special () *special*) CHECK-SPECIAL > (check-special) 5 > (let ((*special* 6)) (check-special)) 6

Tradycyjnie, nazwa zmiennej specjalnej rozpoczyna się od `*'. Zmienne specjalne są używane głównie jako zmienne globalne, gdyż programiści zazwyczaj oczekują dla zmiennych lokalnych zawężania leksykalnego, a dla globalnych dynamicznego.

Dla dalszych informacji o różnicach między zawężeniami leksykalnymi i dynamicznymi, zobacz Common LISP: the Language.

Tablice

Funkcja make-array tworzy tablicę. Funkcja aref daje dostęp do jej elementów. Wszystkie elementy tablicy są początkowo ustawione na `nil'. Na przykład:


> (make-array '(3 3))
#2a((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))
> (aref * 1 1)
NIL
> (make-array 4)        ;tablice 1D nie wymagają dodatkowych nawiasów
#(NIL NIL NIL NIL)

Indeksy tablic zawsze rozpoczynają się od 0.

Jak ustawiać elementy tablicy, opisano niżej.

Napisy

Napis jest sekwencją znaków w podwójnych cudzysłowach. W LISP napis jest reprezentowany jako tablica znaków o zmiennej długości. Napis zawierający podwójne cudzysłowy można zapisać, poprzedzając cudzysłów znakiem lewego ukośnika; podwójny lewy ukośnik oznacza pojedynczy lewy ukośnik. Na przykład:


        "abcd" ma 4 znaki
        "\"" ma 1 znak
        "\\" ma 1 znak

Oto kilka funkcji zajmujących się napisami:


> (concatenate 'string "abcd" "efg") "abcdefg" > (char "abc" 1) #\b ;LISP zapisuje znaki poprzedzone #\ > (aref "abc" 1) #\b ;pamiętaj, napisy w rzeczywistości są tablicami

Funkcja concatenate może w rzeczywistości działać z dowolnym rodzajem sekwencji:


> (concatenate 'string '(#\a #\b) '(#\c)) "abc" > (concatenate 'list "abc" "de") (#\a #\b #\c #\d #\e) > (concatenate 'vector '#(3 3 3) '#(3 3 3)) #(3 3 3 3 3 3)

Struktury

Struktury LISP są analogiczne do struktur C lub rekordów Pascala. Oto przykład:


> (defstruct foo
    bar
    baaz
    quux
  )
FOO

Przykład ten definiuje typ danych o nazwie `foo', który jest strukturą, zawierającą 3 pola. Definiuje też 4 funkcje, które operują na tym typie danych: make-foo, foo-bar, foo-baaz i foo-quux. Pierwsza tworzy nowy obiekt typu `foo'; pozostałe dają dostęp do poszczególnych pól obiektu. Oto jak używać tych funkcji:


> (make-foo) #s(FOO :BAR NIL :BAAZ NIL :QUUX NIL) > (make-foo :baaz 3) #s(FOO :BAR NIL :BAAZ 3 :QUUX NIL) > (foo-bar *) NIL > (foo-baaz **) 3

Funkcja `make-foo' może pobierać argument słowa kluczowego dla każdego pola struktury `foo'. Funkcje dostępu do pól pobierają jeden argument, strukturę typu `foo' i zwracają odpowiednią wartość.

Zobacz niżej jak ustawić pola struktury.

Setf

Pewne formy w LISP-ie naturalnie definiują pozycję w pamięci. Na przykład, jeśli wartość `x' jest strukturą typu `foo', to (foo-bar x) definiuje pole `bar' o wartości `x'. Lub jeśli wartość `y' jest jednowymiarową tablicą, (aref y 2) definiuje trzeci element `y'.

Specjalna forma setf używa swojego pierwszego argumentu do zdefiniowania miejsca w pamięci, analizuje drugi argument i zapisuje wynik w wynikowej pozycji pamięci. Na przykład,


> (setq a (make-array 3))
#(NIL NIL NIL)
> (aref a 1)
NIL
> (setf (aref a 1) 3)
3
> a
#(NIL 3 NIL)
> (aref a 1)
3
> (defstruct foo bar)
FOO
> (setq a (make-foo))
#s(FOO :BAR NIL)
> (foo-bar a)
NIL
> (setf (foo-bar a) 3)
3
> a
#s(FOO :BAR 3)
> (foo-bar a)
3

Setf jest jedynym sposobem na ustawianie pól struktury lub tablicy.

Oto kilka innych przykładów setf i związanych z nim funkcji.


> (setf a (make-array 1)) ;setf na zmiennej jest równoważne setq #(NIL) > (push 5 (aref a 1)) ;push może działać jak setf (5) > (pop (aref a 1)) ;podobnie pop 5 > (setf (aref a 1) 5) 5 > (incf (aref a 1)) ;incf odczytuje z danego miejsca, 6 ;inkrementuje i zapisuje z powrotem. > (aref a 1) 6

Zmienne logiczne i warunki

LISP do oznaczania fałszu używa symbolu `nil'. Wszystko inne niż `nil' oznacza prawdę. O ile nie mamy specjalnych powodów, dla prawdy używa się symbolu `t'.

LISP daje standardowy zestaw funkcji logicznych, np. and, or i not. And i or są blisko-zwierające: and nie zanalizuje żadnych argumentów na prawo od pierwszego zrozumianego jako nil, a or nie zanalizuje żadnych argumentów na prawo od pierwszego, zrozumianego jako t.

LISP daje też specjalne formy dla wyrażeń warunkowych. Najprostszą jest if. Pierwszy argument tej formy określa czy wywołać argument drugi, czy trzeci.


> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5

Jeśli potrzebujesz więcej niż jednej instrukcji w klauzuli then lub else if'a, to możesz użyć specjalnej formy progn. Progn wykonuje każdą instrukcję swojego ciała i zwraca ostatnią wartość.


> (setq a 7) 7 > (setq b 0) 0 > (setq c 5) 5 > (if (> a 5) (progn (setq a (+ b 7)) (setq b (+ c 8))) (setq b 4) ) 13

Instrukcja if, której brak klauzuli then lub else, może być zapisana przy użyciu specjalnej formy unless:


> (when t 3) 3 > (when nil 3) NIL > (unless t 3) NIL > (unless nil 3) 3

When i unless w przeciwieństwie do if, zezwalają na dowolną liczbę instrukcji w swoich ciałach. (Np. (when x a b c) jest równoważne (if x (progn a b c)).


> (when t (setq a 5) (+ a 6) ) 11

Bardziej złożone warunki można definiując przy użyciu formy specjalnej cond która jest równoważna konstrukcji if ... else if .. fi.

Cond składa się z symbolu `cond', za którym następują klauzule cond, z których każda jest listą. Pierwszy element klauzuli cond jest warunkiem; pozostałe elementy (jeśli istnieją) są akcją. Forma cond szuka pierwszej klauzuli, której warunek jest spełniony; potem wykonuje odpowiednią akcję i zwraca wartość wynikową. Żaden pozostały warunek nie jest już analizowany; nie są też wykonywane inne akcje niż ta, odpowiadająca warunkowi. Na przykład:


> (setq a 3) 3 > (cond ((evenp a) a) ;jeśli a jest parzyste, zwróć a ((> a 7) (/ a 2)) ;inaczej, jeśli a jest > niż 7, zwróć a/2 ((< a 5) (- a 1)) ;inaczej, jeśli a jest < niż 5, zwróć a-1 (t 17) ;inaczej zwróć 17 ) 2

Jeśli w danej klauzuli cond brakuje akcji, cond zwraca wartość, do której został zredukowany warunek:


> (cond ((+ 3 4))) 7

Oto mała sprytna rekurencyjna funkcja, używająca cond. Możesz spróbować udowodnić, że kończy się dla wszystkich liczb całkowitych x wielkości przynajmniej 1. (Jeśli ci się uda, opublikuj rezultat.)


> (defun hotpo (x steps) ;hotpo oznacza Half Or Triple Plus One (cond ((= x 1) steps) ((oddp x) (hotpo (+ 1 (* x 3)) (+ 1 steps))) (t (hotpo (/ x 2) (+ 1 steps))) ) ) A > (hotpo 7 0) 16

Instrukcja case w LISP jest podobna do instrukcji switch w C:


> (setq x 'b) B > (case x (a 5) ((d e) 7) ((b f) 3) (otherwise 9) ) 3

Klauzula otherwise oznacza, że jeśli x nie jest a, b, d, e lub f, instrukcja case ma zwrócić 9.

Iteracja

Najprostszą konstrukcją iteracyjną w LISP jest pętla (loop): konstrukcja ta kolejno wykonuje swoje ciała, aż nie natrafi na specjalną formę return. Na przykład


> (setq a 4)
4
> (loop 
   (setq a (+ a 1))
   (when (> a 7) (return a))
  )
8
> (loop
   (setq a (- a 1))
   (when (< a 3) (return))
  )
NIL

Kolejną najprostszą jest dolist: wiąże ona zmienną do elementów listy w kolejności i kończy gdy trafi na koniec listy.


> (dolist (x '(a b c)) (print x)) A B C NIL

Dolist zawsze zwraca `nil'. Zauważ, że wartość `x' w powyższym przykładzie nigdy nie była `nil': NIL znajdujący się po C był wartością zwracaną przez dolist, drukowaną przez pętlę odczytu-analizy-drukowania.

Najbardziej skomplikowanym rodzajem iteracji jest do. Instrukcja do wygląda następująco:


> (do ((x 1 (+ x 1)) (y 1 (* y 2))) ((> x 5) y) (print y) (print 'working) ) 1 WORKING 2 WORKING 4 WORKING 8 WORKING 16 WORKING 32

Pierwsza część `do' określa, które zmienne należy związać, jakie są ich wartości początkowe i jak je odświeżać. Następna część określa warunek zakończenia i wartość zwracaną. Ostatnią częścią jest ciało. Forma do wiąże jak `let' swoje zmienne do wartości początkowych, a potem sprawdza warunek zakończnia. Dopóki warunek jest fałszywy, wykonuje ciało; gdy warunek staje się prawdziwy, zwraca wartość formy return-value.

Forma do* jest analogiczna do `let*' w `let'.

Nielokalne wyjścia

Forma specjalna return, wspomniana w poprzedniej sekcji o iteracji jest przykładem nielokalnego wyjścia. Innym przykładem jest forma return-form, która zwraca wartość z otaczającej funkcji:


> (defun foo (x)
    (return-from foo 3)
    x
  )
FOO
> (foo 17)
3

Właściwie, forma return-form może zwrócić wartość z dowolnego nazwanego bloku--po prostu funkcje są jedynymi blokami, które są domyślnie nazwane. Blok nazwany możesz tworzyć specjalną formą block:


> (block foo (return-from foo 7) 3 ) 7
Forma specjalna return może zwrócić wartość z dowolnego bloku o nazwie `nil'. Domyślnie przez nil oznaczane są pętle, lecz możesz też tworzyć własne bloki oznaczone `nil':

> (block nil (return 7) 3 ) 7

Inną formą, która powoduje nielokalne wyjście jest forma error:


> (error "This is an error") Error: This is an error

Forma error załącza format do swoich argumentów, a potem umieszcza cię w debuggerze.

Funcall, Apply, i Mapcar

Wcześniej obiecałem, że dam trochę funkcji, które jako argumenty pobierają funkcje. Oto one:


> (funcall #'+ 3 4)
7
> (apply #'+ 3 4 '(3 4))
14
> (mapcar #'not '(t nil t nil t nil))
(NIL T NIL T NIL T)

Funcall woła swój pierwszy argument z pozostałymi argumentami.

Apply działa podobnie do funcall, lecz jej ostatni argument powinien być listą; elementy tej listy są traktowane tak, jakby były dodatkowymi argumentami funcall.

Pierwszy argument mapcar musi być funkcją jednoargumentową; mapcar stosuje tą funkcję do każdego elementu listy i zbiera wyniki w innej liście.

Funcall i apply są używane często gdy ich pierwszym argumentem jest zmienna. Na przykład mechanizm przeszukiwania może brać jako parametr funkcję heurystyczną i używać funcall lub apply do wołania jej dla opisu stanu. Funkcje sortowania, opisane dalej używają funcall do wołania funkcji porównawczych.

Mapcar wraz funkcjami bez nazwy (patrz niżej) może zastąpić wiele pętli.

Lambda

Jeśli chcesz utworzyć funkcję tymczasową i nie chcesz nadawać jej nazwy, to możesz użyć lambda.


> #'(lambda (x) (+ x 3))
(LAMBDA (X) (+ X 3))
> (funcall * 5)
8
Połączenie lambda i mapcar może zastąpić wiele pętli. Na przykład, następujące postacie są równoważne:

> (do ((x '(1 2 3 4 5) (cdr x)) (y nil)) ((null x) (reverse y)) (push (+ (car x) 2) y) ) (3 4 5 6 7) > (mapcar #'(lambda (x) (+ x 2)) '(1 2 3 4 5)) (3 4 5 6 7)

Sortowanie

LISP daje do sortowania dwa prymitywy: sort i stable-sort.


> (sort '(2 1 5 4 6) #'<)
(1 2 4 5 6)
> (sort '(2 1 5 4 6) #'>)
(6 5 4 2 1)

Pierwszym argumentem sort jest lista; drugim funkcja porównawcza. Funkcja sortująca nie gwarantuje stabilności: jeśli są dwa elementy takie, że (and (not (< a b)) (not (< b a))) , to sort może zaaranżować je w dowolnym porządku. Funkcja stable-sort jest taka sama jak sort, lecz gwarantuje, że dwa równoważne elementy pojawią się w liście posortowanej w tej samej kolejności co na liście wejściowej.

Uwaga: sort może niszczyć swój argument, więc jeśli sekwencja wejściowa jest dla ciebie ważna, utwórz jej kopię poleceniem copy lub copy-list czy copy-seq.

Równość

LISP ma wiele różnych wizji równości. Równość numeryczna jest oznaczana przez =. Dwa symbole są eq tylko gdy są identyczne. Dwie kopie tej samej listy nie są eq, choć są równe.


> (eq 'a 'a)
T
> (eq 'a 'b)
NIL
> (= 3 4)
T
> (eq '(a b c) '(a b c))
NIL
> (equal '(a b c) '(a b c))
T
> (eql 'a 'a)
T
> (eql 3 3)
T

Eql jest równoważny eq dla symboli i = dla liczb.

Equal jest równoważny eql dla symboli i liczb. Jest prawdziwy dla dwóch wagoników tylko gdy ich car-y i cdr-y są sobie równe. Jest prawdziwy dla dwóch struktur tylko gdy wszystkie ich pola są sobie równe.

Przydatne funkcje listowe

Oto funkcje, działające na listach.


> (append '(1 2 3) '(4 5 6))    ;łącz (konkatenuj) listy
(1 2 3 4 5 6)
> (reverse '(1 2 3))            ;odwróć elementy listy
(3 2 1)
> (member 'a '(b d a c))        ;ustaw członkowstwo--zwraca pierwszy ogon
(A C)                           ;w którym car jest żądanym elemetem.
> (find 'a '(b d a c))          ;inny sposób ustawiania członkowstwa
A
> (find '(a b) '((a d) (a d e) (a b d e) ()) :test #'subsetp)
(A B D E)                       ;find jest jednak bardziej elastyczny
> (subsetp '(a b) '(a d e))     ;zawieranie się zbiorów
NIL
> (intersection '(a b c) '(b))  ;przekrój zbiorów
(B)
> (union '(a) '(b))             ;suma zbiorów
(A B)
> (set-difference '(a b) '(a))  ;różnica zbiorów
(B)

Subsetp, intersection, union i set-difference zakładają, że żaden z argumentów nie posiada zduplikowanych elementów. Np. (subsetp '(a a) '(a b b)) może się nie powieść.

Find, subsetp, intersection, union i set-difference mogą brać argument o słowie kluczowym :test; domyślnie wszystkie używają eql.

Początki z Emacsem

Do edycji kodu LISP możesz używać Emacsa: większość Emacsów jest skonfigurowania na wchodzenie w tryb LISP za każdym razem gdy znajdą plik, kończący się na .lisp. Jeśli u ciebie tak się nie dzieje, wpisz M-x lisp-mode.

Pod Emacsem możesz też uruchamiać LISP: upewnij się, że masz w ścieżce komendę o nazwie lisp, która uruchamia twojego ulubionego LISP-a. Na przykład możesz wpisać


        ln -s /usr/local/bin/clisp ~/bin/lisp

Potem, w Emacsie wpisz M-x run-lisp. Możesz wtedy wysłać kod LISP do interpretera i zrobić wiele innych fajnych rzeczy; dla dalszych informacji wpisz C-h m z dowolnego bufora w trybie LISP.

W rzeczywistości nawet nie musisz tworzyć dowiązania. Emacs ma zmienną o nazwie inferior-lisp-program; więc jeśli dodasz linię


(setq inferior-lisp-program "/usr/local/bin/clisp")

do swojego pliku .emacs, to Emacs będzie wiedział gdzie szukać CLISP-a dla komendy M-x run-lisp.

AUTORZY

Geoffrey J. Gordon <[email protected]> (piątek, 5 lutego 1993). Poprawione przez Brunona Haible'a <[email protected]>. Przetłumaczone na język polski i przekonwertowane do postaci man przez Przemka Borysa <[email protected]>

INFORMACJE O TŁUMACZENIU

Powyższe tłumaczenie pochodzi z nieistniejącego już Projektu Tłumaczenia Manuali i może nie być aktualne. W razie zauważenia różnic między powyższym opisem a rzeczywistym zachowaniem opisywanego programu lub funkcji, prosimy o zapoznanie się z oryginalną (angielską) wersją strony podręcznika.