Чтобы дать краткий ответ, макросы используются для определения языковых синтаксических расширений для Common Lisp или Domain-Specific Languages (DSL). Эти языки встроены прямо в существующий код на Лиспе. Теперь DSL могут иметь синтаксис, похожий на Lisp (например, Prolog Interpreter для Common Lisp Питера Норвига) или совершенно другой (например, Математическая система обозначений Infix для Clojure).
Вот более конкретный пример:
Python имеет встроенные в язык списки. Это дает простой синтаксис для общего случая. Линия
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
возвращает список, содержащий все четные числа от 0 до 9. В течение дней Python 1,5 такого синтаксиса не было; вы бы использовали что-то вроде этого:
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
Они оба функционально эквивалентны. Давайте вызовем нашу приостановку неверия и притворимся, что в Лиспе есть очень ограниченный макрос цикла, который просто выполняет итерации, и нет простого способа сделать эквивалентную работу со списком.
В Лиспе вы могли бы написать следующее. Я должен отметить, что этот надуманный пример выбран так, чтобы он был идентичен коду Python, а не является хорошим примером кода на Лиспе.
;; 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))))
Прежде чем идти дальше, я должен лучше объяснить, что такое макрос. Это преобразование, выполненное для кода кодом . То есть фрагмент кода, читаемый интерпретатором (или компилятором), который принимает код в качестве аргумента, манипулирует и возвращает результат, который затем запускается на месте.
Конечно, это много печатать, а программисты ленивы. Таким образом, мы могли бы определить DSL для выполнения списка. На самом деле мы уже используем один макрос (макрос цикла).
Lisp определяет несколько специальных синтаксических форм. Цитата ('
) указывает, что следующий токен является литералом. Квазицитат или обратный кавычка (`
) указывает, что следующий токен является литералом с escape-символами. Побеги указаны оператором запятой. Литерал '(1 2 3)
является эквивалентом [1, 2, 3]
в Python. Вы можете назначить его другой переменной или использовать на месте. Вы можете думать о `(1 2 ,x)
как о эквиваленте [1, 2, x]
в Python, где x
является переменной, определенной ранее. Эта запись списка является частью магии, которая входит в макросы. Вторая часть - это читатель Lisp, который разумно заменяет макросы на код, но это лучше всего показано ниже:
Таким образом, мы можем определить макрос с именем lcomp
(сокращение от понимания списка). Его синтаксис будет точно таким же, как у питона, который мы использовали в примере [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)))
Теперь мы можем выполнить в командной строке:
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
Довольно аккуратно, а? Теперь это не останавливается там. У вас есть механизм или кисть, если хотите. Вы можете иметь любой синтаксис, какой только захотите. Как Python или синтаксис with
в C #. Или .NET синтаксис LINQ. В конце концов, это то, что привлекает людей в Lisp - максимальная гибкость.