如何将 gensym 应用于每个特定变量
how to apply gensym to each specific variable
我想写一个宏(my-dotimes [x init end] & body)来计算从 init 到 end-1 的 x 的 body 值,增量为 1。在这里你必须再次确保避免"variable capture problem"。它应该像这样工作:
user=> (my-dotimes [x 0 4] (print x))
0123nil
我的代码是:
(defmacro my-dotimes [[x initial end] & body]
`(loop [i# ~initial]
(when (< i# ~end)
~@body
(recur (inc i#))))))
但是当我使用 macroexpand 检查它并发现:
user=> (macroexpand '(my-dotimes [x 0 4] (println x)))
(loop* [i__4548__auto__ 0] (clojure.core/when (clojure.core/<i__4548__auto__ 4)
(println x)
(recur (clojure.core/inc i__4548__auto__))))
我在想怎么改
(println x) => (clojure.core/println i__4548__auto__)
此处,您提供应绑定到计数器的符号(此处 x
),因此您不需要 使用 gensyms。
不要使用 i#
,只需引入宏的用户给你的符号即可。
当您引入新符号并且不希望它们与现有符号冲突时,您需要 gensyms。
在 Common Lisp 中,用 user-supplied 符号到 i
的当前值的绑定来包装 body 是有意义的,使用 (let ((,x ,i)) ,@body)
,因为用户的代码可能会在迭代期间更改计数器的值(这可能很糟糕)。但是这里我认为你不能直接改变变量,所以你不需要担心。
你的第二个例子是:
(defmacro for-loop [[symb ini t change] & body]
`(loop [symb# ~ini]
(if ~t
~@body
(recur ~change))))
第一个问题:当您扩展 body 时,它可能是一个或多个表单,您将 end-up 得到一个包含许多分支而不是 2 个分支的 if
表单。例如 (if test x1 x2 x3 (recur ...))
如果您的 body 包含 x1
、x2
和 x3
。您需要用 do
表达式包裹主体,使用 (do ~@body)
.
现在,情况与以前没有太大不同:您仍然有一个符号,由用户提供,您负责在宏中建立绑定。不要使用 symb#
,它会创建一个新的符号,完全不同于 symb
,只需直接使用 symb
。
例如,您可以这样做(未经测试):
(defmacro for-loop [[symb init test change] &body]
`(loop [~symb ~init]
(if ~test (do ~@body) (recur ~change))))
只要您使用宏调用者提供的符号,就不需要 gensym。当您必须在生成的代码中创建一个新变量时,您需要 gensyms,这需要一个新的符号。例如,您只对一个表达式求值一次,并且需要一个变量来保存它的值:
(defmacro dup [expr]
`(let [var# ~expr]
[var# var#]))
我想写一个宏(my-dotimes [x init end] & body)来计算从 init 到 end-1 的 x 的 body 值,增量为 1。在这里你必须再次确保避免"variable capture problem"。它应该像这样工作:
user=> (my-dotimes [x 0 4] (print x))
0123nil
我的代码是:
(defmacro my-dotimes [[x initial end] & body]
`(loop [i# ~initial]
(when (< i# ~end)
~@body
(recur (inc i#))))))
但是当我使用 macroexpand 检查它并发现:
user=> (macroexpand '(my-dotimes [x 0 4] (println x)))
(loop* [i__4548__auto__ 0] (clojure.core/when (clojure.core/<i__4548__auto__ 4)
(println x)
(recur (clojure.core/inc i__4548__auto__))))
我在想怎么改
(println x) => (clojure.core/println i__4548__auto__)
此处,您提供应绑定到计数器的符号(此处 x
),因此您不需要 使用 gensyms。
不要使用 i#
,只需引入宏的用户给你的符号即可。
当您引入新符号并且不希望它们与现有符号冲突时,您需要 gensyms。
在 Common Lisp 中,用 user-supplied 符号到 i
的当前值的绑定来包装 body 是有意义的,使用 (let ((,x ,i)) ,@body)
,因为用户的代码可能会在迭代期间更改计数器的值(这可能很糟糕)。但是这里我认为你不能直接改变变量,所以你不需要担心。
你的第二个例子是:
(defmacro for-loop [[symb ini t change] & body]
`(loop [symb# ~ini]
(if ~t
~@body
(recur ~change))))
第一个问题:当您扩展 body 时,它可能是一个或多个表单,您将 end-up 得到一个包含许多分支而不是 2 个分支的 if
表单。例如 (if test x1 x2 x3 (recur ...))
如果您的 body 包含 x1
、x2
和 x3
。您需要用 do
表达式包裹主体,使用 (do ~@body)
.
现在,情况与以前没有太大不同:您仍然有一个符号,由用户提供,您负责在宏中建立绑定。不要使用 symb#
,它会创建一个新的符号,完全不同于 symb
,只需直接使用 symb
。
例如,您可以这样做(未经测试):
(defmacro for-loop [[symb init test change] &body]
`(loop [~symb ~init]
(if ~test (do ~@body) (recur ~change))))
只要您使用宏调用者提供的符号,就不需要 gensym。当您必须在生成的代码中创建一个新变量时,您需要 gensyms,这需要一个新的符号。例如,您只对一个表达式求值一次,并且需要一个变量来保存它的值:
(defmacro dup [expr]
`(let [var# ~expr]
[var# var#]))