Aby udzielić krótkiej odpowiedzi, makra są używane do definiowania rozszerzeń składni języka dla Common Lisp lub DSL (Domain Specific Languages). Te języki są osadzone bezpośrednio w istniejącym kodzie Lisp. Teraz DSL mogą mieć składnię podobną do Lisp (jak interpreter Prologa Petera Norviga dla Common Lisp) lub zupełnie inną (np. Infix Notation Math dla Clojure).
Oto bardziej konkretny przykład:
Python ma wbudowane w listę interpretacje list. Daje to prostą składnię dla typowego przypadku. Linia
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
zwraca listę zawierającą wszystkie liczby parzyste od 0 do 9. W Pythonie 1,5 dnia nie było takiej składni; użyłbyś czegoś takiego:
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
Oba są funkcjonalnie równoważne. Przywołajmy nasze zawieszenie niewiary i udawajmy, że Lisp ma bardzo ograniczone makro pętli, które po prostu wykonuje iterację i nie ma łatwego sposobu na zrobienie odpowiednika wyrażeń listowych.
W Lisp możesz napisać następujące. Powinienem zauważyć, że ten wymyślony przykład został wybrany jako identyczny z kodem Python, a nie dobrym przykładem kodu Lisp.
;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
(if (= x 0)
(list x)
(cons x (range-helper (- x 1)))))
(defun range (x)
(reverse (range-helper (- x 1))))
;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)
;; loop from 0 upto and including 9
(loop for x in (range 10)
;; test for divisibility by two
if (= (mod x 2) 0)
;; append to the list
do (setq divisibleByTwo (append divisibleByTwo (list x))))
Zanim przejdę dalej, powinienem lepiej wyjaśnić, czym jest makro. Jest to transformacja wykonywana na kod po kodzie. To znaczy fragment kodu odczytywany przez interpretera (lub kompilatora), który przyjmuje kod jako argument, manipuluje i zwraca wynik, który jest następnie uruchamiany w miejscu.
Oczywiście to dużo pisania, a programiści są leniwi. Możemy więc zdefiniować DSL do wykonywania list. W rzeczywistości używamy już jednego makra (makro pętli).
Lisp definiuje kilka specjalnych form składni. Quote ( '
) wskazuje, że następny token jest dosłowny. Quasi-cytat lub backtick ( `
) oznacza, że następny token jest dosłowny ze znakami ucieczki. Ucieczki są oznaczone przez przecinek. Dosłowność '(1 2 3)
jest odpowiednikiem języka Python [1, 2, 3]
. Możesz przypisać ją do innej zmiennej lub użyć w miejscu. Możesz myśleć o `(1 2 ,x)
tym, że jest odpowiednikiem Pythona, [1, 2, x]
gdzie x
jest wcześniej zdefiniowaną zmienną. Ten zapis listy jest częścią magii, która przechodzi w makra. Druga część to czytnik Lisp, który inteligentnie zastępuje makra kodem, ale najlepiej to ilustruje poniżej:
Możemy więc zdefiniować makro o nazwie lcomp
(skrót od listy). Jego składnia będzie dokładnie taka jak w pythonie, którego użyliśmy w przykładzie [x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
(defmacro lcomp (expression for var in list conditional conditional-test)
;; create a unique variable name for the result
(let ((result (gensym)))
;; the arguments are really code so we can substitute them
;; store nil in the unique variable name generated above
`(let ((,result nil))
;; var is a variable name
;; list is the list literal we are suppose to iterate over
(loop for ,var in ,list
;; conditional is if or unless
;; conditional-test is (= (mod x 2) 0) in our examples
,conditional ,conditional-test
;; and this is the action from the earlier lisp example
;; result = result + [x] in python
do (setq ,result (append ,result (list ,expression))))
;; return the result
,result)))
Teraz możemy wykonać w wierszu poleceń:
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
Całkiem nieźle, co? Teraz to nie koniec. Masz mechanizm lub pędzel, jeśli chcesz. Możesz mieć dowolną składnię. Jak with
składnia Python lub C # . Lub składnia LINQ .NET. To właśnie przyciąga ludzi do Lisp - najwyższa elastyczność.