Макросы выполняют преобразование кода
Макрос преобразовывает исходный код.Ленивая оценка не дает.Представьте себе, что теперь вы можете писать функции, которые преобразуют произвольный код в произвольный другой код.
Очень простые преобразования кода
Создание простых языковых конструкций также очень простопример.Рассмотрим пример открытия файла:
(with-open-file (stream file :direction :input)
(do-something stream))
против
(call-with-stream (function do-something)
file
:direction :input)
Что дает мне макрос - это немного другой синтаксис и структура кода.
Встроенный язык: расширенные итерационные конструкции
Далее рассмотрим немного другой пример:
(loop for i from 10 below 20 collect (sqr i))
против
(collect-for 10 20 (function sqr))
Мы можем определить функцию COLLECT-FOR
который делает то же самое для простого цикла и имеет переменные для начала, конца и функции шага.
Но LOOP
предоставляет новый язык.Макрос LOOP
является компилятором для этого языка.Этот компилятор может выполнять LOOP
определенные оптимизации, а также может проверять синтаксис во время компиляции для этого нового языка.Еще более мощный макрос цикла - ITERATE
.Эти мощные инструменты на уровне языка теперь могут быть написаны как библиотеки без какой-либо специальной поддержки компилятора.
Обход дерева кода в макросе и внесение изменений
Далее еще один простойпример:
(with-slots (age name) some-person
(print name)
(princ " "
(princ age))
противчто-то похожее:
(flet ((age (person) (slot-value person 'age))
(name (person) (slot-value person 'name)))
(print (name))
(princ " ")
(princ (age)))
Макрос WITH-SLOTS
вызывает полный обход вложенного дерева исходных кодов и заменяет имя переменной вызовом (SLOT-VALUE SOME-PERSON 'name)
:
(progn
(print (slot-value some-person 'name))
(princ " "
(princ (slot-value some-person 'age)))
В этомВ этом случае макрос может переписать выбранные части кода.Он понимает структуру языка Lisp и знает, что имя и возраст являются переменными.Он также понимает, что в некоторых ситуациях name
и age
не могут быть переменными и не должны быть переписаны.Это приложение так называемого Code Walker , инструмента, который может обходить деревья кода и вносить изменения в дерево кода.
Макросы могут изменять среду времени компиляции
Еще один простой пример, содержимое небольшого файла:
(defmacro oneplus (x)
(print (list 'expanding 'oneplus 'with x))
`(1+ ,x))
(defun example (a b)
(+ (oneplus a) (oneplus (* a b))))
В этом примере нас интересует не макрос ONEPLUS
, а макрос DEFMACRO
сама.
Что в этом интересного?В Лиспе вы можете иметь файл с указанным выше содержимым и использовать файл-компилятор для компиляции этого файла.
;;; Compiling file /private/tmp/test.lisp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is on
;;; Cross referencing is on
; (TOP-LEVEL-FORM 0)
; ONEPLUS
(EXPANDING ONEPLUS SOURCE A)
(EXPANDING ONEPLUS SOURCE (* A B))
; EXAMPLE
;; Processing Cross Reference Information
Итак, мы видим, что файл-компилятор расширяетсяиспользование макроса ONEPLUS
.
Что особенного в этом?В файле есть определение макроса, и в следующей форме мы уже используем этот новый макрос ONEPLUS
.Мы никогда не загружали определение макроса в Лисп.Каким-то образом компилятор знает и регистрирует определенный макрос ONEPLUS
и затем может использовать его.
Таким образом, макрос DEFMACRO
регистрирует вновь определенный макрос ONEPLUS
в среде времени компиляции, так чтоКомпилятор знает об этом макросе - без загрузки кода.Затем макрос может быть выполнен во время компиляции во время раскрытия макроса.
С помощью функции мы не можем этого сделать.Компилятор создает код для вызовов функций, но не запускает их.Но макрос можно запустить во время компиляции и добавить «знание» компилятору.Эти знания действительны во время работы компилятора и частично забыты позже.DEFMACRO
- это макрос, который выполняется во время компиляции, а затем информирует окружение во время компиляции о новом макросе.
Обратите также внимание, что макрос ONEPLUS
также запускается дважды, поскольку он используется дваждыфайл.Побочным эффектом является то, что он что-то печатает.Но ONEPLUS
может иметь и другие произвольные побочные эффекты.Например, он может проверить вложенный источник по базе правил и предупредить вас, если, например, вложенный код нарушает некоторые правила (подумайте о проверке стиля).
Это означает, что макрос, здесь DEFMACRO
, может изменять язык и его среду во время компиляции файла.В других языках компилятор может предоставлять специальные директивы компилятора, которые будут распознаваться во время компиляции.Существует множество примеров определения таких макросов, влияющих на компилятор: DEFUN
, DEFCLASS
, DEFMETHOD
, ...
Макросы могут сделать код пользователя короче
Типичным примером является макрос DEFSTRUCT
для определения записи -подобных структур данных.
(defstruct person name age salary)
Выше defstruct
макрос создает код для
- новый тип структуры
person
с тремя слотами - средства доступа к слотам для чтения и записи значений
- предикат для проверки того, принадлежит ли какой-либо объект класса
person
make-person
функция для создания объектов структуры - печатное представление
Дополнительно может:
- записать исходный код
- записать источник исходного кода (файл, буфер редактора, REPL, ...)
- перекрестная ссылка на исходный код
Исходный код для определения структуры является короткимлиния.Развернутый код намного длиннее.
Макрос DEFSTRUCT
не нуждается в доступе к мета-уровню языка для создания этих различных вещей.Он просто преобразует компактный фрагмент описательного кода в, как правило, более длинный, определяющий код, используя типичные языковые конструкции.