WTF Macro?
Примеры макросистем позднего голоцена
Примеры макросистем
Макросы С/С++
#define MAX(x,y) ((x)>(y))?(x):(y)
t=MAX(i,s[i]);
t=((i)>(s[i])?(i):(s[i]);
Примеры макросистем
Макропроцессор М4
define(name [, expansion])
define('exch', '$2, $1')
=>
exch('arg1', 'arg2')
=>
arg2, arg1
Что может М4 ?
- Назначение/удаление переменных/макросов (define/undefile)
- И в стеке тоже (pushdef/popdef)
- Условное раскрытие (ifdef)
- Цикл и рекурсия (shift, forloop)
- Управление 10 виртуальными потоками вывода
- Вычисление выражений (eval)
- Операции со строками (substr, len, regexp, patsubst)
- Подстановка содержимого файлов (include)
- Выполнение shell-команд (syscmd)
- Коссвенные вызовы макросов (indir), в т.ч. встроенных (buildin)
- Трассировка раскрытия (traceon/traceoff)
Для чего используется М4 ?
- Генерация кода из декларативной спецификации
- Генерация SQL во время компиляции
- Документирование
- Администрирование (конфиги)
В сравнении с макросами Lisp
Макросы в лисп
Простой маленький пример
(defparameter *dbg-enable* t)
(defparameter *dbg-indent* 1)
(defun dbgout (out)
(when *dbg-enable*
(format t (format nil "~~%~~~AT~~A" *dbg-indent*) out)))
(defmacro dbg (frmt &rest params)
`(dbgout (format nil ,frmt ,@params)))
(macroexpand-1 '(dbg "~A~A~{~A~^,~}" "this is debug message " "15:57:02 " (list 1 2 3 4)))
=> (DBGOUT
(FORMAT NIL "~A~A~{~A~^,~}" "this is debug message " "15:57:02 "
(LIST 1 2 3 4))), T
(dbg "~A~A~{~A~^,~}" "this is debug message " "15:57:02 " (list 1 2 3 4))
=> this is debug message 15:57:02 1,2,3,4
Почему Lisp макросы лучше?
Почему Lisp макросы лучше?
- Вся мощь языка (и любых) библиотек - в compile-time
- Высокоуровневая работа с AST (в противовес работы с токенами)
- Можно делать CodeWalkers
- Можно расширять компилятор
- Можно ходить по стадиям компиляции вперед и назад
Простые примеры
Compile-time факториал
(defmacro !1 (x)
(if (= x 1)
1
`(* ,x (!1 ,(1- x)))))
(macroexpand-all '(!1 5))
(SB-CLTL2:MACROEXPAND-ALL '(!1 5))
=> (* 5 (* 4 (* 3 (* 2 1))))
Более сложный пример
Макрос, который определяет макрос
(defmacro defsynonym (old-name new-name)
"Define OLD-NAME to be equivalent to NEW-NAME
when used in the first position of a Lisp form."
`(defmacro, new-name (&rest args)
`(,',old-name ,@args)))
=> DEFSYNONYM
(macroexpand-1 '
(defsynonym cons make-pair))
=>(DEFMACRO MAKE-PAIR (&REST ARGS) `(CONS ,@ARGS)), T
(defsynonym cons make-pair)
=>MAKE-PAIR
(make-pair 'a 'b)
=> (A . B)
Еще более сложный пример
Преобразование AST
(defmacro -> (forms)
(let ((forms (reverse forms)))
(if (null (cdr forms))
(car forms)
(let ((base (car forms)))
(unless (listp base)
(setf base (list base)))
(list* (car base)
(reverse (cdr forms))
(cdr base))))))
(macroexpand-1 '(-> ((handler/site app-routes)
(wrap-resource "web")
(wrap-file-info)
(wrap-params)
(wrap-keyword-params))))
=> (WRAP-KEYWORD-PARAMS
(WRAP-PARAMS
(WRAP-FILE-INFO
(WRAP-RESOURCE
(HANDLER/SITE APP-ROUTES)
"web")))), T
Как это работает?
Стадии вычисления
- read
- macro expansion
- compilation
- loading
- execute
Цитирование и квазицитирование
(defmacro av (x y) `(/ (+ ,x ,y) 2))
` — символ квази-цитирования;
квази-цитирование означает, что мы берем готовое AST,
и в нем часть узло заменяем на другие выражения;
,x — это как раз спецификация того, что на данное
место в квази-цитируемом выражении следует подставить x
Гигиена и анафорические макросы
Гигиена вручную
(defmacro swap (pl1 pl2)
"Macro to swap two places"
(let ((temp1-name (gensym))
(temp2-name (gensym)))
`(let ((,temp1-name ,pl1)
(,temp2-name ,pl2))
(setf ,pl1 ,temp2-name)
(setf ,pl2 ,temp1-name))))
(defparameter *var1* 123)
(defparameter *var2* 456)
(swap *var1* *var2*)
*var1*
=>456
*var1*
=>123
Анафорические макросы
(defmacro aif (var then &optional else)
`(let ((it ,var))
(if it ,then ,else)))
Once-only
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g
,,n)))
,(let (,@(loop for n in names for g in gensyms collect `(,n
,g)))
,@body)))))
(macroexpand-1 '(once-only (start end)
(print 123)))
=>
(LET ((#:G858 (GENSYM)) (#:G859 (GENSYM)))
`(LET ((,#:G858 ,START) (,#:G859 ,END))
,(LET ((START #:G858) (END #:G859))
(PRINT 123)))), T
(defmacro do-primes ((var start end) &body body)
(once-only (start end)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
((> ,var ,end))
,@body)))
(macroexpand-1 '(do-primes (variable 1 20)
(print variable)))
=>
(LET ((#:G856 1) (#:G857 20))
(DO ((VARIABLE (NEXT-PRIME #:G856) (NEXT-PRIME (1+ VARIABLE))))
((> VARIABLE #:G857))
(PRINT VARIABLE))), T
Десять (± 100) причин избегать макросов
- Трудно придумать задачу, которую нельзя решить без макросов
- Никто не отменял обычную кодогенерацию
- Как буравить глазами стектрейс?
- IDE не умеет в макросы
- Вы не одни в команде
- Макросы - причина странных ошибок
- Вы изобретаете DSL
Почему макросы? Почему Lisp?
В твоем языке есть макросы, но...
- Нет строковой интерполяции - у тебя есть строковая интерполяция благодаря макросам
- Нет try-with-resources/using - у тебя есть using благодаря макросам
- Нет yield return - у тебя есть yield return благодаря макросам
- Нет нормального async/await, но есть дурацкие коллбеки - у тебя есть async/await благодаря макросам.
- Нет возможности определить тип по твоему специфичному шаблону (и наследование не решает проблему) - у тебя есть такая возможность благодаря макросам.
- Нет паттерн матчинга - есть паттерн матчинг благодаря макросам.
- В языке нет статической проверки типов? Можно сделать поддержку статической проверки типов с крутым автовыводом.
- Ты хочешь описывать решения своих задач максимально выразительно?
Да здравствуют макросы.
deck
By Rigidus Rigidus
deck
- 2,213