Я читаю книгу Лисп, написанную Уинстоном. Кроме того, я использую SBCL, Emacs и Slime.
В главе 8 (о макросах) в книге есть следующее упражнение:
Problem 8-7: A stack is a learnly ordered set of things that can be accessed using push and pop operations. OUR-PUSH adds a new item to the top of the stack, while OUR-POP removes the item on top of the stack. A list can be used to represent a stack, with the first element corresponding to the item on top. Define OUR-PUSH and OUR-POP as macros. OUR-PUSH takes two arguments, the item to be pushed and the name of a variable whose value it the list representing the stack. The value returned is the enlarged list. OUR-POP takes a single element, the name of the variable whose value is in the list. The value returned is the item popped. In both cases the value of the variable is changed to reflect the new state of the stack.
Я правильно понял функцию pop
. Однако у меня возникла проблема с повторной реализацией push
. Это лист ответов:
(defmacro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
Он работает так, как ожидалось. Мой первоначальный ответ был похож:
(defmacro our-push (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,stack)))
Но это генерирует случайный вложение списка, такой как:
CL-USER> (defparameter so-stack '(3 4 5 6))
SO-STACK
CL-USER> so-stack
(3 4 5 6)
CL-USER> (our-push 2 so-stack)
(2 (3 4 5 6))
Тогда я подумал: «О, это должно быть та ситуация, в которой вы используете ,@
». Потому что ,@
создает поведение сплайсинга. Таким образом, я сделал:
(defmacro our-push (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,@stack)))
Тем не менее, это не работает. Более того, Slime выдает сообщение об ошибке, которое кажется мне странным:
The value
SO-STACK
is not of type
LIST
Специально потому что:
CL-USER> (listp so-stack)
T
Есть ли способ использовать ,@
в этой ситуации?
Почему слизь указывает, что переменная so-stack
является списком нет, когда на самом деле это один?
Пожалуйста, замените изображение текстом - это сделает ваш вопрос доступным для поиска и, следовательно, более полезным. Спасибо.
@sds, это не обычный pdf. Это отсканированный pdf. Страницы книги ведут себя как изображения. Таким образом, мне нужно было бы вручную перепечатать все.
@PedroDelfino: c'est la vie.
Это не так много, чтобы напечатать, не так ли?
@ Манфред, не так уж и много. Иногда я чувствую себя ленивым, хотя, ха-ха. Я уже заменил картинку текстом.
Вы находитесь на уровне износа. Когда макрос расширяется с помощью его функции макроса, вы работаете с исходный код.
Исходный код это список:
(my-push thing foos)
я. е. список с тремя символы с именами my-push
, thing
и foos
.
Функция макроса берет этот список и преобразует его в другой список, в вашем случае:
(setf foos (list thing foos))
Затем это компилируется дальше (больше расширений макроса, наконец, компиляция в машинный код).
Если вы попытаетесь соединить foos
, это невозможно, потому что символ — это не список.
Функция макроса никогда не видит, что означают эти символы. Он завершается еще до того, как форма компилируется в машинный код.
Это лист ответов:
(defmacro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
Как вы говорите, ваш первоначальный ответ был похож:
(defmacro our-push (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,stack)))
И, по сути, это проблема. cons
похож на тюбик клея — он приклеивает значение к существующему списку. list
— это просто большой мешок, в который я могу положить вещи.
(list 5 '(1 2 3 4)) ; puts 5 and '(1 2 3 4) into a single list, side by side
; => (5 (1 2 3 4))
(cons 5 '(1 2 3 4)) ; attaches 5 to list '(1 2 3 4)
; => (5 1 2 3 4)
Обычно вы используете список, но не в этом случае.
Затем вы попытались соединить значения вместе, используя @
. Это не сработало.
(defmacro our-push-splice (item-to-be-pushed stack)
`(setf ,stack (list ,item-to-be-pushed ,@stack)))
Лучший способ выяснить, почему это не сработало, — использовать macro-expand-1. Это показывает, что макрос делает на самом деле, а не то, что он должен делать.
CL-USER> (defparameter stack '(1 2 3 4))
STACK
CL-USER> (macroexpand-1 '(our-push 5 stack))
(SETF STACK (LIST 5 STACK))
T
CL-USER> (macroexpand-1 '(our-push-splice 5 stack))
(SETF STACK (LIST 5 . STACK))
T
CL-USER> (our-push 5 stack)
(5 (1 2 3 4))
CL-USER> (our-push-splice 5 stack)
; Evaluation aborted on #<TYPE-ERROR expected-type: LIST datum: STACK>.
Таким образом, попытка сплайсинга не удалась, так как ожидалось, что стек будет сплайсирован при оценке макроса. Вставляется имя, не более того.
Ответ Сванте правильный: макросы - это функции, которые преобразуют исходный код. Но иногда полезно увидеть это, заставив макросы печатать то, что они делают. Вот простой хак для этого (гораздо более общий хак от кого-то другого — здесь).
(defmacro define-traced-macro (name args &body forms)
;; Define a traced macro. This probably misses edge cases
`(progn
(defmacro ,name ,args ,@forms)
(let ((m (macro-function ',name)))
(setf (macro-function ',name)
(lambda (form environment)
;; Do it like this so we get the source form even if
;; the macro fails
(let ((*print-pretty* t))
(format *trace-output* "~&~S~%" form))
(let ((*print-pretty* t)
(expansion (funcall m form environment)))
(format *trace-output* "~& -> ~S~%" expansion)
expansion))))
',name))
И теперь, если вы определите макрос с помощью define-traced-macro
, а не defmacro
, он будет отслеживать его расширение:
(define-traced-macro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
> (let ((x '(1 2)))
(book-our-push 3 x)
x)
(book-our-push 3 x)
-> (setq x (cons 3 x))
(3 1 2)
Это должно убедительно показать, почему ваша версия не может работать.
Кроме того, когда у вас есть define-traced-macro
, вы можете использовать его, чтобы переопределить себя (чтобы сделать это серьезно, вам нужно определить его, жестко встроив его собственное расширение в исходный код):
> (define-traced-macro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
(define-traced-macro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
-> (progn
(defmacro book-our-push (item-to-be-pushed stack)
`(setq ,stack (cons ,item-to-be-pushed ,stack)))
(let ((m (macro-function 'book-our-push)))
(setf (macro-function 'book-our-push)
(lambda (form environment)
(let ((*print-pretty* t))
(format *trace-output* "~&~S~%" form))
(let ((*print-pretty* t)
(expansion (funcall m form environment)))
(format *trace-output* "~& -> ~S~%" expansion)
expansion))))
'book-our-push)
book-our-push
Это забавно, если вас это развлекает.
Символ
so-stack
не является списком.(listp 'so-stack)
->NIL