惰性求值Clojure描述

26次阅读

共计 795 个字符,预计需要花费 2 分钟才能阅读完成。

先看一个简单的例子:

(defn pinc [n]
  (prn ".")
  (inc n))

定义nums:

(def nums (map pinc [1 2 3]))

REPL 没有任何输出.
输入nums:

nums
"."
"."
"."
=> (2 3 4)

nums才被真正的计算.nums在定义的时候并没有被计算, 只有在使用的时候才会真正的计算.

许多函数式编程语言都是惰性的.Haskell 是完全惰性, 在 Clojure 中, 主要的序列操作像 map,reduce,filter,repeatedly 都是惰性求值.
例如

(def n (pinc 0))
"."
=> #'logic.core/n

上个例子被立刻求值因为没有序列的存在.

无限序列

最常见的惰性求值是无限序列或流. 如果我们想要定义一个 list 包含所有的质数, 这个列表是无穷大的.
如果我们在 C ++ 或其他语言定义了这样一个质数序列, 程序将无限的计算下去. 如果在 Clojure 或者 Haskell 中定义了序列, 计算不会立刻发生. 我们可以只打印前 100 个质数. 因为惰性求值只计算所需要的部分序列.

游戏服务器

想象我们需要制作一个游戏服务器, 游戏中有许多的怪兽, 每个怪兽都有一个随机生成的物品清单:

(defn gen-item []
  {:name "sword"
   :attack (rand-int 100)})
  
(def monster {:name "wolf"
              :level 3
              :inventory (repeatedly 10 gen-item)})

我们的游戏十分巨大, 每秒产生 1000 个怪物, 每个怪物都随身携带 10 个随机生成的物品.

如果非惰性求值, 服务器不得不消耗大量的资源在随机生成这些物品上.

感谢这些序列都是惰性计算! 尽管每秒都有 1000 个怪物被生成, 但实际上没有任务物品是被实际产生. 这些随机物品被实际的生成仅在玩家查看死亡的怪物装备时才发生!
如果只有 50% 的怪物被杀死, 我们的服务器就减轻了一半的计算量. 感谢惰性求值的威力!

正文完
 0