实施数据模型以防止常见错误

Mat*_*ick 7 clojure datamodel

似乎有多种方法可以在Clojure中实现数据模型:

  • 普通的内置数据类型(maps/lists/sets/vectors)
  • 内置数据类型+元数据 - 例如: (type ^{:type ::mytype} {:fieldname 1})
  • 内置数据类型+特殊访问器函数(例如,get从映射中输入不存在的键会引发异常,而不是静默返回nil)
  • DEFTYPE
  • defstruct
  • defrecord
  • defprotocol

我们已经达到了地图/列表不再适合我们的程度 - 我们遇到了很多错误,前置条件/​​后置条件很容易被捕获,但是需要花费很长时间来追捕(并且它是难以为接受嵌套地图/列表/向量的函数编写有效的前/后条件 - 但我们不确定上述哪一个可供选择.

我们有三个主要目标:

  • 写出惯用的Clojure代码
  • 避免花费大量时间寻找愚蠢的类型错误
  • 我们有能力通过默默地破坏任何东西来改变/重构代码

我们如何利用Clojure的力量来帮助我们?

Rob*_*lan 4

Clojure 文化强烈支持原始数据类型。确实如此。但显式类型可能很有用。当你的普通数据类型变得足够复杂和具体时,你基本上就拥有了一个没有规范的隐式数据类型。

依赖构造函数。 从 OOP 的角度来看,这听起来有点肮脏,但构造函数只不过是一个安全、方便地创建数据类型的函数。普通数据结构的一个缺点是它们鼓励动态创建数据。因此,我尝试直接组合数据,而不是调用 (myconstructor ...)。如果我需要更改底层数据类型,那么很可能会出现错误以及问题。

记录是最佳选择。 由于对原始数据类型的大惊小怪,人们很容易忘记记录可以做很多地图可以做的事情。可以通过相同的方式访问它们。你可以对它们调用 seq 。您可以用同样的方式解构它们。绝大多数需要映射的函数也将接受记录。

元数据不会拯救你。 我对依赖元数据的主要反对意见是它没有体现在平等性上。

user> (= (with-meta [1 2 3] {:type :A})  (with-meta [1 2 3] {:type :B}))
true
Run Code Online (Sandbox Code Playgroud)

这是否可以接受取决于你,但我担心这会引入新的微妙错误。


其他数据类型选项:

  • deftype 仅适用于创建新的基本或特殊用途数据结构的低级工作。与 defrecord 不同,它并没有带来 clojure 的所有优点。对于大多数工作来说,这是没有必要或不建议的。
  • defstruct 应该被弃用。当 Rich Hickey 介绍类型和协议时,他本质上是说 defrecord 应该永远是首选。

协议非常有用,尽管它们感觉有点背离(函数+数据)范式。如果您发现自己在创建记录,您也应该考虑定义协议。

编辑:我发现了普通数据类型的另一个优点,这对我来说之前并不明显:如果您正在进行 Web 编程,则可以高效、轻松地在普通数据类型与 JSON 之间进行转换。(用于执行此操作的库包括 clojure.data.json、clj-json 和我最喜欢的 cheshire)。对于记录和数据类型,这项任务要麻烦得多。