实施数据模型以防止常见错误
Posted
技术标签:
【中文标题】实施数据模型以防止常见错误【英文标题】:Implementing a data model to prevent common errors 【发布时间】:2011-12-15 20:58:18 【问题描述】:似乎有多种方法可以在 Clojure 中实现数据模型:
普通的内置数据类型(maps/lists/sets/vectors) 内置数据类型 + 元数据——例如:(type ^:type ::mytype :fieldname 1)
内置数据类型 + 特殊访问器函数(例如,get
从映射中提取不存在的键会引发异常,而不是静默返回 nil
)
定义类型
defstruct
defrecord
定义协议
我们已经到了地图/列表不再适合我们的地步——我们遇到了很多前置条件/后置条件可以轻松捕获的错误,但需要很长时间才能找到(而且很难为接受嵌套映射/列表/向量的函数编写有效的前置/后置条件)——但我们不确定从上述哪个中进行选择。
我们有三个主要目标:
编写惯用的 Clojure 代码 避免花费大量时间寻找愚蠢的类型错误 对我们更改/重构代码的能力充满信心,并且默默地破坏任何东西我们如何利用 Clojure 的力量来帮助我们?
【问题讨论】:
【参考方案1】:Clojure 文化强烈支持原始数据类型。理所当然的。但是显式类型可能很有用。当你的普通数据类型变得足够复杂和具体时,你基本上有一个没有规范的隐式数据类型。
依赖于构造函数。这听起来有点脏,有点像 OOP,但构造函数只不过是一个可以安全方便地创建数据类型的函数。普通数据结构的一个缺点是它们鼓励动态创建数据。因此,我尝试直接组合我的数据,而不是调用 (myconstructor ...)。如果我需要更改基础数据类型,则可能会出现很多错误以及问题。
记录是最佳选择。由于对原始数据类型大惊小怪,很容易忘记记录可以做很多地图可以做的事情。可以以相同的方式访问它们。您可以对它们调用 seq。你可以用同样的方式解构它们。绝大多数需要地图的函数也将接受记录。
元数据不会拯救你。我对依赖元数据的主要反对意见是它没有反映在平等中。
user> (= (with-meta [1 2 3] :type :A) (with-meta [1 2 3] :type :B))
true
这是否可以接受取决于你,但我担心这会引入新的微妙错误。
其他数据类型选项:
deftype 仅用于创建新的基本或特殊用途数据结构的低级工作。与 defrecord 不同,它并没有带来所有的 clojure 优点。对于大多数工作来说,这不是必需的,也不建议这样做。 defstruct 应该被弃用。当 Rich Hickey 介绍类型和协议时,他基本上说应该永远首选 defrecord。协议非常有用,尽管它们感觉有点偏离(函数 + 数据)范式。如果您发现自己在创建记录,您也应该考虑定义协议。
编辑:我发现了纯数据类型的另一个优势,这在我之前并不明显:如果您正在做 Web 编程,纯数据类型可以高效轻松地与 JSON 相互转换. (执行此操作的库包括 clojure.data.json、clj-json 和我最喜欢的 cheshire)。有了记录和数据类型,这个任务就更烦人了。
【讨论】:
好的,所以我需要弄清楚defrecord
和defprotocol
,可以忽略defstruct
,也不必太担心deftype
。 defrecord
创建 java 代码对 clojure 程序是否重要——从某种意义上说,我不想担心拥有一个 java 类,但如果 clojure 想私下使用一个,那很好吗?很好的答案,非常有帮助。
好吧,一个普通的地图也是一个java类,你可以在github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/…看到
所以 defrecord 创建一个 java 类并不是什么大不了的事。从我们使用记录的角度来看,它不会有太大的不同——除了构造之外,它会感觉完全像 clojure 数据。
是的,一定要阅读 defrecord defprotocol 和扩展协议。 IMO,它们背后的逻辑经过深思熟虑。
我担心的是我不知道这到底意味着什么:Dynamically generates compiled bytecode for class with the given name, in a package with the same name as the current namespace, the given fields, and, optionally, methods for protocols and/or interfaces.
(来自 defrecord 文档)【参考方案2】:
能够编写在地图和列表上工作的函数真的很方便,而通过切换到类和协议来放弃它会有点可惜。毕竟,最好在一种类型上拥有一百个功能。切换到协议或记录会有点笨拙。例如,它会阻止您在调试时使用(debug (map :rows (get-state))
。
元数据是添加“恰到好处的类型”的好方法,可以让您的数据在需要它的地方更安全,而不会失去其他代码库的好处。我建议使用选项 2
'内置数据类型 + 元数据 ((type ^:type ::mytype :fieldname 1))'【讨论】:
这不是 Alan Perlis 主义关于一种数据类型上的一百个函数吗?我重新格式化了标题中的那个例子——不小心把它放在了非 LISP 括号中! 是的,这就是我想要的报价,信用到期!以上是关于实施数据模型以防止常见错误的主要内容,如果未能解决你的问题,请参考以下文章