一种选择是使用动态绑定——以scope-emitted binding 形式将 Var 初始绑定到 [] 和 conj 范围。总体而言,它应该非常简单,并且在某些方面也受到限制——您必须小心绑定的动态范围/生命周期。
如果您追求的是词法作用域,您可以使用tools.macro 的本地宏来重新定义嵌套在其他scope 调用中的scope 调用。 (在这种情况下,我会将尽可能多的实际逻辑分解到一个或两个辅助函数中。)
作为一个非常简单的替代方案,您可以引入一个可分辨的本地名称——最好使用gensym 生成——并在scope 的扩展中使用它,就像上述方法中的可分辨 Var 的动态绑定一样。不同之处在于,这里的范围实际上是词法范围的。至少有一个警告,主体中的恶意用户代码可以访问或隐藏杰出的本地 - 使用gensym 不太可能,但如果你想破坏东西,这并不难。不过,关键是人们可能有理由对“全球魔法当地人”感到不舒服。
最后,您还可以在每个扩展包中引入新鲜的“本地魔法当地人”,并在&env 中搜索它们。下面是一个简单的概念证明,使用元数据来标记神奇的局部变量:
(defmacro scope [sname & body]
(let [scopes (filterv #(contains? (meta %) :scope) (keys &env))
maybe-printout (if (seq scopes)
[(list* `println scopes)])]
`(let [~(with-meta (gensym "scope__") {:scope true}) '~sname]
~@maybe-printout
~@body)))
在 REPL,
(scope :foo
(scope :bar
(scope :quux
(scope :xyz))))
打印出来
:foo
:foo :bar
:foo :bar :quux
返回nil。
(set! *print-meta* true) 和 macroexpand 调用显示上述扩展为
(let* [^{:scope true} scope__16668 (quote :foo)]
^{:line 4, :column 6}
(scope :bar
^{:line 5, :column 8}
(scope :quux
^{:line 6, :column 10}
(scope :xyz))))
当然只有:scope 元数据是有趣的。如果可以假设作用域名称必须是编译时常量,甚至可以在编译时检索它们的实际值(参见clojure.lang.Compiler$LocalBinding 和clojure.lang.Compiler$ConstantExpr),但在这种情况下这是不必要的破解。
这并不能解决所写的整个问题——scope 应该维护具有不断增长的范围名称向量的局部变量,而不是创建新的单个“作用域局部变量”以保持顺序。这可以做到,例如通过搜索带有:scope 标记的本地并隐藏它(如果不存在,我们在顶级scope 中,应该创建一个新的本地)。