Почему использование `,@` не работает, как я ожидал, в этом общем макросе lisp? Почему Slime возвращает это сообщение об ошибке?

Я читаю книгу Лисп, написанную Уинстоном. Кроме того, я использую 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 является списком нет, когда на самом деле это один?

Символ so-stack не является списком. (listp 'so-stack) -> NIL

Rainer Joswig 07.04.2022 22:11

Пожалуйста, замените изображение текстом - это сделает ваш вопрос доступным для поиска и, следовательно, более полезным. Спасибо.

sds 07.04.2022 22:29

@sds, это не обычный pdf. Это отсканированный pdf. Страницы книги ведут себя как изображения. Таким образом, мне нужно было бы вручную перепечатать все.

Pedro Delfino 08.04.2022 00:39

@PedroDelfino: c'est la vie.

sds 08.04.2022 01:02

Это не так много, чтобы напечатать, не так ли?

Manfred 08.04.2022 09:47

@ Манфред, не так уж и много. Иногда я чувствую себя ленивым, хотя, ха-ха. Я уже заменил картинку текстом.

Pedro Delfino 08.04.2022 16:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
63
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вы находитесь на уровне износа. Когда макрос расширяется с помощью его функции макроса, вы работаете с исходный код.

Исходный код это список:

(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

Это забавно, если вас это развлекает.

Другие вопросы по теме