defrecord 参数过滤

defrecord argument filtrering

假设我定义了这样一条记录:

(defrecord MyRecord [x y z])

我是这样构建的:

(def test (map->MyRecord {:x "1" :y "2" :z "3" :w "ikk"}))

我可以这样做:

(:w test) ; Returns "ikk"
  1. 为什么要保留:w?我在想,因为我创建了一个包含 xyz 的记录,所以这些是唯一应该存在的 "keys"。
  2. 有没有一种好方法可以在不使用 select-keys 的情况下排除 record 声明中未作为参数出现的键?

例如:

(defrecord MyRecord1 [x y z])
(defrecord MyRecord2 [x y w])
(defprotocol MyProtocol
  (do-stuff [data]))

(extend-protocol MyProtocol
  MyRecord1
  (do-stuff [data]
    (let [data (select-keys data [:x :y :z])] ; (S1)
      ...))

  MyRecord2
  (do-stuff [data]
    (let [data (select-keys data [:x :y :w])] ; (S2)
      ...)))

我想避免在使用 MyProtocol 时为每条记录手动执行 select-keys (S1, S2) 当使用 map-> 构建记录时使用附加数据(我不知道不关心)。

这是defrecord的一个特点。请参阅:http://clojure.org/datatypes 部分 deftype and defrecord

defrecord provides a complete implementation of a persistent map, including:

  • ...
  • extensible fields (you can assoc keys not supplied with the defrecord definition)

通过反射你可以看到,你的 x,y,z 参数是对象的常规属性:

user=> (>pprint (.? (map->MyRecord {:w 4})))
(#[__extmap :: (user.MyRecord) | java.lang.Object]
 ;...
 #[x :: (user.MyRecord) | java.lang.Object]
 #[y :: (user.MyRecord) | java.lang.Object]
 #[z :: (user.MyRecord) | java.lang.Object])

附加值存储在 __extmap:

 user=> (.-__extmap (map->MyRecord {:w 4}))
 {:w 4}

这意味着,除了注意要将记录作为 Map 处理的地方外,您别无选择,因为可以随时添加新键:

user=> (let [r (->MyRecord 1 2 3) r (assoc r :w 4)] (keys r))
(:x :y :z :w)

因此,如果您发现自己在重复 (select-keys myr [:x :y :z]) 之类的代码,则将其提取为辅助函数 fn。

添加诸如您自己的 c'tors 之类的东西始终是一个好主意(例如,如果您希望 0 用于丢失的密钥而不是 nil,例如),但这只会保护您免受自己的伤害以及以您为榜样的用户。

Clojure 记录为 Java 互操作实现了 IPersistentMap (as well as java.util.Map),并且其行为类似于法线贴图 - 这意味着您可以在任何地方以与使用贴图相同的方式使用它们。您可以将记录视为类型映射 - 它是一个映射,但您可以使用多种方法和协议轻松地进行调度。

这使得开始使用普通地图表示数据变得非常容易,然后在需要类型时前进到记录。

作为映射,记录支持附加键,但它们的处理方式不同。对于您的示例,(.x test) 有效,但 (.w test) 无效,因为只有预定义键成为实现中的字段 Java class.

为了避免额外的键,只需创建自己的构造函数:

(defn limiting-map->MyRecord
  [m]
  (map->MyRecord
   (select-keys m [:x :y :z])))