Оценка макро аргументов в clojure
Я пытаюсь перевести следующий макрос из Лиспа в clojure:
(defmacro tag (name atts &body body)
`(progn (print-tag ',name
(list ,@(mapcar (lambda (x)
`(cons ',(car x) ,(cdr x)))
(pairs atts)))
nil)
,@body
(print-tag ',name nil t)))
Но я продолжаю зацикливаться на умениях, требующих еще одного уровня оценки. Например, для оценки t# необходимо следующее:
(defmacro tag [tname atts & body]
`(do (print-tag '~tname '[~@(map (fn [[h# t#]] [h# t#]) (pair atts))] nil)
~@body
(print-tag '~tname nil true)))
Как он производит такие вещи, как:
(tag mytag [color 'blue size 'big])
<mytag color="(quote blue)" size="(quote big)"><\mytag>
Где я хочу, чтобы атрибут оценивался. Если я использую "(eval t#)" в вышеупомянутом, я падаю на проблемы, подобные этой:
(defn mytag [col] (tag mytag [colour col]))
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:1)
Какие-либо предложения?
Почему кажется, что в Clojure происходит еще один уровень оценки?
Определения вспомогательных функций:
;note doesn't handle nils because I'm dumb
(defn pair [init-lst]
(loop [lst init-lst item nil pairs []]
(if (empty? lst)
pairs
(if item
(recur (rest lst) nil (conj pairs [item (first lst)]))
(recur (rest lst) (first lst) pairs)))))
(defn print-tag [name alst closing]
(print "<")
(when closing
(print "\\"))
(print name)
(doall
(map (fn [[h t]]
(printf " %s=\"%s\"" h t))
alst))
(print ">"))
(По какой-то причине я не выполнял парную функцию так же, как в книге, что означает, что она неправильно обрабатывает нули)
3 ответа
Ваше Clojure определение tag
цитирует все в карте атрибутов, в то время как общая версия lisp цитирует только имена. Это непосредственный источник ваших проблем - если вы просто бросили '
перед вашим вектором / картой, а затем возился с map
процитировать первый элемент, вы, вероятно, будет в порядке.
Однако, хотя портирование может быть хорошим упражнением, этот код не написан в Clojure Way: печать - неприятный неприятный побочный эффект, который затрудняет использование print-tag для выполнения чего-либо значимого; возврат строки вместо этого был бы намного лучше.
(defmacro tag [name attrs & body]
`(str "<"
(clojure.string/join " "
['~name
~@(for [[name val] (partition 2 attrs)]
`(str '~name "=\"" ~val "\""))])
">"
~@body
"</" '~name ">"))
user> (tag head [foo (+ 1 2)] "TEST" (tag sample []))
"<head foo=\"3\">TEST<sample></sample></head>"
Конечно, поскольку порядок не имеет значения, использование атрибутов вместо карты лучше, чем вектор. Это также означает, что вы можете отказаться (partition 2...)
, так как последовательный вид карты уже в виде пар.
И как только мы продвинулись так далеко, оказалось, что уже есть много способов представить XML как структуры данных Clojure, поэтому я бы никогда не использовал свой приведенный выше код в реальном приложении. Если вы хотите сделать XML по-настоящему, проверьте любой из Hiccup, prxml или data.xml.
Возможно, я что-то упустил, но есть ли конкретная причина, по которой вы указали синий и большой, но не цвет и размер, вы также указали в макросе вектор, чтобы содержимое внутри него не оценивалось, если вы отбросите кавычку вокруг вектора, а также Цвет цитаты и большой вы получаете то, что вы хотите,
(defmacro tag [tname atts & body]
`(do (print-tag '~tname [~@(map (fn [[h# t#]] [h# t#]) (pair atts))] nil)
~@body
(print-tag '~tname nil true)))
(tag mytag ['color 'blue 'size 'big])
<mytag color="blue" size="big"><\mytag>nil
Просто для записи вместо символов использование ключевых слов было бы более идиоматическим закрытием для этого.
Ради полноты, то, что я хотел, оказалось:
(defmacro tag [tname atts & body]
`(do (print-tag '~tname [~@(map (fn [[h# t#]] [`'~h# t#]) (pair atts))] nil)
~@body
(print-tag '~tname nil true)))