我对此有一个想法:为什么不使用动态范围值作为上下文,它将包含结构调用堆栈的所有数据。然后你可以分析你的结构,在这个上下文中进行评估。
我会选择这样的:
(def ^:dynamic *context* ())
(defn lookup-context [& kv-pairs]
(some #(when (every? (fn [[k v]] (= (k %) v)) kv-pairs) %)
*context*))
(defmacro with-context [data]
(let [items (tree-seq #(and (vector? %) (#{:frame :page} (first %)))
#(nthnext % 2)
data)
ctx-items (reverse (map second items))
let-bindings (zipmap ctx-items (repeatedly gensym))
data (clojure.walk/postwalk-replace let-bindings data)]
(reduce (fn [acc [itm sym]]
`(let [~sym ~itm]
(binding [*context* (cons ~sym *context*)] ~acc)))
data ;; here goes your data parsing
let-bindings)))
所以这个宏建立了级联动态绑定,以及在其中对lookup-context 的所有调用(即使在从“;;here go your data parsing”部分调用的嵌套函数中)
以这种结构为例:
(with-context [:page
{:name "page0" :val 1000}
[:frame
{:name "frame0" :val 10}
[:frame {:name "frame1" :val (+ (:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]]])
它将被扩展为:
(let [G__8644 {:name "page0", :val 1000}]
(binding [*context* (cons G__8644 *context*)]
(let [G__8643 {:name "frame0", :val 10}]
(binding [*context* (cons G__8643 *context*)]
(let [G__8642 {:name "frame1",
:val
(+
(:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]
(binding [*context* (cons G__8642 *context*)]
[:page G__8644 [:frame G__8643 [:frame G__8642]]]))))))
给你你需要的结果,我猜
更新
作为对@amalloy 关于动态范围 var 使用原因的问题的回答:
user> (defn item-factory []
[:frame {:name "frame2" :val (+ (:val (lookup-context [:name "frame1"]))
(:val (lookup-context [:name "page0"])))}])
#'user/item-factory
user>
(with-context [:page
{:name "page0" :val 1000}
[:frame
{:name "frame0" :val 10}
[:frame {:name "frame1" :val (+ (:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]
(item-factory)]])
;;=> [:page {:name "page0", :val 1000}
;; [:frame {:name "frame0", :val 10}
;; [:frame {:name "frame1", :val 1010}]
;; [:frame {:name "frame2", :val 2010}]]]
如您所见,在数据处理内部调用的 item-factory 函数也是上下文感知的,这意味着 lib 用户可以简单地分解数据生成,保持对定义上方项目的隐式依赖堆栈。