【问题标题】:Clojure - What's the benefit of Using Records Over MapsClojure - 在地图上使用记录有什么好处
【发布时间】:2020-10-22 08:09:14
【问题描述】:

我很难决定何时使用 defrecord 是正确的选择,更广泛地说,如果我在记录中使用的协议是语义 clojure 和功能性的。

在我当前的项目中,我正在构建一个游戏,其中包含不同类型的敌人,它们都有相同的动作集,而这些动作的实现方式可能不同。

来自 OOP 背景,我很想这样做:

(defprotocol Enemy
  "Defines base function of an Enemy"
  (attack [this] "attack function"))

(extend-protocol Enemy
  Orc
  (attack [_] "Handles an orc attack")  
  Troll
  (attack [_] "Handles a Troll attack"))



(defrecord Orc [health attackPower defense])
(defrecord Troll [health attackPower defense])

(def enemy (Orc. 1 20 3))
(def enemy2 (Troll. 1 20 3))

(println (attack enemy))
; handles an orc attack

(println (attack enemy2))
;handles a troll attack

从表面上看,这似乎是有道理的。我希望每个敌人总是有一种攻击方法,但实际实施应该能够因特定敌人而异。使用extend-protocol,我可以高效地调度因敌人而异的方法,并且可以轻松添加新的敌人类型以及更改这些类型的功能。

我遇到的问题是为什么我应该在通用地图上使用记录?以上对我来说有点 OOP 的感觉,并且似乎我正在反对一种更实用的风格。所以,我的问题分为两个:

  1. 我上面的记录和协议实现是一个合理的用例吗?
  2. 更笼统地说,什么时候记录优先于地图?我已经读过,当您多次重新构建同一张地图时,您应该支持记录(就像我在这种情况下一样)。这个逻辑合理吗?

【问题讨论】:

  • 是什么让 Orc 攻击与 Troll 攻击有根本不同?
  • Orc V. Troll 可能不是最好的例子,但在某些情况下,特定实体的攻击方法会大不相同,以至于需要自己的独特计算。攻击也只是一个例子,会有许多共享方法在实现上有所不同。
  • 是的,但它会附加到“类型”还是某些键的存在?
  • 我认为按类型,兽人总是会以相同的方式执行攻击,这对于任何其他常见的“敌人”方法都是如此。
  • 那么记录似乎没问题。如果您有其他调度标准,您可能需要研究多方法

标签: clojure functional-programming diamond-problem


【解决方案1】:

对于 Sean 的出色回答,我只想补充一点,记录会减慢迭代开发的速度,尤其是使用 lein-test-refresh 或类似的工具。

记录形成一个单独的 Java 类,每次更改都必须重新编译,这会减慢迭代周期。

此外,重新编译会破坏与仍然存在的记录对象的比较,因为重新编译的对象(即使没有更改!)不会是 = 到原来的,因为它有一个不同的类文件。例如,假设您有一个Point 记录:

(defrecord Point [x y])

(def p (->Point 1 2)) ; in file ppp.clj
(def q (->Point 1 2)) ; in file qqq.clj

(is (= p q)) ; in a unit test

如果文件ppp.clj 被重新编译,它会生成一个新的Point 类,其“ID”值与以前不同。由于记录必须具有相同的类型和值才能被视为相等,因此即使两者都是 Point 类型并且都具有值 [1 2],单元测试也会失败。这是使用记录时意想不到的痛点。

【讨论】:

    【解决方案2】:

    这个流程图仍然是很好的建议,九年后:https://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/ -- 似乎已经转移到https://cemerick.com/blog/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form.html

    我的经验法则是:始终使用普通哈希映射,直到您真正需要多态性,然后再决定您是需要多方法(对一个或多个参数/属性进行调度)还是协议(仅对类型进行调度)。

    【讨论】:

    • 仅供参考,您指向流程图的链接已损坏。
    • 看起来 Chas Emerick 已经完全改写了他的博客——而且很多有用的内容链接现在都不见了(包括几年的 Clojure 状态调查结果)。该链接在我发布时有效...
    • 我找到了一个更新的链接并将其添加到原始答案中。
    猜你喜欢
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 1970-01-01
    • 2016-11-18
    • 2023-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多