关于clojure:使用doseq和atoms而不是loop/recur

Using doseq and atoms rather than loop/recur

我一直在用 Clojure 重写 Land Of Lisp 的兽人战斗游戏。在此过程中,我使用了更实用的样式。我想出了两种方法来编写更高级别的游戏循环的一部分。一个涉及循环/递归,另一个使用doseq 和atoms。下面是两个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(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
2
3
4
5
6
7
8
9
(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 循环,因为玩家会改变。所以它必须是折叠(即减少),它带着玩家前进"。然后很容易写:

1
2
3
4
5
6
7
8
9
(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,因为它们有助于为其他人记录流程)。