Hi Stuart, I'll be mailing the agreement later today, and I'll work on a patch shortly. I've noticed that the unit tests file for multi-methods, where tests for derive and underive would be located, is essentially empty. So I thought I might put some tests in there for isa, parents, ancestors, etc. while I'm about it.
I take your point on preferring a simpler version. I have a feeling that: -most people are using the global hierarchy -most people are using the hierarchy essentially statically which is a bit of a shame, since the hierarchy system is quite powerful. With independent hierarchies, you can confidently juggle several hierarchy relationships, and dynamically update them. I think their uses go beyond multi-methods. But for hierarchies which are largely static, and which will rarely underive, a simpler underive in core will be good. I'll probably put my complicated version together with some other convenience functions for hierarchies in a library of my own, in case anyone has a need for it. Rob p.s. Loved the book. On Jun 16, 7:54 am, Stuart Halloway <stuart.hallo...@gmail.com> wrote: > Hi Rob, > > Thanks for tracking this down. If you will execute a CA [1], I would love to > get a patch (with tests) that fixes this. I have created a ticket at [2] to > track it. > > I would prefer something along the lines of the simpler fix shown below, > unless anybody pops up on this thread with a real-world use case for > performance-critical underive. > > Stu > > [1]http://clojure.org/contributing > [2]https://www.assembla.com/spaces/clojure/tickets/382-fix-underive-for-... > > > > > As an alternative, if we're willing to rebuild the entire hierarchy, > > not just the subgraph that's affected by the underivation, > > we can just do this: > > > (defn easy-underive [h child parent] > > (let [oldParents (:parents h) > > childsParents (disj (clojure.set/union #{} (child oldParents)) > > parent) > > newParents (if (not-empty childsParents) > > (assoc oldParents child childsParents) > > (dissoc oldParents child)) > > derivation-seq (flatten (map #(cons (key %) (interpose (key %) (val > > %))) > > (seq newParents)))] > > (reduce #(apply derive (cons %1 %2)) (make-hierarchy) > > (partition 2 derivation-seq)))) > > > It's less efficient, because it tosses out all of the information in > > the ancestor and descendant sets. But it is a good deal simpler -- 10 > > lines of code instead of 60 or so. For small hierarchies this would > > be fine. If anyone were to make large > > hierarchies which had to be modified efficiently, though, I think > > something like in the first message would be required. > > > On Jun 15, 4:29 pm, Rob Lachlan <robertlach...@gmail.com> wrote: > >> Oh God. What broken formatting. Sorry about that. > > >> On Jun 15, 4:24 pm, Rob Lachlan <robertlach...@gmail.com> wrote: > > >>> I think that the underive function for removing hierarchy > >>> relationships between keywords is broken. I'll illustrate with an > >>> example and describe what I think the problems are. I've got some > >>> code for a function which (I hope!) performs underive correctly, and > >>> I'd love it if people had a look. > > >>> Consider a hierarchy with child-parent relationships: > > >>> :c -> :p1, :c -> :p2, :p1 -> :a1, :p1 -> :a2, :p2 -> :a2 > > >>> Which I'll illustrate with the world's worst ascii art: > > >>> a1 a2 > >>> | / | > >>> | / | > >>> | / | > >>> p1 p2 > >>> | / > >>> | / > >>> | / > >>> c > > >>> ; creating this hierarchy: > >>> user> (def h (reduce #(apply derive (cons %1 %2)) (make-hierarchy) > >>> [[:p1 :a1] [:p1 :a2] [:p2 :a2] [:c :p2] [:c :p1]])) > >>> #'user/h > >>> user> h > >>> {:parents {:c #{:p1 :p2}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :ancestors {:c > >>> #{:p1 :p2 :a2 :a1}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :descendants {:p1 > >>> #{:c}, :p2 #{:c}, :a2 #{:p1 :p2 :c}, :a1 #{:p1 :c}}} > > >>> Now the underive: > > >>> user> (underive h :c :p1) > >>> {:parent {:c #{:p2}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :ancestors {:c > >>> #{:p2}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :descendants {:p1 #{}, :p2 > >>> #{:c}, :a2 #{:p1 :p2}, :a1 #{:p1}}} > > >>> Problems: > > >>> Most seriously, it is incorrect: :c should still show up as a > >>> descendant of > >>> :a2, since :c is a child of :p2, and :p2 a child of :a2. Note that > >>> the > >>> parent map is correct, so it is the ancestor and descendant maps which > >>> are > >>> inconsistent. > > >>> Also, notice that the key for the parent map has changed from :parents > >>> to > >>> :parent, which causes calls to the parents function to return nil. > > >>> Also, keys which map to empty sets are left in each of the maps > >>> (parents, > >>> descendants and ancestors), with the result that equality tests could > >>> fail > >>> in some cases in which they would be true. > > >>> Potential fix, and request for code review: > > >>> I've written an underive-new function which appears to fix these > >>> problems, > >>> at least in the tests that I've done. > > >>>http://pastebin.com/eRiz5ihw > > >>> My approach is to identify the sets of tags in the hierarchy whose > >>> ancestors > >>> or descendants may be affected, remove their ancestor or descendant > >>> mappings, > >>> then rebuild them. In a call (underive h child parent), the child and > >>> all > >>> the child's descendants will need to have their ancestor mappings > >>> fixed. > >>> Similarly, the parent and all of the parent's ancestors will need to > >>> have > >>> their descendant mappings rebuilt. > > >>> (As an aside, the problem here is that our hierarchy can be a DAG > >>> rather > >>> a tree, because multiple inheritance is allowed. And therefore, for > >>> any > >>> ancestor/descendant relationship it's comparatively expensive to > >>> determine > >>> whether a particular parent-child edge is essential or not.) > > >>> Next, the two sets of interest (child + child's descendants, and > >>> parent + > >>> parent's ancestors) are sorted topologically. For the child + > >>> child's > >>> descendants, we can now rebuild the ancestor mappings by taking a tag > >>> from > >>> the top of the set, setting its ancestors to the union of tag's > >>> parents and > >>> parents' ancestors, and repeating the process on the next tag in > >>> topologically > >>> sorted order until all tags are consumed. The parent + parent's > >>> ancestors > >>> can be done in a similar way, except that a children function is > >>> required, and > >>> the tags must be topologically sorted in the reverse direction. > > >>> Some remarks: > > >>> An alternative approach would be to just recompute the whole ancestor > >>> and > >>> descendant maps for the whole hierarchy. This would be simpler, but > >>> for > >>> large hierarchies would be unnecessarily expensive. > > >>> The topological sort function which I wrote avoids recursion. If we > >>> wanted > >>> to allow multiple recursion, it could be simplified a bit, but I > >>> wanted to > >>> avoid stack consumption. > > >>> Anyway, I'd appreciate any suggestions and criticisms, since this is > >>> the > >>> first clojure code of mine that anyone has looked at. > > > -- > > You received this message because you are subscribed to the Google > > Groups "Clojure" group. > > To post to this group, send email to clojure@googlegroups.com > > Note that posts from new members are moderated - please be patient with > > your first post. > > To unsubscribe from this group, send email to > > clojure+unsubscr...@googlegroups.com > > For more options, visit this group at > >http://groups.google.com/group/clojure?hl=en -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en