Hi Mira: some thoughts for our discussion. I like your view of adjustments as class-level description entities. You have good points against visitor objects. One problem with propagation patterns was that we had to be careful with name conflicts. Visitor objects solved that problem. Is it the same with adjusters? That is consistent with the propagation patterns view in earlier phases of the Demeter project. There the behavior modification was inserted directly into the classes. What Rondo suggests is that behavior modifications should be separated from traversals but represented as class level entities which are not instantiated. It is like using the visitor classes but not instantiating them; instead they are mentioned when an object is created as in: Container c = new Container(CHECK during S1); where CHECK is an adjuster and S1 a strategy for Container-objects. This does not deal with attachment of context objects to objects but I think that is much less important. In the new view we have: Adjusters parameterized by a strategy. Each adjuster defines an operation. Each adjuster contains several adjustments which modify classes in the scope of the strategy. What about init, return, start, final adjustments? We can modify a class with: Container { with A1 during to ...; with A2 during S2; ... } Where do we define the signatures of the new operations provided by the adjuster? Should it be: Container { R1 f1() with A1 during to ...; R2 f2() with A2 during S2; ... } -- Karl ============== From mira@ccs.neu.edu Mon Nov 17 13:41:52 1997 From: Mira Mezini Subject: our discussion To: lieber@ccs.neu.edu (Karl Lieberherr) Hi Karl: in the following you will find a modified version of the last mail you sent me on Friday. I have inserted my answers to your questions in the appropriate place in your mail. -- Mira ------------------ cut here --------------------- >From lieber@ccs.neu.edu Fri Nov 14 14:11:03 1997 To: mira@ccs.neu.edu Subject: strategies as arguments to visitors Status: RO Hi Mira: giving strategies as arguments to adjustments seems a good idea. we can use: strategy s or something like: strategy s = {A -> B, B -> {C,D}} and then we can refer to A,B,C and D in the adjustments. This will restrict the visitor to be used with strategies of the abive form. Here I view A,B,C,D as class-valued variables which are mapped to classes later. What is not yet clear to me is what the benefits are of giving up the "context" object idea. It seems natural to use the oo principles also for the behavior modifications? What is wrong with that? What are we losing? Answer (by Mira): ----------------- To my taste, using oo primciples means actually that we represent object modifications as classes (adjustments, subclasses, i.e. in general as description entities) and not as objects. This is what we actually do in the standard object-oriented languages: having a base class B, we model modifications of B in its subclasses, which are not objects. To my understanding, an object represents a real-world entity -- it has a well-defined, self-contained ("complete") behavior. On the other side, the behavior defined by a visitor class is inherently incomplete -- it is there to modify another existing complete behavior, the one specified in the base clas graph. So what is the behavior of a visitor object? Its behavior is "parametrized" by a complete behavior -- denoted by "super". As long as this "super" is not bound, the visitor is incomplete. So why should it be an object then? It is as if we would create instances of a subclass, while leaving the "super" parameter in the subclass undefined. This is at the conceptual level. In fact, I had the impression that you (+ Linda + Jens) totally agree me in your context relation paper, as I read the paper the first time. Read for example the left colomn of the second page in the TSE paper. By reading your paper, after our first email contact in January, I thought that, it was exactly the realization of a modification as method environment updates, i.e. at the as an operation on descriptions (classes) and not objects that make my work more related to yours than, e.g. to the work on composition filters. This intrepretation of mine shows through also in the related work part of my thesis, and of the ecoop paper: For example in the thesis, I write (pp. 125) last paragraph on AP: "More importantly, the evolution is not based on aggregation (I mean on modeling modifications as objects) as with MOPs, but on a method environment update. This is similar to our approach. This understanding of mine was also based on reading the operational semantics in Linda's thesis, which is based on Cardelli's object calculis where method environments (i.e. classes) are updated on a modification. It may be that I misinterpreted your argument (I guess because my way of thinking was strongly tuned toward this kind of interpretation). So what about pragmatic arguments? 1) Modeling visitors as objects increases the number of objects present in the system. 2) This is related to an increased number of message calls in order to delegate responsibility forth and back between the base object and its visistors. To make this concrete: consider the pricing and inventory visiting functionality on a computer equipment object structure. Consider the generated code for all_Equip_trv1(InventoryVisitor _v0, PricingVisitor _v1) in three classes: Equipment and Drive, which are related to each other by the following inheritance hierarchy: Drive extends Equipment The generated code is as follows: Equipment: --------- all_Equip_trv1(InventoryVisitor _v0, PricingVisitor _v1) {} //empty void allEquip_trv1_bef(InventoryVisitor __v0, PricingVisitor __v1) { __v0.before(this); __v1.before(this); } void allEquip_trv1_aft(InventoryVisitor __v0, PricingVisitor __v1) { } Drive: ------ void allEquip_trv1(InventoryVisitor __v0, PricingVisitor __v1) { allEquip_trv1_bef(__v0, __v1); super.allEquip_trv1(__v0, __v1); allEquip_trv1_aft(__v0, __v1); } void allEquip_trv1_bef(InventoryVisitor __v0, PricingVisitor __v1) { super.allEquip_trv1_bef(__v0, __v1); } void allEquip_trv1_aft(InventoryVisitor __v0, PricingVisitor __v1) { super.allEquip_trv1_aft(__v0, __v1); } Thus, when allEquip_trv1 will be sent to a Drive object, the following message calls will be made: Drive:: allEquip_trv1(InventoryVisitor __v0, PricingVisitor __v1) --> Drive::allEquip_trv1_bef(__v0, __v1); (1) --> Equipment (Drive's super)::allEquip_trv1_bef(__v0, __v1); (2) --> __v0.before(this); --> __v1.before(this); --> Equipment (Drive's super)::allEquip_trv1(__v0, __v1); (3) --> Drive::allEquip_trv1_aft(__v0, __v1); (4) --> Equipment (Drive's super)::allEquip_trv1_aft(__v0, __v1); (5) In this chain of message calls all enumerated message calls are unnecessary. They are (partly) the result of encapsulating the visiting functionality in a separate object, instead of attaching it to the basic functionality. In adition, additional dispatching on the type of "this" is needed at the visitor site. When the same modification is modelled by class (adjustment, or code attachement) the message call chain will be as follows: Drive(Equipment)::allEquip() --> Pricing1::allEquip_trv1(renamed from change) --> Invent(super of Pricing)::allEquip_trv1(renamed from change) --> Equipment::allEquip_trv() which is empty How can we add data members to the traversed data for duration of a call? Answer(by Mira): ---------------- I hope, I have a correct understanding of what is meant by a "modification for the duration of a call". Anyway, to my understanding this shouldn't be a problem. We could chose one of the following when the generator encounters the statement: Container c = new Container{CHECK during S1} a) add methods for creating objects declared by each of entry adjustments and those that will affect the Container class. These objects are cretated outside c (the container object being created), and will be passed as parameters to the other adjustments in the adjuster that affect the behavior of source classes in the strategy graph (e.g. total_sum parameter of the sum adjustment) Since they are created outside c, the adjustment-declared objects like total_sum, or initial, or stack, ...etc, will exist only for the duration of the call. b) the other strategy would be to make the extension of the c object with variables declared in adjustments contained in the CHECK adjuster permanent. In that case the generator adds automatically declarations for these variables in the subclass of Container which it creates automatically (see notes in the last mail) of which c will be creted as an instance of. We could decide to add a syntactic notation for making this choice explicit (i.e. for allowing the programmer to declare what he/she wants). -- Karl Example 2: Container -------------------- For the container example the summing adjuster above can be reused. The strategy now is S1 = from Container to Weight; adjuster CHECK(strategy s) adjustment SummingEntry { (@ int total_sum; @) init (@ total_sum = 0; @) return (@ total_sum @) } adjustment Sum(int total_sum) { change target(s) (@ total_sum += this.value; super(); @)} adjustment Initial modifies Summing { (@ int initial; Stack stack; @) change source(s) (@ stack.push(this.total_sum); super(); initial = (Integer) stack.pop(); @) } adjustment Checking modifies Initial { (@ int violations;@) change source(s) (@ System.out.println(" start new container "); super(); integer initial = this.initial; int cap = this.get_capacity().get_i().intValue(); int diff = total_sum.intValue() - initial.intValue(); if (diff > cap) { violations = new Integer(violations.intValue() + 1); // this.set_violations(new Integer(violations.intValue() + 1)); System.out.println(" total weight " + diff + " but limit is = " + cap + " OVER CAPACITY "); }; System.out.println(" end container "); } Container c = new Container{CHECK during S1}. c.checking(). USING CLASS VALUED VARIABLES -------------------- S1 = from Container, Weight adjuster CHECK(strategy s = {Container -> Weight}) adjustment SummingEntry { (@ int total_sum; @) init (@ total_sum = 0; @) return (@ total_sum @) } adjustment Sum(int total_sum) { change Weight (@ total_sum += this.value; super(); @)} adjustment Initial modifies Summing { (@ int initial; Stack stack; @) change Container (@ stack.push(this.total_sum); super(); initial = (Integer) stack.pop(); @) } adjustment Checking modifies Initial { (@ int violations;@) change Container (@ System.out.println(" start new container "); super(); integer initial = this.initial; int cap = this.get_capacity().get_i().intValue(); int diff = total_sum.intValue() - initial.intValue(); if (diff > cap) { violations = new Integer(violations.intValue() + 1); // this.set_violations(new Integer(violations.intValue() + 1)); System.out.println(" total weight " + diff + " but limit is = " + cap + " OVER CAPACITY "); }; System.out.println(" end container "); } Container c = new Container{CHECK during S1}. c.checking().