Hi Doug, Johan and Josh: I agree with Johan that "Traversals are the core of demjava, and they have to be bullet-proof." That is why Doug is developing the bullet-proof AP library so that others can use traversals in their tools. This is a recurring theme and I was "accused" by your predecessors for not taking action on this issue. We need to put all our combined expertise into a TR with the title: Design Issues with Traversal Strategies and Abstract Classes Abstract: This report analyzes tradeoffs between various design decisions related to the semantics of code attachment to abstract classes (for both visitors and APPCs). Traversal strategies have the primary goal to specify traversals of objects but we need systematic and robust rules for handling code at abstract classes. After weighing the pros and cons, this report selects a specific solution that defines the semantics to be supported by Demeter/Java. Preliminary table of contents: Code attached to abstract classes What is the problem? What are the design goals? Robustness under abstract(A) <-> concrete(A) transformation Invariance under flattening of class graphs See 9.3.5, 9.3.6, ... in my book visitors APPCs Design Space and tradeoffs Replacing all occurrences of abstract class A by descendants(A) Role of Abstract Superclass Rule Meaning of to-stop abstract class visitors APPCs Theses by Ciao, Huersch, Seiter: what do they recommend on this topic? My book: 256-258: what does it recommend: semantics of Demeter/C++ Controlling generated visitors with traversal strategies CopyVisitor EqualVisitor PrintingVisitor I would like to have Johan take a lead on this with strong participation from all of us. -- Karl From dougo@ccs.neu.edu Wed Oct 14 00:40:21 1998 From: Doug Orleans Date: Wed, 14 Oct 1998 00:40:19 -0400 (EDT) To: Johan Ovlinger cc: lieber@ccs.neu.edu, jayantha@ccs.neu.edu, dougo@ccs.neu.edu Subject: Re: Man bitten by dog, sues owner Johan Ovlinger writes: > > Doug, I just spent god-knows how many hours tracking this one > down. (Ok, that says alot about my bug-hunting skills, too) > > when I traverse to-stop an alternation class, and have a wrapper on the > alternation class, I expect the wrapper to be called. As near as I can > tell, this isn't the case. (?) > > If this is a bug w/ demjava, and not my own fault, it is truly > nasty. Traversals are the core of demjava, and they have to be > bullet-proof. If you can't fix it, at least have demjava complain > about it; complaining about alternation classes as targets of the > traversal should be enough. I'd have to look into it, but I wouldn't be surprised if this were the behavior. The reason that alternation-class wrappers aren't called if none of the descendants are in the traversal is because of the premature-termination thingy, which says to immediately prune the traversal if you ever get to an object that is of a class which is not on the traversal. I'm pretty sure this behavior was in Demeter/C++ as well, and I have always found it a little annoying, because then you have to specifically enumerate all the descendants that you want to traverse to, which is not very adaptive. I think we've discussed this before, with one idea being to allow you to say "to descendants(Foo)" instead of having to say "to { A,B,C,D...}". But maybe it should be smart enough to figure that if none of the descendants are in the traversal, and yet none of them have been specifically bypassed, then go ahead and add them all in. Err, there are some details I'm glossing over, but you get the idea. If nothing else, this should certainly be documented. Karl, what do you think is the best way to get around this idiosyncracy? Was there some other motivation for this kind of behavior in Demeter/C++ besides the premature-termination thing? --Doug From johan@ccs.neu.edu Wed Oct 14 00:56:45 1998 cc: Johan Ovlinger , lieber@ccs.neu.edu, jayantha@ccs.neu.edu Subject: Re: Man bitten by dog, sues owner From: Johan Ovlinger Oops. Forgot to email you 'bout this. Karl and I discussed this, and I/we are of the opinion that we might as well be consistent with our view of abstract classes as collections of concrete classes. (cf flattening) What this means is that as a preprocessing stage in the strategy expansion, each alternation class can be replaced by all of it's concrete subclasses. Why should wrappers be different from normal methods? Methods are dispatched on the most specific type of the reciever, so we could argue that the analogous case for wrappers should be that all applicable wrappers for the host should be called. 'nother argument for this change is that it is clearly counter intuitive that void Foo() to Bar { before Bar {{ }} } does call Bar's wrapper if it (Bar) is a concrete class, but not if it is an alternation. Lastly, this change should hopefully clarify the semantics of Copy & Equal visitor. Johan From dougo@ccs.neu.edu Wed Oct 14 03:27:57 1998 From: Doug Orleans Date: Wed, 14 Oct 1998 03:27:54 -0400 (EDT) To: Johan Ovlinger Cc: lieber@ccs.neu.edu, jayantha@ccs.neu.edu, dougo@ccs.neu.edu Subject: Re: Man bitten by dog, sues owner Johan Ovlinger writes: > Why should wrappers be different from normal methods? Methods are > dispatched on the most specific type of the reciever, so we could > argue that the analogous case for wrappers should be that all > applicable wrappers for the host should be called. Sure, except the issue is whether the host is actually being visited at all. When you say "to-stop A" where A is an alternation class, then no B-objects (assuming B is a child of A) will be visited, because B is not in the traversal. (There are no A-objects, of course, because A is abstract.) It's not really a question of whether an inherited wrapper is called, but whether an object is being visited. > 'nother argument for this change is that it is clearly counter > intuitive that > > void Foo() to Bar { > before Bar {{ }} > } > > does call Bar's wrapper if it (Bar) is a concrete class, but not if it > is an alternation. Actually, this works just fine if Bar is an alternation class: Main ~ { A }. A : B | C. B = "b". C = "c". Main { Public static void main(String args[]) throws Exception (@ Main m = parse("b c b c"); System.out.println(m.count()); @) int count() to A { before A (@ return_val++; @) } } This program correctly prints "4". However, if you change the "to" to "to-stop", it prints "0". What's going on here is that the traversal is going down the alternation edge A => B and back up the inheritance edge B :> A (and the same for C). But when you say "to-stop A", it bypasses all outgoing edges from A, including the alternation edges. So B and C are not in the traversal graph, and thus the B-objects and C-objects are ignored. What's more, if you change the traversal to "bypassing C to A", the program prints "2", because it ignores B-objects but not C-objects. Maybe what we really need is to modify the semantics of "to-stop". How about instead of bypassing all outgoing edges, we only bypass outgoing construction edges, and if it's an alternation class, we also bypass all construction edges outgoing from any descendant class? For example, with this CD: Main ~ { A }. A : B | C common A. B = A. C = A. the strategy graph "to-stop A" would still visit the top-level B-objects and C-objects, but not any inner objects (or recursive links). How does this sound? --Doug