Common Lisp (w skrócie CL) to specyfikacja języka Lisp znormalizowana przez ANSI .
Common Lisp jest dialektem Lispa znormalizowanym przez ANSI X3.226-1994. Opracowany w celu ujednolicenia rozbieżnych wariantów Lispa, które pojawiły się przed nim, nie jest implementacją, ale specyfikacją, do której implementacje Lispa starają się dostosować. Często jest skracany do CL .
Common Lisp jest językiem programowania ogólnego przeznaczenia, w przeciwieństwie do dialektów Lispa, takich jak Emacs Lisp i AutoLisp , które są językami rozszerzeń osadzonymi w poszczególnych produktach. W przeciwieństwie do wielu starszych Lispsów, ale podobnie jak Scheme , Common Lisp używa domyślnego zakresu leksykalnego dla zmiennych.
Common Lisp to wielo-paradygmatyczny język programowania, który:
Jak każdy język z rodziny Lisp, Common Lisp używa wyrażeń S do oznaczania zarówno kodu, jak i pewnych struktur danych. Wywołania funkcji, formularzy specjalnych i makr są zapisywane w literalnej składni listy, z nazwą funkcji (odpowiednio formą specjalną i makrem) na pierwszym miejscu, jak w poniższych przykładach:
(+ 2 2) ; ajoute 2 et 2, renvoie 4 (defvar e) ; définit la variable e (setf e 2.7182817) ; assigne 2.7182817 à la variable e (setf e 'rouge) ; assigne le mot rouge à la variable e nil ; valeur booléenne false et liste vide t ; valeur booléenne true (if (< x y) y ; résultat si la condition est vérifiée (ici, si x < y) x) ; résultat par défaut (defun carre (x) (* x x)) ; définit une fonction qui met un nombre au carré (carre 3) ; exécution de la fonction : retourne 9Typy liczbowe obejmują liczby całkowite , liczby wymierne, liczby zmiennoprzecinkowe i liczby zespolone . Common Lisp używa dużych liczb do reprezentowania wartości liczbowych o dowolnym rozmiarze i precyzji. Typ wymierny reprezentuje dokładnie ułamki. Common Lisp automatycznie odpowiednio konwertuje wartości liczbowe między tymi typami. Oto numeryczna wieża , czyli hierarchia numerycznych typów Common Lisp:
complex ratio fixnum / / / number <--+-real--+-rational--+-integer--+-bignum \ \ \ (1) signed-byte--unsigned-byte--bit \ float--+-short-float \-single-float \-double-float \-long-float(1) liczba całkowita i bajt ze znakiem to specyfikacje typu rozłącznego; jednak domeny są takie same.
Na przykład wyrażenie
(+ (sqrt -2) (/ 6 4))powrót
#C(3/2 1.4142135)to znaczy liczba zespolona, której częścią urojoną jest liczba zmiennoprzecinkowa 1,4142135, a częścią rzeczywistą jest liczba wymierna 3/2.
PostacieTyp znaków Common Lisp nie jest ograniczony do znaków ASCII ; nie jest to zaskakujące, ponieważ Lisp jest starszy niż ASCII. Niektóre nowoczesne implementacje obsługują znaki Unicode .
SymbolikaTyp symbolu jest wspólny dla języków Lisp, ale w dużej mierze nieznany na zewnątrz. Symbol jest nazwanym, unikalnym obiektem w odniesieniu do przestrzeni nazw . Symbole literalne w Lisp są używane jako identyfikatory zmiennych; są jednak bardziej ogólne i mogą być używane samodzielnie jako obiekty. Kiedy symbol jest oceniany, zwracana jest jego wartość jako zmienna.
Istnienie symboli jako typu danych ułatwia pisanie makr (które wykonują transformacje kodu w czasie kompilacji) i implementowanie symbolicznych systemów obliczeniowych. W rzeczywistości Lisp był pierwszym językiem do implementacji systemów algebry komputerowej. Bardzo kompletne systemy algebry komputerowej zostały napisane w Lispie ( Maxima i Axiom , a dla przekształceń symbolicznych można je porównać z popularnymi Mathematica i Maple ).
Istnieją specjalne przypadki:
Przykłady:
foo ;; -> La variable FOO n'a pas de valeur. (function foo) ;; -> La fonction FOO n'est pas définie.Operator QUOTE chroni symbole przed oceną (gdy chcemy użyć symbolu dla siebie):
(quote foo) ;; -> FOO 'foo ;; -> FOOMożesz zapytać, czy symbol jest powiązany z wartością lub funkcją:
(boundp 'foo) ;; -> NIL (pas de valeur liée) (fboundp 'foo) ;; -> NIL (aucune fonction nommée FOO n'existe)Skojarzenie symbol-wartość:
(defparameter foo 77) ;; -> FOO foo ;; -> 77Skojarzenie symbol-funkcja:
(defun foo (bar) (1+ bar)) ;; -> FOOWywołanie funkcji FOO z wartością FOO (ilustruje, że symbol ma oddzielne miejsca na wartości i funkcje):
(foo foo) ;; -> 78 (boundp 'foo) ;; -> T (fboundp 'foo) ;; -> T (function foo) ;; -> #<CLOSURE FOO (BAR) (DECLARE (SYSTEM::IN-DEFUN FOO)) (BLOCK FOO (1+ BAR))>Rekurencja:
(defun factoriel (n) (if (= n 0) 1 (* n (factoriel(- n 1)))))Inny:
(first '(7 3 10)) ;;-> 7 (rest '(20 2 45)) ;;-> (2 45) (endp '()) ;;-> T (endp '(5)) ;;-> NIL liste non videTe sekwencje są abstrakcyjny typ reprezentujący uporządkowany zbiór elementów. Konkretne typy wyprowadzone z sekwencji to listy i wektory (w tym wektory bitowe i łańcuchy). Dla sekwencji dostępnych jest wiele funkcji. Jednak programista nie jest w stanie zdefiniować własnych podtypów sekwencji. Proponowane jest rozszerzenie standardu pozwalające na definiowanie nowych sekwencji (ok. 2007 r.).
Pary, listyLista Common Lisp nie są typem danych, ale są wykonane z wagoników (liczba mnoga), czasami nazywany idioci komórkowe lub pary . A minusy to konstrukcja z dwoma elementami, dostępny przez funkcje samochodu i CDR (lub nawet pierwszego i reszta ). Lista jest więc łańcuchem połączonych konsów , gdzie cdr każdej z wad wskazuje na następny element, a ostatni cdr wskazuje na wartość NIL. W Wagoniki może być łatwo wykorzystane do realizacji drzew lub skomplikowanych struktur danych; chociaż w tym drugim przypadku zalecane jest użycie struktur lub klas.
Drzewo (1 (2/7 3.14) A "foo") jest reprezentowane przez następujący ciąg CONS:
Można go zbudować na różne sposoby, wymieniamy dwa:
(list 1 (list 2/7 3.14) 'a "foo") (cons 1 (cons (cons 2/7 (cons 3.14 NIL)) (cons 'a (cons "foo" NIL))))Listy lub struktury cykliczne zbudowane z par nie mają reprezentacji dosłownej, ale można je wydrukować:
(setf *print-circle* t) ; active l'affichage des structures circulaires (évite le bouclage infini) (let ((list (copy-list '(a b c)))) (setf (cdr (last list)) list)) ; => #1=(A B C . #1#) ObrazyCommon Lisp obsługuje tablice o dowolnych wymiarach, a także może dynamicznie zmieniać rozmiar tablic. Te tablice wielowymiarowe można na przykład wykorzystać do obliczeń macierzy. Tylko jednowymiarowe tablice (zwane wektorami) są podtypem sekwencji.
Tabele mogą być wyspecjalizowane według rodzaju zawartych w nich elementów. W szczególności wektory bitowe i wektory znakowe (łańcuchy) są standardowo dostarczane przez język.
Przykład tworzenia wektorów:
(setf v (make-array 3)) ; creation du vecteur (setf (aref v 0) 1) ; v[0]:= 1 (aref v 0) ; -> 1Przykład tworzenia trójwymiarowej tablicy (4 x 2 x 3) i inicjalizacji:
(make-array '(4 2 3) :initial-contents '(((a b c) (1 2 3)) ((d e f) (3 1 2)) ((g h i) (2 3 1)) ((j k l) (0 0 0))))... to zwraca:
#3A(((A B C) (1 2 3)) ((D E F) (3 1 2)) ((G H I) (2 3 1)) ((J K L) (0 0 0))) RejestryPrzykłady:
(defstruct livre auteur titre) ; construit une structure "livre" contenant deux champs (make-livre) ; construit la structure en mémoire (setf l (make-livre :auteur 'Hugo)) ; crée un livre dont l'auteur est Hugo et l'associe à la variable l (setf (livre-titre l) 'Miserables) ; associe un titre au livre de Hugo (livre-titre l) ;;-> Miserables Tabele haszująceTe tabele hash przechowywania powiązań pomiędzy obiektami. Jako klucz lub wartość można użyć dowolnego typu obiektu. Tabele skrótów, podobnie jak tablice, są w razie potrzeby automatycznie zmieniane.
PakietyPakiety ( pakiety ) to zbiory symboli, używane głównie do dzielenia programu na przestrzenie nazw . Pakiet może eksportować określone symbole, oznaczając je jako część publicznego interfejsu. Tak zwane zmienne prywatne i metody klasycznych języków obiektowych (zasada hermetyzacji ) uzyskuje się w Lispie przez zadeklarowanie ich w przestrzeni nazw, bez ich eksportowania.
StrukturyDo struktury , podobnej do elemencie C i płyt (rejestr) Pascal, oznaczają dowolne złożonych struktur danych o dowolnej liczbie i dowolnego typu pola (zwane szczeliny ). Struktury obsługują ograniczoną formę dziedziczenia. Na potrzeby programowania obiektowego mówimy o CLOS .
Klasy i obiektyCommon Lisp był pierwszym znormalizowanym językiem obiektowym (w 1994 r. Przez ANSI ). Część języka zajmująca się obiektami nazywa się CLOS dla Common Lisp Object System . Istotne cechy CLOS są następujące:
CLOS umożliwia także definiowanie meta-klas i klas w celu zmiany klasy obiektu w czasie wykonywania .
System warunków Common Lisp używa CLOS do definiowania typów warunków, które mogą wystąpić w czasie wykonywania.
Niektóre implementacje oferują jako rozszerzenie CLOS protokół meta-obiektu opisany w książce The Art of the Metaobject Protocol .
W Common Lisp funkcje są typem danych. Na przykład możliwe jest napisanie funkcji, które przyjmują inne funkcje jako parametry i zwracają funkcje (nazywamy je funkcjami wyższego rzędu lub funkcjami pierwszej klasy ). Umożliwia to pisanie bardzo ogólnych operatorów.
PrzykładNa przykład funkcja sort (sort) przyjmuje jako parametr sekwencję i operator porównania. Można go używać nie tylko do sortowania dowolnych typów danych, ale także do sortowania struktur danych według klucza.
(sort (list 5 2 6 3 1 4) #'>) ;; -> (6 5 4 3 2 1), en utilisant la fonction > comme opérateur de comparaison (sort `((9 a) (3 b) (4 c)) (lambda (x y) (< (first x) (first y)))) ;; -> ((3 b) (4 c) (9 a)), i.e. la liste triée sur le premier élémentZdefiniowaną powyżej funkcję FOO możemy zastosować do sekwencji:
(mapcar #'foo (list 1 2 3 4 5)) ;; -> (2 3 4 5 6) Przestrzenie nazwCommon Lisp ma przestrzeń nazw odpowiednio dla funkcji i zmiennych (w przeciwieństwie na przykład do Scheme , o którym mówi się, że to „Lisp-1”). Lisp-2 (lub więcej) ma tę zaletę, że nie nazwa zmiennej może ukryć nazwy funkcji: można nazwać zmienną wady lub nawet jeśli bez problemu. Jednak aby odnieść się do funkcji jako zmiennej, należy użyć funkcji (funkcja…) lub równoważnej notacji # ', jak w powyższych przykładach. Aby wywołać funkcję przekazaną jako parametr, musisz użyć funkcji funcall .
Oprócz funkcji i zmiennych istnieje oddzielna przestrzeń nazw dla par operatorów blok / powrót z i tagbody / go.
Na koniec dodajmy, że przestrzeń nazw funkcji jest w rzeczywistości współdzielona przez same funkcje i różne rodzaje makr.
OcenaModel oceny jest prosty: kiedy oceniający napotyka wyrażenie (F A1 A2… An), symbol F może reprezentować jedną z następujących pozycji:
Jeśli F jest funkcją, parametry są oceniane kolejno od lewej do prawej, a funkcja jest wywoływana z obliczonymi wartościami parametrów. W przypadku operatorów specjalnych lub makr to zależy . Operatorzy ci mają tendencję do kontrolowania oceny swoich parametrów. Na przykład operator if nie ocenia wszystkich swoich parametrów, musi ocenić swój stan, a następnie, w zależności od wyniku, gałąź alternatywy.
Ujęcie leksykalneZamknięcie leksykalne to funkcja, której zmienne wolne przechwytują linki środowiska leksykalnego, w którym są zdefiniowane. Umożliwia to budowanie funkcji posiadających stan wewnętrzny (w C używalibyśmy słowa kluczowego static, aby uzyskać stan wewnętrzny, ale przechwytywanie leksykalne nie jest możliwe). Z zamknięć można budować proste obiekty , na przykład fabrykę liczników:
(defun fabriquer-compteur () ; fabriquer-compteur renvoie une fonction qui incrémente et affiche sa valeur interne (let ((valeur 0)) ; dans l'environnement de la fabrique, on crée la valeur du compteur (lambda () ; le nouveau compteur lui-même (incf valeur)))) ; ici, la référence à "valeur" capture sa définition dans la fabriqueInne typy danych Common Lisp obejmują:
Common Lisp zawiera również zestaw narzędzi do programowania obiektowego , Common Lisp Object System lub CLOS. Dlatego możliwe jest dodanie nieskończonej liczby typów.
Makro w Lisp powierzchownie przypomina funkcję. Makra umożliwiają programiście Lispa tworzenie nowych form składniowych w języku poprzez przekształcanie ich parametrów reprezentujących kod źródłowy. Na przykład poniższe makro zapewnia formę pętli until(pętla ... do), która jest znana w języku takim jak Perl .
W związku z tym makra nie są oceniane w czasie wykonywania funkcji, ale w czasie kompilacji. Wartością zwracaną przez makro jest jego rozwinięcie makra , czyli dokonana transformacja kodu; jest to oceniane podczas wykonywania programu dla każdego wywołania makra.
Makra można traktować jako funkcje, które akceptują i zwracają abstrakcyjne drzewa składniowe (wyrażenia S), ale kontrolują ocenę ich parametrów. Rzeczywiście, w wywołaniu makra (my-makro (+ 1 2)) wyrażenie (+ 1 2) nie jest najpierw oceniane, a jego wynik jest przekazywany jako argument, jest przekazywany tak jak jest do makra, które może ponownie użyć go w nienaruszonym stanie , interpoluj go z innymi fragmentami kodu lub przekształć go mniej lub bardziej całkowicie.
Podobnie jak funkcje, makra mogą używać całego języka Common Lisp (i bibliotek innych firm) do wykonywania swoich prac transformacyjnych (dlatego nazywane są makrami proceduralnymi ), w przeciwieństwie do makr języka C, które pozwalają jedynie na podstawianie łańcuchów z poziomu źródła, bez dostępu do cały język C.
Głównym przedmiotem zainteresowania makr nie są małe narzędzia, takie jak powyższy przykład, których rozprzestrzenianie się w programach Lisp może prowadzić do efektu offuskacji , ale możliwość zdefiniowania języków osadzonych w Lisp, które są przeznaczone dla określonego obszaru aplikacji. Klasycznym przykładem jest włączenie do języka rozszerzenia umożliwiającego programowanie logiczne w sposób podobny do Prologu lub nawet programowania z ograniczeniami (dodanie nowego paradygmatu programowania). Rzeczywiście jest możliwe zbudowanie, używając makr i funkcji Lisp, kompilatora dla języka na wyższym poziomie niż język podstawowy, ale który pozostaje zintegrowany z tym ostatnim.
Wszystkie obszary zastosowań mogą skorzystać na technice pisania programu przy użyciu podejścia odgórnego , w którym problem do rozwiązania jest dekomponowany, aż można go wyrazić w języku bazowym, a podejście wznoszące ( oddolne ) , gdzie rozszerza rdzeń języka o koncepcje ułatwiające wyrażenie problemu. Ponieważ nie język programowania ogólnego przeznaczenia może być zasilany z operatorami specyficzne dla nieskończoności konkretnych dziedzin zastosowania, zdolność do budowania dostosowany język ( oddolne podejście ), poprzez rozszerzenie i ponownego wykorzystania bazy, jest główną zaletą Lisp w stosunku do innych języków uniwersalnych . Z tego punktu widzenia makra są wyjątkową zaletą języków z rodziny Lisp. Łatwość pisania makr wynika z użycia wyrażeń S dla składni Lispa.
Mechanizm makr nie byłby wystarczająco wygodny w użyciu bez mechanizmu pozwalającego na przedstawienie kodu Lispa w postaci wzorca (modelu, a nawet szablonu ), do którego można wstrzyknąć elementy obliczone lub podane jako parametry. Common Lisp oferuje quasi-cudzysłów , reprezentowany przez znak `(wspomniany cytat odwrotny ). Chociaż operator odwrotnego cudzysłowu może być używany w funkcjach, to w makrach jego użycie jest kluczowe: poprawia czytelność kodu tworzonego przez makro w znacznych proporcjach.
W powyższym przykładzie do obliczenia wyniku wykorzystano cudzysłowy odwrotne. Ciało aż makro jest całkowicie reprezentowana przez szablon kodu, używając zrobić iteracji operatora . Przypomnijmy, że DO przyjmuje trzy argumenty: zestaw zmiennych iteracyjnych zdefiniowanych lokalnie i iterowanych równolegle, zestaw klauzul umożliwiających określenie warunków iteracji zmiennych i zatrzymania pętli oraz grupę - dowolnej wielkości - dowolne operacje lub akcje:
do ({var | (var [init-form [[step-form]])}*) (end-test-form result-form*) declaration* {tag | statement}* => result*W przypadku pętli till użycie DO jest proste: nie ma zmiennych iteracyjnych, jest dokładnie jedna klauzula testowa i możemy zezwolić na wykonanie grupy operacji w każdej iteracji. Szablon, który ma zostać utworzony, odzwierciedla to:
`(do () (, test) ,@body))Zwracamy uwagę na odwrotny cudzysłów poprzedzający wyrażenie DO. W wyrażeniu backquote możemy wstrzyknąć informacje na dwa sposoby: z operatorem , ( koniec cytatu ), który natychmiast wstrzykuje obiektu do jego prawa lub operator @ ( splice-koniec cytatu ), który przyznaje listę do jego prawa i wtryskuje treści listy w miejscu.
Wynik:
(macroexpand-1 '(until (= (random 10) 0) (write-line "Hello")))jest ekspansją
(DO NIL ((= (RANDOM 10) 0)) (WRITE-LINE "Hello"))w którym znajdujemy szablon połączony z parametrami wywołania makra. Końcowe rozwinięcie, wyprodukowane przez (makroexpand…), zawiera rozszerzenie samego operatora DO w zakresie operatorów niższego poziomu. Ponieważ DO jest operatorem zdefiniowanym przez język Common Lisp, semantyka operatora jest zawsze taka sama. Ale to rozszerzenie może się różnić w zależności od kompilatora. Widzimy, że makra są już szeroko stosowane w implementacji bogatego i złożonego języka, takiego jak Common Lisp.
Typowe makra Lisp są zdolne do przechwytywania zmiennych , czyli sytuacji, w której symbole znajdujące się w treści makro-rozwinięcia pokrywają się z symbolami w kontekście wywołania. Z tego powodu czasami określa się je jako makra „niehigieniczne” w porównaniu do systemu „makr higienicznych” Schematu , który zapewnia separację między tymi zestawami symboli.
Czasami pożądanym efektem jest przechwytywanie zmienne; jeśli tak nie jest, należy tego unikać, stosując gensymy lub symbole, których niepowtarzalność jest gwarantowana.
Common Lisp jest definiowany przez specyfikację (jak Ada i C ), a nie przez pojedynczą implementację (jak Perl lub Python ). Istnieje wiele implementacji, a standard opisuje, gdzie mogą się one różnić z ważnych powodów.
Ponadto implementacje zwykle zawierają różne zestawy „pakietów” bibliotek, które zapewniają funkcje nie objęte standardem. Niektóre z tych funkcji zostały później wprowadzone do standardu, takie jak CLOS i LOOP ; inne pozostają specyficzne dla tych implementacji. Wiele udogodnień dla współczesnego programisty - takich jak dostęp do sieci TCP / IP - pozostaje poza standardem, ale zapewnia je różne implementacje, czasami z niewielkimi różnicami. Proces zwany CLRFI (Common Lisp Request For Improvement), podobny do SRFI Scheme , ma na celu połączenie przydatnych funkcji pozostawionych poza standardem ANSI z 1995 roku.
Istnieje powszechne błędne przekonanie, że wszystkie implementacje Common Lisp są interpreterami. W rzeczywistości kompilacja jest częścią specyfikacji języka . Większość popularnych implementacji Lisp kompiluje funkcje do kodu maszynowego. Inne kompilują do kodu obiektowego lub nawet do kodu maszyny wirtualnej aplikacji, co zmniejsza szybkość, ale poprawia przenośność.
Niektóre implementacje w środowisku UNIX , takie jak CLISP, mogą być używane jako interpretery skryptów (lub nawet jako powłoka).
Common Lisp jest z powodzeniem używany w wielu komercyjnych zastosowaniach:
Istnieją również aplikacje open source napisane w Common Lisp, takie jak:
Common Lisp jest również używany przez wiele rządów i instytucje typu Act 1901. Przykłady użycia przez NASA obejmują: