Clojure中的动态范围界定?

Clojure中的动态范围界定?,clojure,Clojure,我正在寻找一种惯用的方法来获取Clojure中的动态作用域变量(或类似效果),以便在模板等中使用 下面是一个使用查找表将标记属性从一些非HTML格式转换为HTML的示例问题,其中该表需要访问从其他地方提供的一组变量: (def *attr-table* ; Key: [attr-key tag-name] or [boolean-function] ; Value: [attr-key attr-value] (empty array to ignore) ; Context: V

我正在寻找一种惯用的方法来获取Clojure中的动态作用域变量(或类似效果),以便在模板等中使用

下面是一个使用查找表将标记属性从一些非HTML格式转换为HTML的示例问题,其中该表需要访问从其他地方提供的一组变量:

(def *attr-table* 
  ; Key: [attr-key tag-name] or [boolean-function]
  ; Value: [attr-key attr-value] (empty array to ignore)
  ; Context: Variables "tagname", "akey", "aval"
  '(
        ; translate :LINK attribute in <a> to :href
     [:LINK "a"]    [:href aval]
        ; translate :LINK attribute in <img> to :src
     [:LINK "img"]  [:src aval]
        ; throw exception if :LINK attribute in any other tag
     [:LINK]        (throw (RuntimeException. (str "No match for " tagname)))
     ; ... more rules
        ; ignore string keys, used for internal bookkeeping
     [(string? akey)] []  )) ; ignore
这很好,因为查找逻辑独立于表,所以它可以与其他表和不同的变量一起重用。(当然,加上通用表方法大约是我在一个巨大的时间内“手工”翻译时代码大小的四分之一。)

这不是很好,因为我需要将每个变量声明为全局变量,以便绑定工作

下面是另一种使用“半宏”的方法,该函数的语法引用了返回值,不需要全局变量:

