Макросы, которые пишут макросы - ошибка компиляции
Когда я компилирую следующий код, SBCL жалуется, что g! -Unit-value и g! -Unit не определены. Я не уверен, как отладить это. Насколько я могу судить, сгладить не удается.
Когда сглаживание достигает нецитированной части расщеплений, кажется, что вся часть рассматривается как атом. Это звучит правильно?
Далее используется код из книги Let over Lambda:
Пол Грэм Утилиты
(defun symb (&rest args)
(values (intern (apply #'mkstr args))))
(defun mkstr (&rest args)
(with-output-to-string (s)
(dolist (a args) (princ a s))))
(defun group (source n)
(if (zerop n) (error "zero length"))
(labels ((rec (source acc)
(let ((rest (nthcdr n source)))
(if (consp rest)
(rec rest (cons (subseq source 0 n) acc))
(nreverse (cons source acc))))))
(if source (rec source nil) nil)))
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
Пусть более лямбда-Утилиты - Глава 3
(defmacro defmacro/g! (name args &rest body)
(let ((g!-symbols (remove-duplicates
(remove-if-not #'g!-symbol-p
(flatten body)))))
`(defmacro ,name ,args
(let ,(mapcar
(lambda (g!-symbol)
`(,g!-symbol (gensym ,(subseq
(symbol-name g!-symbol)
2))))
g!-symbols)
,@body))))
(defun g!-symbol-p (symbol-to-test)
(and (symbolp symbol-to-test)
(> (length (symbol-name symbol-to-test)) 2)
(string= (symbol-name symbol-to-test)
"G!"
:start1 0
:end1 2)))
(defmacro defmacro! (name args &rest body)
(let* ((o!-symbols (remove-if-not #'o!-symbol-p args))
(g!-symbols (mapcar #'o!-symbol-to-g!-symbol o!-symbols)))
`(defmacro/g! ,name ,args
`(let ,(mapcar #'list (list ,@g!-symbols) (list ,@o!-symbols))
,(progn ,@body)))))
(defun o!-symbol-p (symbol-to-test)
(and (symbolp symbol-to-test)
(> (length (symbol-name symbol-to-test)) 2)
(string= (symbol-name symbol-to-test)
"O!"
:start1 0
:end1 2)))
(defun o!-symbol-to-g!-symbol (o!-symbol)
(symb "G!" (subseq (symbol-name o!-symbol) 2)))
Пусть над лямбда - Глава 5
(defun defunits-chaining (u units prev)
(if (member u prev)
(error "~{ ~a~^ depends on~}"
(cons u prev)))
(let ((spec (find u units :key #'car)))
(if (null spec)
(error "Unknown unit ~a" u)
(let ((chain (second spec)))
(if (listp chain)
(* (car chain)
(defunits-chaining
(second chain)
units
(cons u prev)))
chain)))))
(defmacro! defunits (quantity base-unit &rest units)
`(defmacro ,(symb 'unit-of- quantity)
(,g!-unit-value ,g!-unit)
`(* ,,g!-unit-value
,(case ,g!-unit
((,base-unit) 1)
,@(mapcar (lambda (x)
`((,(car x))
,(defunits-chaining
(car x)
(cons
`(,base-unit 1)
(group units 2))
nil)))
(group units 2))))))
2 ответа
Это довольно сложно:
Проблема: вы предполагаете, что обратные кавычки / запятые являются простыми списками.
Вы должны задать себе этот вопрос:
Что представляет собой выражение обратной кавычки / запятой?
Это список?
На самом деле полное представление не указано. Смотрите здесь: CLHS: Раздел 2.4.6.1 Примечания о Backquote
Мы используем SBCL. Видеть это:
* (setf *print-pretty* nil)
NIL
* '`(a ,b)
(SB-INT:QUASIQUOTE (A #S(SB-IMPL::COMMA :EXPR B :KIND 0)))
Таким образом, выражение запятой представляется структурой типа SB-IMPL::COMMA
, Разработчики SBCL решили, что это представление помогает, когда такие списки обратных цитат должны быть напечатаны симпатичным принтером.
Так как ваш flatten
рассматривает структуры как атомы, это не заглянет внутрь...
Но это конкретное представление SBCL. Clozure CL делает что-то еще, а LispWorks снова делает что-то еще.
Clozure CL:
? '`(a ,b)
(LIST* 'A (LIST B))
LispWorks:
CL-USER 87 > '`(a ,b)
(SYSTEM::BQ-LIST (QUOTE A) B)
отладка
Так как вы узнали, что как-то flatten
были вовлечены следующие шаги отладки:
Сначала: проследить функцию flatten
и посмотреть, с какими данными он вызывается и что он возвращает.
Так как мы не уверены, что на самом деле данные, можно INSPECT
Это.
Пример отладки с использованием SBCL:
* (defun flatten (x)
(inspect x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
STYLE-WARNING: redefining COMMON-LISP-USER::FLATTEN in DEFUN
FLATTEN
Выше звонков INSPECT
на данные аргумента. В Common Lisp Инспектор обычно представляет собой нечто, где можно интерактивно проверять структуры данных.
В качестве примера мы называем flatten
с обратным выражением:
* (flatten '`(a ,b))
The object is a proper list of length 2.
0. 0: SB-INT:QUASIQUOTE
1. 1: (A ,B)
Мы в интерактивном инспекторе. Команды теперь доступны:
> help
help for INSPECT:
Q, E - Quit the inspector.
<integer> - Inspect the numbered slot.
R - Redisplay current inspected object.
U - Move upward/backward to previous inspected object.
?, H, Help - Show this help.
<other> - Evaluate the input as an expression.
Within the inspector, the special variable SB-EXT:*INSPECTED* is bound
to the current inspected object, so that it can be referred to in
evaluated expressions.
Итак, команда 1
идет в структуру данных, здесь список.
> 1
The object is a proper list of length 2.
0. 0: A
1. 1: ,B
Пройдите дальше:
> 1
The object is a STRUCTURE-OBJECT of type SB-IMPL::COMMA.
0. EXPR: B
1. KIND: 0
Здесь Инспектор говорит нам, что объект является структурой определенного типа. Это то, что мы хотели знать.
Теперь мы оставляем Инспектора с помощью команды q
и flatten
Функция продолжается и возвращает значение:
> q
(SB-INT:QUASIQUOTE A ,B)
В случае, если кто-то все еще интересуется этим, вот мои три цента. Мое возражение против вышеуказанной модификации flatten
является то, что это может быть более естественно полезным, как это было изначально, в то время как проблема с представлениями кавычки является довольно эндемическим для defmacro/g!
, Я придумал не слишком красивую модификацию defmacro/g!
используя функции, чтобы решить, что делать. А именно, когда речь идет о реализациях не-SBCL (#-sbcl
) мы продолжаем, как и прежде, а в случае SBCL (#+sbcl
) копаемся в sb-impl::comma
структура, используйте его expr
атрибут при необходимости и использовать equalp
в remove-duplicates
, как мы сейчас имеем дело со структурами, а не символами. Вот код:
(defmacro defmacro/g! (name args &rest body)
(let ((syms (remove-duplicates
(remove-if-not #-sbcl #'g!-symbol-p
#+sbcl #'(lambda (s)
(and (sb-impl::comma-p s)
(g!-symbol-p (sb-impl::comma-expr s))))
(flatten body))
:test #-sbcl #'eql #+sbcl #'equalp)))
`(defmacro ,name ,args
(let ,(mapcar
(lambda (s)
`(#-sbcl ,s #+sbcl ,(sb-impl::comma-expr s)
(gensym ,(subseq
#-sbcl
(symbol-name s)
#+sbcl
(symbol-name (sb-impl::comma-expr s))
2))))
syms)
,@body))))
Работает с SBCL. Мне еще предстоит тщательно протестировать его на других реализациях.
Для всех, кто пытается получить defmacro! чтобы работать с SBCL, временное решение этой проблемы заключается в нахождении внутри структуры без кавычек во время процедуры сглаживания, рекурсивно сглаживая ее содержимое:
(defun flatten (x)
(labels ((flatten-recursively (x flattening-list)
(cond ((null x) flattening-list)
((eq (type-of x) 'SB-IMPL::COMMA) (flatten-recursively (sb-impl::comma-expr x) flattening-list))
((atom x) (cons x flattening-list))
(t (flatten-recursively (car x) (flatten-recursively (cdr x) flattening-list))))))
(flatten-recursively x nil)))
Но это ужасно зависит от платформы. Если я найду лучший способ, я выложу его.