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