(defn attr-table [tagname akey aval]
  `(
     [:LINK "a"]   [:href ~aval]
     [:LINK "img"] [:src ~aval]
     [:LINK]       (throw (RuntimeException. (str "No match for " ~tagname)))
     ; ... more rules     
     [(string? ~akey)]        [] )))
只需对代码的其余部分进行几处更改:

In rule-match? The syntax-quoted function call is no longer a list:
- (list? rule-val) (eval rule-val) 
+ (seq? rule-val) (eval rule-val) 

In html-attr:
- (binding [tagname tagname akey k aval v]
- (or (rule-lookup [k tagname] *attr-table*) kv)))
+ (or (rule-lookup [k tagname] (attr-table tagname k v)) kv)))
在没有全局变量的情况下,我们得到了相同的结果。(并且没有动态范围。)

如果没有Clojure的
绑定所需的全局变量,是否还有其他方法可以传递其他地方声明的变量绑定集

有没有一种惯用的方法,比如 还是

更新

我可能把它弄得太复杂了,这里是我假设的上述功能性更强的实现——没有全局、没有评估,也没有动态范围:

(defn attr-table [akey aval]
  (list
    [:LINK "a"]   [:href aval]
    [:LINK "img"] [:src aval]
    [:LINK]       [:error "No match"]
    [(string? akey)] [] ))

(defn match [rule test-key]
  ; returns rule if test-key matches rule key, nil otherwise.
  (when (every? #(boolean %)
          (map #(or (true? %1) (= %1 %2))
            (first rule) test-key))
    rule))

(defn lookup [key table]
  (let [[hkey hval] (some #(match % key)
                      (partition 2 table)) ]
    (if (= (first hval) :error)
      (let [msg (str (last hval) " at " (pr-str hkey) " for " (pr-str key))]
        (throw (RuntimeException. msg)))
      hval )))

(defn html-attr [tagname h-attr]
  (apply hash-map
    (flatten
      (map (fn [[k v :as kv]]
             (or
               (lookup [k tagname] (attr-table k v))
               kv ))
        h-attr ))))
(deftable html-attr [[akey tagname] aval]
   [:LINK ["a" "link"]] [:href aval]
   [:LINK "img"]        [:src aval]
   [:LINK]              [:ERROR "No match"]
   (string? akey)        [] ))))
这个版本更短,更简单,读起来更好。所以我想我不需要动态范围,至少现在还不需要

Postscript

在我上面的更新中,“每次评估”的方法被证明是有问题的,我不知道如何将所有条件测试作为一个多方法分派来实现(尽管我认为这应该是可能的)

因此,我最终得到了一个宏,它将表扩展为一个函数和一个cond。这保留了原始eval实现的灵活性,但效率更高,编码更少,不需要动态范围:

(defn attr-table [akey aval]
  (list
    [:LINK "a"]   [:href aval]
    [:LINK "img"] [:src aval]
    [:LINK]       [:error "No match"]
    [(string? akey)] [] ))

(defn match [rule test-key]
  ; returns rule if test-key matches rule key, nil otherwise.
  (when (every? #(boolean %)
          (map #(or (true? %1) (= %1 %2))
            (first rule) test-key))
    rule))

(defn lookup [key table]
  (let [[hkey hval] (some #(match % key)
                      (partition 2 table)) ]
    (if (= (first hval) :error)
      (let [msg (str (last hval) " at " (pr-str hkey) " for " (pr-str key))]
        (throw (RuntimeException. msg)))
      hval )))

(defn html-attr [tagname h-attr]
  (apply hash-map
    (flatten
      (map (fn [[k v :as kv]]
             (or
               (lookup [k tagname] (attr-table k v))
               kv ))
        h-attr ))))
(deftable html-attr [[akey tagname] aval]
   [:LINK ["a" "link"]] [:href aval]
   [:LINK "img"]        [:src aval]
   [:LINK]              [:ERROR "No match"]
   (string? akey)        [] ))))
扩展到

(defn html-attr [[akey tagname] aval]
  (cond
    (and 
      (= :LINK akey) 
      (in? ["a" "link"] tagname)) [:href aval]
    (and 
      (= :LINK akey) 
      (= "img" tagname))          [:src aval]
    (= :LINK akey) (let [msg__3235__auto__ (str "No match for "
                                             (pr-str [akey tagname])
                                             " at [:LINK]")]
                     (throw (RuntimeException. msg__3235__auto__)))
    (string? akey) []))
我不知道这是否特别实用,但它肯定是DSLish(制作一种微语言来简化重复性任务)和Lispy(代码作为数据,数据作为代码),两者都与实用性是正交的


关于最初的问题——如何在Clojure中进行动态范围界定——我想答案应该是,惯用的Clojure方法是找到一个不需要它的重新表述。

你解决问题的方法似乎不是很实用,而且你太频繁地使用
eval
;这闻起来像是糟糕的设计


与其使用传递给
eval
的代码片段,为什么不使用适当的函数呢?如果所有模式所需的变量都是固定的,那么您可以直接将它们作为参数传入;如果不是,您可以将绑定作为映射传递。

您的代码看起来比需要的更难。我想你真正想要的是clojure的多种方法。您可以使用它们更好地抽象在attr table中创建的分派表,而不需要动态作用域或全局变量来实现它

; helper macro for our dispatcher function
(defmulti html-attr (fn [& args] (take (dec (count args)) args)))

(defmethod html-attr [:LINK "a"]
  [attr tagname aval] {:href aval})

(defmethod html-attr [:LINK "img"]
  [attr tagname aval] {:src aval})
所有这些都非常简洁和实用,不需要全局表甚至attr表

用户=>(html属性:链接“a”) {:href}


它不完全像你做的那样,只是做了一点修改。我想你说的有道理。我添加了一个更新版本,更好吗?我接受你的回答,因为它让我思考如何在没有评估的情况下完成它。我对功能性风格是新的,我的直觉反应是,评估更多的东西是难以置信的浪费韩:你当然必须这么做,但这可能是非功能性思维?谢谢你的提示。乍一看,这看起来比表格版本更像是打字,因为重复“defmethod html attr”“另外,参数列表中的字符比实际规则多,但我会仔细研究一下。在任何情况下,我们都同意我让它变得比需要的更难:)j-g-faustus:看看clojure.template;布莱恩:谢谢,我会查一下。杰里米·沃尔:我接受了布莱恩的回答,因为它让我思考如何不用评估。但是多方法方法很好,我可能会在每次匹配都有更多处理的情况下使用它,所以我宁愿避免在每次查找时运行所有计算。谢谢