clojure宏如何解析特殊符号?
How clojure macro parses special symbol?
当我用 Clojure 重新实现一个用 Scheme 编写的宏时,我遇到了麻烦。
该宏尝试将成对的测试数据加载到 all-tests
var 中供以后使用。
因为宏的参数是可变长度的并且包含特殊的未定义符号,即 =>
,我根本不知道如何像 Scheme syntax-rules 那样解析它。
方案版本:
(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
来测试我的代码。等它成功了,我会尝试做解析和加载工作。
经过反复试验,终于想出了解决办法
先用解构处理变长参数;
以后不要在宏中使用 Syntax-Quoting,即反引号`,因为如果是这样,一旦你需要取消引用 ~
参数,即 body
,由于特殊符号 =>
:
,您将收到这样的错误消息
CompilerException java.lang.RuntimeException: Unable to resolve
symbol: => in this context
以下是我的解决方案。
如果你有更好的,或者你知道 Syntax-Quote 和 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))))))
第一个宏形成一个循环,第二个宏形成一个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"]))
当我用 Clojure 重新实现一个用 Scheme 编写的宏时,我遇到了麻烦。
该宏尝试将成对的测试数据加载到 all-tests
var 中供以后使用。
因为宏的参数是可变长度的并且包含特殊的未定义符号,即 =>
,我根本不知道如何像 Scheme syntax-rules 那样解析它。
方案版本:
(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
来测试我的代码。等它成功了,我会尝试做解析和加载工作。
经过反复试验,终于想出了解决办法
先用解构处理变长参数;
以后不要在宏中使用 Syntax-Quoting,即反引号`,因为如果是这样,一旦你需要取消引用 ~
参数,即 body
,由于特殊符号 =>
:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: => in this context
以下是我的解决方案。 如果你有更好的,或者你知道 Syntax-Quote 和 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))))))
第一个宏形成一个循环,第二个宏形成一个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"]))