我正在研究宏,并作为构建路由系统的项目示例。 路径(函数get / post / put / ...)可以附加到范围,当路径网址出现时会添加前缀按实际范围,例如:
;; base scope macro
(defmacro scope [url & body]
`(let [~'base-scope ~url
~'get #(str ~'base-scope %)]
~@body))
(scope "admin/"
(get "stackoverflow")) ;; OK! returns 'admin/stackoverflow'
问题是范围可以嵌套:
(scope "admin/"
(scope "api/"
(get "stackoverflow")))
我的问题是:如何在实际范围内进行查找,搜索路由前缀(范围)?我知道我可以访问& env 和& form 含义;我应该使用& env 并尝试创建一种隐藏的前缀,还是使用& form?
答案 0 :(得分:3)
一种选择是使用动态绑定 - 将一个Var最初绑定到[]
和conj
范围内的scope
- 发出binding
形式。总的来说,它应该非常简单,并且在某些方面也受到限制 - 您必须小心绑定的动态范围/生命周期。
如果您在词汇作用范围之后,可以使用tools.macro的本地宏重新定义嵌套在其他scope
次调用中的scope
次调用。 (在这种情况下,我会将实际逻辑分解为一个或两个辅助函数。)
作为一个非常简单的替代方案,您可以引入一个杰出的本地名称 - 最好使用gensym
生成 - 并在scope
的扩展中使用它,就像区分Var的动态绑定一样在上述方法中。不同之处在于,范围实际上是词法范围。至少有一个警告是,身体中的恶意用户代码可以访问或遮蔽尊贵的本地人 - 这对于gensym
几乎不太可能,但如果感觉像是破坏了事情那就不那么困难了。然而,关键的一点是,人们可能有理由对全球魔术本地人感到不舒服。#/ p>
最后,您还可以介绍新鲜的#34;本地魔术当地人"在每次扩展中,在&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
并且应创建新的本地)。