Как clojure макрос разбирает спецсимвол?
Когда я заново внедряю макрос, написанный на Scheme с Clojure, я попадаю в беду. Макрос пытается загрузить пары тестовых данных в all-tests
Вар для последующего использования.
Потому что аргументы для макроса имеют переменную длину и содержат специальный неопределенный символ, т.е. =>
Я просто не знаю, как его анализировать, как это делают правила синтаксиса Scheme.
Версия схемы:
(define all-tests '())
;;; load tests into all-tests
(define-syntax add-tests-with-string-output
(syntax-rules (=>)
[(_ test-name [expr => output-string] ...)
(set! all-tests
(cons
'(test-name [expr string output-string] ...)
all-tests))]))
(add-tests-with-string-output "integers"
[0 => "0\n"]
[1 => "1\n"]
[-1 => "-1\n"]
[10 => "10\n"]
[-10 => "-10\n"]
[2736 => "2736\n"]
[-2736 => "-2736\n"]
[536870911 => "536870911\n"]
[-536870912 => "-536870912\n"]
)
Моя текущая неудачная версия Clojure:
(def all-tests (atom '()))
(defmacro add-tests-with-string-output
[test-name & body]
`(loop [bds# (list body)]
(when-not (empty? bds#)
(println (first bds#))
(recur (rest bds#)))))
Ps: я использую println
чтобы проверить мой код прямо сейчас. Когда это сработает, я постараюсь выполнить разбор и загрузку.
2 ответа
Первый макрос образует цикл, а второй - doseq
(так проще). Оба должны вести себя одинаково. Также я считаю хорошей идеей извлекать как можно больше логики из макросов во вспомогательные функции. Функции легче отлаживать, тестировать и писать. Если бы макрос был немного сложнее, я бы оставил в нем еще меньше логики.
(def all-tests (atom '()))
(defn add-test [test-name expr output-string]
(swap! all-tests #(cons (list test-name [expr output-string]) %)))
(defmacro add-tests-with-string-output
[test-name & body]
;`(loop [bds# '(~@body)]
`(loop [bds# '~body] ; edit
(when-not (empty? bds#)
(let [bd# (first bds#)
expr# (first bd#)
output-string# (last bd#)]
(add-test ~test-name expr# output-string#)
(recur (rest bds#))
))))
(defmacro add-tests-with-string-output2
[test-name & body]
;`(doseq [bd# '(~@body)]
`(doseq [bd# '~body] ; edit
(let [expr# (first bd#)
output-string# (last bd#)]
(add-test ~test-name expr# output-string#))))
user=> (add-tests-with-string-output "test1" [0 => "0\n"] [1 => "1\n"])
nil
user=> (add-tests-with-string-output2 "test2" [0 => "0\n"] [1 => "1\n"])
nil
user=> @all-tests
(("test2" [1 "1\n"]) ("test2" [0 "0\n"]) ("test1" [1 "1\n"]) ("test1" [0 "0\n"]))
После проб и ошибок наконец-то выясняю, как ее решить.
Сначала используйте Destructuring для решения аргументов переменной длины; позже не используйте синтаксические кавычки, т.е. обратные кавычки `, внутри макроса, потому что если это так, однажды вам нужно снять кавычки ~
аргумент, т.е. body
, вы получите сообщение об ошибке, как это из-за специального символа =>
:
CompilerException java.lang.RuntimeException: невозможно разрешить символ: => в этом контексте
Ниже мое решение. Если вам лучше или вы знаете причину, по которой синтаксические цитаты и Unquote работают неправильно, пожалуйста, дайте мне знать.
;;; load tests into all-tests
(def all-tests (atom '()))
(defmacro add-tests-with-string-output
[test-name & body]
(loop [bds body, tests '()]
(if (empty? bds)
(do
(swap! all-tests #(cons (cons test-name tests) %))
nil)
(let [pair (first bds),
input (first pair)
output (last pair)]
(recur (rest bds) (cons (list input ''string output) tests))))))