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

时间:2011-10-26 16:10:51

标签: clojure datamodel

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

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

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

我们有三个主要目标:

  • 编写惯用的Clojure代码
  • 避免花费大量时间追捕愚蠢的类型错误
  • 对我们能够通过默默地破坏任何内容来改变/重构代码的能力充满信心

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

2 个答案:

答案 0 :(得分:4)

Clojure文化强烈支持原始数据类型。理所当然。但显式类型可能很有用。当您的纯数据类型变得足够复杂和具体时,您实际上有一个没有规范的隐式dataype。

依赖于构造函数。这听起来有点脏,以OOP的方式,但构造函数只不过是一个安全方便地创建数据类型的函数。普通数据结构的缺点是它们鼓励动态创建数据。所以,我没有调用(myconstructor ...),而是尝试直接编写我的数据。如果我需要更改基础数据类型,则存在很大的错误可能性以及问题。

记录是最佳选择。由于对原始数据类型的所有大惊小怪,很容易忘记记录执行了很多地图可以做的事情。它们可以以相同的方式访问。你可以打电话给他们seq。你可以用同样的方式去构造它们。期望地图的绝大多数函数也会接受记录。

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

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

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


其他dataype选项:

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

协议是非常有用的,即使它们有点偏离(函数+数据)范例。如果您发现自己正在创建记录,则还应考虑定义协议。

编辑:我发现普通数据类型的另一个优点是我之前没有明白过:如果你正在进行Web编程,那么普通数据类型可以高效,轻松地转换为JSON和从JSON转换。 (这样做的图书馆包括clojure.data.json,clj-json和我最喜欢的,柴郡)。对于记录和数据类型,任务更加烦人。

答案 1 :(得分:1)

能够编写适用于地图和列表的函数真的很方便,通过切换到类和协议来放松它会是一种耻辱。毕竟最好在一种类型上拥有一百个功能。切换到协议或记录会有点沉重。例如,它会在调试时阻止你(debug (map :rows (get-state))

元数据是添加“足够类型”的好方法,可以让您的数据在需要它的地方更安全,而不会丢失其他代码库中的好处。我会建议选择2

  • '内置数据类型+元数据((类型^ {:type :: mytype} {:fieldname 1}))'