Clojure宏:累积值超出范围

时间:2016-05-03 22:35:21

标签: clojure macros

我正在研究宏,并作为构建路由系统的项目示例。 路径(函数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?

1 个答案:

答案 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$LocalBindingclojure.lang.Compiler$ConstantExpr),但是在这种情况下,这是一个不必要的黑客。

这并不能解决写入的整个问题 - 而不是创建新的个体"范围本地人",scope应该维护具有范围名称向量增长的本地人以保持排序。这可以通过例如完成。搜索:scope - 已标记的本地并将其隐藏(如果存在)(如果不存在,我们将在顶级scope并且应创建新的本地)。