如何使Clojure在编译时评估常量局部表达式

时间:2014-04-02 13:54:15

标签: clojure

追求4Clojure Problem 178 - Best Hand,我将其用于将卡片值从字符转换为数字:

 (fn [ch]
   (or
    ({\A 1} ch)
    ((zipmap "TJQK" (iterate inc 10)) ch)
    (- (int ch) (int \0))))

zipmap表达式会在每次调用时进行评估,始终生成{\K 13, \Q 12, \J 11, \T 10}

我们怎样才能让编译器只评估一次?


经过多次脑力激荡,我想出了

(defmacro constant [exp] (eval exp))

...因此包裹zipmap电话:

(constant (zipmap "TJQK" (iterate inc 10)))

我认为这相当于

(eval '(zipmap "TJQK" (iterate inc 10)))

...但没有引用的eval

(eval (zipmap "TJQK" (iterate inc 10)))

欢迎更正,评论和改进。

1 个答案:

答案 0 :(得分:5)

您的constant宏将适用于此特定情况,因为正在评估的表单只有符号可解析为clojure.core中的函数和编译时文字。在其他情况下,您可能会遇到符号解析问题,例如从函数参数计算本地常量以在匿名函数中使用。

更常见的方法是将呼叫转移到zipmap表单之外的fn。一种选择是使用zipmapdef调用的结果存储在var中,如(def my-const (zipmap "TJQK" (iterate inc 10)))中所示。

但是,在这种情况下,您要创建一个匿名函数,创建一个全局可访问的var可能会有点过分。因此,我建议将fn置于捕获常量值的let绑定中:

 (let [face-cards (zipmap "TJQK" (iterate inc 10))]
   (fn [ch]
     (or
       ({\A 1} ch)
       (face-cards ch)
       (- (int ch) (int \0)))))

编辑:正如评论中所指出的,此解决方案将在加载时计算常量值,而不是在编译时计算。我很难想出一个重要的场景,但值得指出的是它的语义与你的方法略有不同。