> Zipper constructors are provided for nested seqs, nested vectors, and
> the xml elements generated by xml/parse. All it takes is a 4-5 line
> function to support other data structures. The zipper support is in
> src/zip.clj.
>
>
> Try it out and let me know what you think,
After coming up with an immutable nested data structure of my own
using a mixture of struct maps, sets, maps, and other (Java)
containers, I started thinking about how I could (effectively) change
something within it. It was then that I understood clearly the
problem that zip.clj solves. I like it.
It would be nice if there were a way to manipulate an arbitrary
clojure nested data structure using a zipper and, after retrieving the
root, have something with (except perhaps for some intentional
editing) the exact same structure as the original. seq-zip almost
does that, but it has the unfortunate property that it turns
everything into lists on the way down and doesn't reverse that on the
way up: A struct-map becomes a list of map entries and stays that way.
It occurs to me that that's a solvable problem. We would need a
function to pass as zipper's make-node that would create a new empty
instance of the exact same type as the parent node and add a seq of
its children to it. One might call this "deseq":
(defn deseq
"Given an exemplar and a seq of values, creates a new instance
of the exact type of the exemplar with the values as contents"
[exemplar values]
...)
The invariants would be:
For any instance of IPersistentCollection coll:
(= (deseq coll (seq coll)) coll)
(= (class (deseq coll (seq coll))) (class coll))
(Ideally for a struct map, the empty copy of the exemplar would only
have the basis's keys in it as well.)
Here's a sketch of a "mostly correct, but difficult to maintain"
definition of deseq:
(import '(clojure.lang PersistentArrayMap PersistentHashMap
PersistentHashSet PersistentList
PersistentQueue PersistentStructMap
PersistentTreeMap PersistentTreeSet
PersistentVector))
(defn deseq
"Given an exemplar and a seq of values, creates a new
instance
of the exact type of the exemplar with the values as
contents"
[exemplar values]
(let [empty-clone
(cond (instance? PersistentArrayMap exemplar)
(. PersistentArrayMap EMPTY)
(instance? PersistentHashMap exemplar)
(. PersistentHashMap EMPTY)
(instance? PersistentHashSet exemplar)
(. PersistentHashSet EMPTY)
;; (instance? PersistentList exemplar) ; for
List, we can return the values seq directly
;; (. PersistentList EMPTY)
(instance? PersistentQueue exemplar)
(. PersistentQueue EMPTY)
(instance? PersistentStructMap exemplar)
(struct (apply create-struct (keys exemplar)))
(instance? PersistentTreeMap exemplar)
(. PersistentTreeMap EMPTY)
(instance? PersistentTreeSet exemplar)
(. PersistentTreeSet EMPTY)
(instance? PersistentVector exemplar)
(. PersistentVector EMPTY))]
(if empty-clone
(reduce conj empty-clone values)
values)))
I think a generic zipper (gen-zip ?) that worked like seq-zip, but used
(deseq node children)
as its make-node function would be a useful addition to clojure.
I have an idea for a more maintainable and correct implementation of
deseq. Subclasses of IPersistentCollection could be required to
provide either a "deseq" method directly (in addition to "seq") or
provide an "empty-clone" method that would make a clojure
implementation of deseq compact and readable.
Any thoughts?
--Steve
I don't like the name deseq. But I agree, all that is needed is for
collections to implement empty(), which you could then use with
'into'.
(into (empty coll) new-stuff)
I'll look into empty. (Aren't languages funny?)