使用doseq和atoms而不是loop / recur

时间:2012-04-17 02:25:04

标签: clojure

我在Clojure中重写了Land of Lisp的兽人战斗游戏。在这个过程中,我使用的是更具功能性的风格。我已经提出了两种方法来编写更高级别的游戏循环。一个涉及循环/复发,另一个使用剂量和原子。以下是两个功能:

(defn monster-round [player monsters]
  (loop [n 0 p player]
    (if (>= n (count monsters))
      p
      (recur (inc n)
         (if (monster-dead? (nth monsters n))
           p
           (let [r (monster-attack (nth monsters n) p)]
             (print (:attack r))
             (:player r)))))))


(defn monster-round-2 [player monsters]
  (let [p (atom player)]
    (doseq [m monsters]
      (if (not (monster-dead? m))
        (let [r (monster-attack m @p)]
             (print (:attack r))
             (reset! p (:player r)))))
    @p))

我更喜欢第二种方法,因为代码更简洁,更容易理解。有没有理由说第一种方法更好?或者我错过了另一种方法来做到这一点?

1 个答案:

答案 0 :(得分:8)

这相当于?如果是这样,我更喜欢它 - 它比你的解决方案(imho!)更紧凑,更清晰,更实用

(defn monster-round [monsters player]
  (if-let [[monster & monsters] monsters]
    (recur monsters
      (if (monster-dead? monster)
        player
        (let [r (monster-attack monster player)]
          (print (:attack r))
          (:player r))))
    player))

(注意:我将参数顺序更改为monster-round,以便重复看起来更好)

更一般地说,你不应该在你的“功能”版本中引入n(如果你有一个索引,它就不是真的有用了......)。索引到序列非常非常需要。如果你曾经尝试过这么做的诱惑,我想你会写上面的例行公事......

但是,在写完之后,我想:“嗯。这只是迭代怪物。为什么我们不能使用标准形式?它不是for循环因为玩家改变。所以它必须是一个折叠(即减少),带有玩家前锋“。然后写起来很容易:

(defn- fight [player monster]
  (if (monster-dead? monster)
    player
    (let [r (monster-attack monster player)]
      (print (:attack r))
      (:player r))))

(defn monster-round [player monsters]
  (reduce fight player monsters))

(如果它符合你的要求)是正确答案(tm)。

(也许我没有回答这个问题?我认为你错过了更好的方法,如上所述。一般来说,你应该能够围绕数据结构进行计算,这通常不需要变异;通常你可以 - 并且应该 - 使用map和reduce之类的标准形式,因为它们有助于为其他人记录过程。)