Неопределенная функция после макроразложения
Я изучаю 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)))
Затем:
with-html-output
расширяется в дерево, которое содержит(js-source-file ...)
- Ваш макрос раскрывается и выдает
(htm (:script ...))
форма. - Затем макролет раскрывается для создания внутреннего
(with-html-output ...)
форма. - Внутренний
with-html-output
расширен и обрабатывает(:script ...)
,
Вы должны выбрать, предпочитаете ли вы использовать макросы или функции здесь. Функции обычно не являются встроенными и могут быть легко переопределены во время выполнения. Теоретически макросы могут быть расширены и во время выполнения, но в большинстве реализаций (и конфигураций по умолчанию) вы должны перекомпилировать любую функцию, которая зависит от макроса. Вы также можете позволить макросу вызывать вспомогательную функцию.