#macros #clojure
#макросы #clojure
Вопрос:
Я недавно начал изучать clojure и читаю The Joy of Clojure, чтобы разобраться с этим. У меня есть вопрос относительно сегмента кода в главе «Макросы» (8) на странице 166
(defmacro domain [name amp; body]
`{:tag :domain, ;`
:attrs {:name (str '~name)}, ;'
:content [~@body]})
Насколько я понимаю, body
это структура, подобная последовательности, со всеми аргументами, кроме первого. Если да, то почему в третьей строке мы снимаем кавычки ( ~@
) и снова помещаем значения в вектор. Почему бы просто не сделать ~body
вместо [~@body]
? В чем разница?
Извините, но мне действительно трудно понять всю суть макросов (из python).
Редактировать: После небольшого эксперимента я обнаружил, что это работает,
(defmacro domain2 [name amp; body]
`{:tag :domain, ;`
:attrs {:name (str '~name)}, ;'
:content '~body})
и наряду с результатами, которые я получил из ответа Джоста, я думаю, что я знаю, что здесь происходит. body
представляется в виде списка, и поэтому, если я не поставлю '
перед ~body
, clojure попытается оценить его.
user=> (domain "sh" 1 2 3)
{:content [1 2 3], :attrs {:name "sh"}, :tag :domain}
user=> (domain2 "sh" 1 2 3)
{:content (1 2 3), :attrs {:name "sh"}, :tag :domain}
Комментарии:
1. Кроме того, можно опубликовать фрагмент кода из книги, подобный этому, здесь, верно?
2. Просто небольшая вещь, на которую следует обратить внимание, это сращивание без кавычек, а не срезание без кавычек
Ответ №1:
Я думаю, что ответ заключается в намерении этого макроса.
При быстром просмотре упомянутой страницы кажется, что идея состоит в том, чтобы создать структуру данных для домена с использованием карты. Выбранная структура идентична той, которая используется clojure.xml библиотека.
Это правда, что функция emit выдаст аналогичные результаты как с вашим кодом, так и с кодом из книги, но поскольку функция выполняет синтаксический анализ из clojure.xml создает карту с содержимым в векторе, лучше сделать то же самое здесь, чтобы не нарушать другой код, полагающийся на ту же структуру. Возможно, было бы хорошей идеей также использовать здесь struct, чтобы соответствовать clojure.xml , как сейчас, например (content (domain ...))
, не работает.
Думая о структурах данных в целом, я нахожу хорошей идеей использовать здесь индексированную последовательность, подобную vector, поскольку это позволяет, например, ((:content domain-item) 1)
, получить доступ ко второму элементу содержимого. Не говоря уже о подпоследовательностях и т.д.
Комментарии:
1. Спасибо Вернери, это имеет большой смысл 🙂
Ответ №2:
Вы совершенно правы; ~body — это уже последовательность, поэтому, если нет необходимости гарантировать, что :content — это вектор (а не просто что-то, что может быть выделено), выражение [~@body] может быть заменено просто на ~body .
Комментарии:
1. Спасибо, однако, если я заменю
[~@body]
на~body
и сделаю(domain "sh" 1 2 3)
, я получуjava.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)
. Вызовmacroexpand
также выдает ту же ошибку.2. Вы должны вызвать macroexpand для выражения, заключенного в кавычки, иначе оно просто вычислит, прежде чем macroexpand доберется до него. Макрорасширение — это функция.