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

Thu*_*ail 6 clojure

追求4Clojure 问题178 - 最好的手,我有这个用于将卡值从字符转换为数字:

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

zipmap每次通话表达式,总是产生{\K 13, \Q 12, \J 11, \T 10}.

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


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

(defmacro constant [exp] (eval exp))
Run Code Online (Sandbox Code Playgroud)

...包裹zipmap呼叫:

(constant (zipmap "TJQK" (iterate inc 10)))
Run Code Online (Sandbox Code Playgroud)

我认为这相当于

(eval '(zipmap "TJQK" (iterate inc 10)))
Run Code Online (Sandbox Code Playgroud)

......但不是eval没有引用:

(eval (zipmap "TJQK" (iterate inc 10)))
Run Code Online (Sandbox Code Playgroud)

欢迎更正,评论和改进.

Ale*_*lex 5

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

更通用的方法是将调用移到表单zipmap外部fn.一种选择是将zipmap调用结果存储在var中def,如下所示(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)))))
Run Code Online (Sandbox Code Playgroud)

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