Clojure - 检索运行时值

时间:2017-07-20 09:47:04

标签: clojure functional-programming

我目前正在开发一个围绕pdfbox构建的pdf生成库,这是一个java库。 我本身没有问题,我只是不确定什么是clojure做某事的聪明方法。 我试图坚持使用Hiccup风格语法来生成pdf。

有类似的东西(一个非常不切实际的例子):

[:page {:title "hey"}
    [:frame {:name "frame1" :top 130}]]

我想在文档的后面检索传递给页面和框架的值(解析后的函数)。例如,下一帧:

[:frame {:bottom (+ 10 (:top "frame1"))} (str "Titre:" (:title page))]

每个函数都将其选项映射传递给另一个函数,因此第一帧的选项实际上如下所示:

{:title "hey", :name "frame1", :top 130}

但显然用户在执行这种代码时无法访问该地图。 对于page我认为使用通过绑定更新的全局Var似乎是一个好的解决方案(对任何建议开放)。但由于可能存在任何数量的帧,因此无法提前声明。因此,我的问题是:

什么样的功能,概念或做事方式最适合处理这类问题?我怎样才能让用户能够检索这些数据? (如果可能,避免所有选项的全局变量和get-in)

1 个答案:

答案 0 :(得分:1)

我对此有一个想法:为什么不为上下文使用动态范围的值,它将包含struct的调用堆栈的所有数据。然后你可以分析你的结构,在这种情况下进行评估。

我会选择这样的东西:

(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的所有调用(即使在嵌套函数中调用“;;这里是你的数据解析”部分)

例如使用此结构:

(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关于动态范围变量使用的原因的问题的答案:

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用户可以简单地分解数据生成,保持对上面定义的项的隐式依赖性定义堆栈。