Clojure中的动态范围界定?
我正在寻找一种惯用的方法来获取Clojure中的动态作用域变量(或类似效果),以便在模板等中使用 下面是一个使用查找表将标记属性从一些非HTML格式转换为HTML的示例问题,其中该表需要访问从其他地方提供的一组变量: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
(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;布莱恩:谢谢,我会查一下。杰里米·沃尔:我接受了布莱恩的回答,因为它让我思考如何不用评估。但是多方法方法很好,我可能会在每次匹配都有更多处理的情况下使用它,所以我宁愿避免在每次查找时运行所有计算。谢谢