【问题标题】:Clojure - Retrieving runtime valuesClojure - 检索运行时值
【发布时间】:2017-07-20 09:47:03
【问题描述】:

我目前正在开发一个围绕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 似乎是一个不错的解决方案(欢迎任何建议)。但是由于可能有任意数量的帧,它们不能更早地声明。因此,我的问题是:

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

【问题讨论】:

    标签: clojure functional-programming


    【解决方案1】:

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

    我会选择这样的:

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

    【讨论】:

    • 我不明白为什么应该使用动态变量而不是普通的词法变量。此外,您的宏看起来需要 :page:frame 硬编码,而我认为还需要针对页面形状执行其他类型的查询。
    • 动态 var 将允许从内部函数调用查找上下文。我看不出它是如何用词法 var 完成的(也许是全局原子,但你仍然需要以某种方式清理它)。关于其他类型的处理:这只是一个概念证明。人们必须制造更多的管道才能使其生产就绪
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 1970-01-01
    • 2012-08-29
    • 2020-08-23
    • 2019-11-25
    • 2016-01-17
    相关资源
    最近更新 更多