13

我有一些有点昂贵的计算(启动数据库),如果我真的要使用它,我只想创建数据库。我正在寻找一个参考变量(或者只是一个普通变量,如果可能的话),它只会在它被使用(或取消引用)的情况下评估它的值。概念上类似于以下内容。

(def v (lazy-var (fn [] (do (println "REALLY EXPENSIVE FUNCTION") true))))

将来,当我只使用 var v 或调用 @v 时,我会让它打印出“非常昂贵的功能”,并且从那时起 v 的值为 true。这里重要的是 fn 直到变量被(取消)引用时才被评估。需要时,对函数进行一次且仅计算一次以计算变量的值。这在clojure中可能吗?

4

2 回答 2

31

delay将非常适合此应用程序:

delay- (delay & body)

接受一个表达式主体并产生一个延迟对象,该对象仅在第一次被强制时调用主体(使用forcederef/ @),并将缓存结果并在所有后续force调用中返回它。

将用于构造数据库句柄的代码放在delay调用主体中,存储为 Var。然后在需要使用 DB 句柄时取消引用此 Var — 在第一次取消引用时将运行主体,在随后的取消引用时将返回缓存的句柄。

(def db (delay (println "DB stuff") x))

(select @db ...) ; "DB stuff" printed, x returned
(insert @db ...) ; x returned (cached)
于 2012-06-14T03:52:02.283 回答
6

Clojure 1.3 为此目的引入了 memoize 函数:

(记住 f)

返回引用透明函数的记忆版本。该函数的记忆化版本保留了从参数到结果的映射的缓存,并且当经常重复使用相同参数的调用时,以更高的内存使用为代价具有更高的性能。

在您的示例中,将不存在的lazy-var 替换为 memoize:

(def v (memoize (fn [] (do (println "REALLY EXPENSIVE FUNCTION") true))))
(v)
=>REALLY EXPENSIVE FUNCTION
=>true
(v)
=>true

(delay expr) 正如另一个答案所解释的那样,也可以完成这项工作。关于取消引用延迟的额外评论 - force 和 deref/@ 之间的区别在于,如果在非延迟变量上使用 force 不会引发异常,而 deref/@ 可能会抛出 ClassCastException“无法转换为 clojure.lang.IDeref”。

于 2013-04-09T00:06:24.033 回答