Неопределенная функция после макроразложения

Я изучаю Common Lisp и хочу поиграть с lisp и веб-разработкой. Моя текущая проблема проистекает из простой идеи перебрать все файлы JavaScript, которые я хочу включить. Я использую SBCL и Quicklisp для быстрого запуска. Проблема может быть связана с cl-who пакет я использую.

Итак, я объявил свой пакет и начал так:

(defpackage :0xcb0
  (:use :cl :cl-who :hunchentoot :parenscript))
(in-package :0xcb0)

Для простоты я сократил свою проблемную функцию. Итак, у меня есть это page функция:

(defun page (test)
  (with-html-output-to-string
    (*standard-output* nil :prologue nil :indent t)
    (:script
     (:script :type "text/javascript" :href test))))

Это даст желаемый результат

*(0xcb0::page "foo")

<script>
   <script type='text/javascript' href='foo'></script>
</script>

Теперь я создал макрос, который производит :script теги.

(defmacro js-source-file (filename)
  `(:script :type "text/javascript" :href ,filename)))

Это работает как ожидалось:

*(macroexpand-1 '(0XCB0::js-source-file "foo"))

(:SCRIPT :TYPE "text/javascript" :HREF "foo")

Однако, если я включу это в мой page функция:

(defun page (test)
  (with-html-output-to-string
    (*standard-output* nil :prologue nil :indent t)
    (:script
     (js-source-file "foo"))))

... это даст мне предупреждение о стиле (undefined function: :SCRIPT) при определении нового page функция. Так же page Функция выдает эту ошибку при выполнении:

*(0xcb0::page "foo")

The function :SCRIPT is undefined.
   [Condition of type UNDEFINED-FUNCTION]

Почему встроенный макрос js-source-file работает, как и ожидалось, в том, что он производит желаемый результат, но не работает, когда вызывается в другой функции?

PS Я знаю, что тема макросов в Lisp может быть довольно утомительной для начинающего, как я. Но в настоящее время я не могу понять, что это должно работать, но это не так!

2 ответа

Решение

Проблема заключается в том, что макросы расширяются немного интуитивно, по порядку, от крайности к глубине. Например:

(defmacro foobar (quux)
  (format t "Foo: ~s~%" quux))

(defmacro do-twice (form)
  `(progn
     ,form
     ,form))

(foobar (do-twice (format t "qwerty")))

Выход будет

Foo: (DO-TWICE (FORMAT T "qwerty"))

foobar никогда не видит расширение do-twice, Вы можете избежать проблемы, позвонив по телефону macroexpand себя в foobar:

(defmacro foobar (quux)
  (format t "Foo: ~s~%" (macroexpand quux)))

(foobar (do-twice (format t "qwerty")))
; => Foo: (PROGN (FORMAT T "qwerty") (FORMAT T "qwerty"))

Поскольку вы используете сторонний макрос, это, вероятно, не очень хорошее решение. Я думаю, что лучший вариант - создать разметку самостоятельно. js-source-file, Я не знаком с cl-who, но это, похоже, сработало в моем быстром тесте:

(defun js-source-file (filename stream)
  (with-html-output (stream nil :prologue nil :indent t)
    (:script :type "text/javascript" :href filename))))

(defun page (test)
  (with-output-to-string (str)
    (with-html-output (str nil :prologue nil :indent t)
      (:script
       (js-source-file test str)))))

В дополнение к другому хорошему ответу я расскажу о частном случае with-html-output, Это происходит из раздела " Синтаксис и семантика " руководства cl-who.

Во-первых, обратите внимание, что если вы макрорасширяете вызов самостоятельно, вы можете увидеть, что with-html-output налаживает macrolet привязки, такие как str, htm, ftm, esc... htm macrolet не принимает аргументов (кроме тела) и расширяется в with-html-output форма, имеющая те же параметры, имеет лексическую оболочку with-html-output макро. Чтобы исправить ваш код, вы можете изменить ваш макрос следующим образом:

(defmacro js-source-file (filename)
  `(htm (:script :type "text/javascript" :href ,filename)))

Затем:

  1. with-html-output расширяется в дерево, которое содержит (js-source-file ...)
  2. Ваш макрос раскрывается и выдает (htm (:script ...)) форма.
  3. Затем макролет раскрывается для создания внутреннего (with-html-output ...) форма.
  4. Внутренний with-html-output расширен и обрабатывает (:script ...),

Вы должны выбрать, предпочитаете ли вы использовать макросы или функции здесь. Функции обычно не являются встроенными и могут быть легко переопределены во время выполнения. Теоретически макросы могут быть расширены и во время выполнения, но в большинстве реализаций (и конфигураций по умолчанию) вы должны перекомпилировать любую функцию, которая зависит от макроса. Вы также можете позволить макросу вызывать вспомогательную функцию.

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