【问题标题】:Clojure Macros: Accumulate value over scopeClojure 宏:在范围内累积价值
【发布时间】:2016-05-03 22:35:20
【问题描述】:

我正在研究宏并作为构建路线系统的项目示例。 route(函数 get/post/put/...)可以附加到 scope,当它发生时,route url 是前缀以实际作用域为例:

;; 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?

【问题讨论】:

    标签: clojure macros


    【解决方案1】:

    一种选择是使用动态绑定——以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$LocalBindingclojure.lang.Compiler$ConstantExpr),但在这种情况下这是不必要的破解。

    这并不能解决所写的整个问题——scope 应该维护具有不断增长的范围名称向量的局部变量,而不是创建新的单个“作用域局部变量”以保持顺序。这可以做到,例如通过搜索带有:scope 标记的本地并隐藏它(如果不存在,我们在顶级scope 中,应该创建一个新的本地)。

    【讨论】:

    • 注意。 :scope 如果在库中使用类似上述“本地魔术本地人”的方法,则应为关键字命名。
    • 非常感谢,您的回答非常详尽!我使用 &env 并定义了一个 local 尝试了与您上一个示例类似的操作,但我没有意识到如何获取其他范围,感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-29
    • 2012-09-24
    • 1970-01-01
    • 2021-04-02
    • 1970-01-01
    • 2017-09-03
    相关资源
    最近更新 更多