来自 Java 背景,我非常喜欢静态类型安全,并且想知道 clojure 程序员如何处理数据格式定义的问题(也许不仅仅是类型,还有一般不变量,因为类型只是其中的一个特例。)
这是类似于现有的问题“Clojure 中的类型安全”,但该问题更关注如何在编译时检查类型方面,而我更感兴趣的是如何务实地解决问题。
作为一个实际示例,我正在考虑处理特定文档格式的编辑器应用程序。每个文档都包含多种不同类型的元素(图形元素、字体元素等)。不同元素类型都有编辑器,当然还有将文档从其本机的字节流转换为字节流的功能。磁盘格式。
我感兴趣的基本问题是编辑器和读/写功能必须就通用数据格式达成一致。在Java 中,我会将文档的数据建模为对象图,例如用一个类代表文档,用一个类代表每个元素种类。通过这种方式,我可以获得关于数据结构的编译时保证,并且图形元素的字段“宽度”是整数而不是浮点数。它不保证宽度为正 - 但使用 getter/setter 接口将允许相应的类添加类似的不变保证。
能够依赖这一点使得处理这些数据的代码更简单,并且可以在编译时或运行早期捕获格式违规(其中某些代码尝试修改会违反不变量的数据)。
如何在 Clojure 中实现类似的“数据格式可靠性”?据我所知,没有办法执行编译时检查,并且将域数据隐藏在函数接口后面似乎不被鼓励,因为不符合习惯(或者也许我误解了?),那么 Clojure 开发人员应该做什么才能感到安全传递给它们的函数的数据格式?当保存函数注意到字体列表中存在图形元素时,如何让代码尽快出错,而不是在用户编辑了 20 分钟以上并尝试保存到磁盘之后编辑器错误?
请注意,我对 Clojure 和学习很感兴趣,但还没有用它编写任何实际的软件,所以我可能只是感到困惑,答案很简单 - 如果是这样,很抱歉浪费你的时间:) 。
Coming from a Java background, I'm quite fond of static type safety and wonder how clojure programmers deal with the problem of data format definitions (perhaps not just types but general invariants, because types are just a special case of that.)
This is similar to an existing question "Type Safety in Clojure", but that one focuses more on the aspect of how to check types at compile time, while I'm more interested in how the problem is pragmatically addressed.
As a practical example I'm considering an editor application which handles a particular document format. Each document consists of elements that come in several different varieties (graphics elements, font elements etc.) There would be editors for the different element types, and also of course functions to transform a document from/to a byte stream in its native on-disk format.
The basic problem I am interested in is that the editors and the read/write functions have to agree on a common data format. In Java, I would model the document's data as an object graph, e.g. with one class representing a document and one class for each element variety. This way, I get a compile-time guarantee about what the structure of my data looks like, and that the field "width" of a graphics element is an integer and not a float. It does not guarantee that width is positive - but using a getter/setter interface would allow the corresponding class to add invariant guarantees like that.
Being able to rely on this makes the code dealing with this data simpler, and format violations can be caught at compile-time or early at runtime (where some code attempts to modify data that would violate invariants).
How can you achieve a similar "data format reliability" in Clojure? As far as I know, there is no way to perform compile-time checking and hiding domain data behind a function interface seems to be discouraged as non-idiomatic (or maybe I misunderstand?), so what do Clojure developers do to feel safe about the format of data handed into their functions? How do you get your code to error out as quickly as possible, and not after the user edited for 20 more minutes and tries to save to disk, when the save function notices that there is a graphics element in the list of fonts due to an editor bug?
Please note that I'm interested in Clojure and learning, but didn't write any actual software with it yet, so it's possible that I'm just confused and the answer is very simple - if so, sorry for wasting your time :).
发布评论
评论(2)
我不认为使用验证 API 来构造和操作数据有任何错误或不惯用的地方,如下所示。
此外,引用(vars、refs、atoms、agents)可以有一个关联的 验证器函数,可用于确保始终有效的状态。
I don't see anything wrong or unidiomatic about using a validating API to construct and manipulate your data as in the following.
In addition, references (vars, refs, atoms, agents) can have an associated validator function that can be used to ensure a valid state at all times.
好问题 - 我还发现从静态类型语言转向动态语言需要更多地关注类型安全。幸运的是,TDD 技术在这里有很大帮助。
我通常编写一个“验证”函数来检查您对数据结构的所有假设。我也经常在 Java 中这样做,以实现不变的假设,但在 Clojure 中,这更重要,因为您也需要检查类似类型的想法。
然后,您可以通过多种方式使用 validate 函数:
(validate foo)
(is (validate (new-foo-from-template abc )))
(read-foo some-foo-input-stream)
是否有效如果您有一个复杂的数据结构,多个不同组件类型的树,您可以为其编写一个验证函数每个组件类型并具有整个文档的验证函数,递归地调用每个子组件的验证。一个不错的技巧是使用协议或多方法使验证函数对于每个组件类型都是多态的。
Good question - I also find that moving from a statically typed language to a dynamic one requires a bit more care about type safety. Fortunately TDD techniques help a huge amount here.
I typically write a "validate" function which checks all your assumptions about the data structure. I often do this in Java too for invariant assumptions, but in Clojure it's more important because you need to check thinks like types as well.
You can then use the validate function in several ways:
(validate foo)
(is (validate (new-foo-from-template a b c)))
(read-foo some-foo-input-stream)
is validIf you have a complex data structure which is a tree of multiple different component types, you can write a validate function for each component type and have the validate function for the whole document call validate for each sub-component recursively. A nice trick is to use either protocols or multimethods to make the validate function polymorphic for each component type.