Abstracting away from data structure implementation details in Clojure
I am developing a complex data structure in Clojure with multiple sub-structures.
I know that I will want to extend this structure over time, and may at times want to change the internal structure without breaking different users of the data structure (for example I may want to change a vector into a hashmap, add some kind of indexing structure for performance reasons, or incorporate a Java type)
My current thinking is:
- Define a protocol for the overall structure with various accessor methods
- Create a mini-library of functions that navigate the data structure e.g. (query-substructure-abc param1 param2)
- Implement the data structure using defrecord or deftype, with the protocol methods defined t开发者_运维问答o use the mini-library
I think this will work, though I'm worried it is starting to look like rather a lot of "glue" code. Also it probably also reflects my greater familiarity with object-oriented approaches.
What is the recommended way to do this in Clojure?
I think that deftype
might be the way to go, however I'd take a pass on the accessor methods. Instead, look into clojure.lang.ILookup
and clojure.lang.Associative
; these are interfaces which, if you implement them for your type, will let you use get
/ get-in
and assoc
/ assoc-in
, making for a far more versatile solution (not only will you be able to change the underlying implementation, but perhaps also to use functions built on top of Clojure's standard collections library to manipulate your structures).
A couple of things to note:
You should probably start with
defrecord
, usingget
,assoc
& Co. with the standarddefrecord
implementations ofILookup
,Associative
,IPersistentMap
andjava.util.Map
. You might be able to go a pretty long way with it.If/when these are no longer enough, have a look at the sources for
emit-defrecord
(a private function defined incore_deftype.clj
in Clojure's sources). It's pretty complex, but it will give you an idea of what you may need to implement.Neither
deftype
nordefrecord
currently define any factory functions for you, but you should probably do it yourself. Sanity checking goes inside those functions (and/or the corresponding tests).The more conceptually complex operations are of course a perfect fit for protocol functions built on the foundation of
get
& Co.
Oh, and have a look at gvec.clj
in Clojure's sources for an example of what some serious data structure code written using deftype
might look like. The complexity here is of a different kind from what you describe in the question, but still, it's one of the few examples of custom data structure programming in Clojure currently available for public consumption (and it is of course excellent quality code).
Of course this is just what my intuition tells me at this time. I'm not sure that there is much in the way of established idioms at this stage, what with deftype
not actually having been released and all. :-)
精彩评论