bertrand@eiffel.UUCP (Bertrand Meyer) (03/10/89)
From <112@eiffel.UUCP>, reposted by me but originally from Jean-Michel Cornily (CNET, Lannion, France, uunet!mcvax!cnetlu!cornily): > We would like to apply routines on sets of objects, but there seems to > have problems with types. [We would like to write:] > > class A > feature > > set_do_all (action : X) is > do > from set.start > until set.offright > loop > set.value.action; > set.forth; > end; > end ; -- set_do_all > > end; -- class A > > Our problem here is to determine what is X (the type of action). Is there > a type "routine" ? If not, we cannot define such a routine. To achieve the desired effect, declare ``action'' not as a routine argument which is itself of type ``routine'', but as a routine in class VALTYPE, where VALTYPE is the type of the set elements (that is to say, the type of ``value'' in the extract given). The extract remains exactly as it is, except that the ``action'' formal argument disappears: ------------------------------------------------------------------------ | class A | feature | | set_do_all is | do | -- THE REST AS BEFORE ------------------------------------------------------------------------ For reusability and generality, this scheme must support more than one possible type of ``action'' and more than one possible variant of ``VALTYPE''. To achieve this, declare VALTYPE as a deferred class and ``action'' as one of its deferred routines (a procedure in the example given). Then ``value'' in class A should be declared of type VALTYPE; actual set ``value''s will be references to instances of non-deferred descendants of class VALTYPE. Each such descendant provides its own version of ``action''; dynamic binding ensures that the appropriate version is automatically selected in each case, based on the types of the actual set elements. The cleanest way to proceed is to declare A as a generic class, whose generic parameter is constrained to be a descendant of VALTYPE. This is written as follows (calling the class ACTIVE_SET rather than A): --------------------------------------------------------------------- | class ACTIVE_SET [V -> VALTYPE] export | | ... do_all, ... | | feature | | value: V is ... (could be a function or attribute | depending on the implementation chosen) | | do_all is | -- Apply ``action'' to all set elements | do | from | set.start | until | set.offright | loop | set.value.action; | set.forth; | end; | end; -- do_all | | ... Other features ... | | end -- class ACTIVE_SET --------------------------------------------------------------------- Actual generic parameters provided for V when ACTIVE_SET is actually used are constrained to be descendants of VALTYPE. Constrained genericity is not described in ``Object-Oriented Software Construction'' (although it was more or less announced in exercise 19.5, page 422); the precise specification of the mechanism is given in the Eiffel User's Manual, section 4.8, pages 38-40. As a minor observation about the recommended style for choosing names, it usually preferable *not* to qualify a routine name by the type of objects to which it applies if this type is just the class where the routine appears. In the above case, ``do_all'' (rather than ``set_do_all'') suffices. Any other class (say a LIST) may have a routine with the same name. This is a form of name overloading; it helps provide consistent, mnemonic class interfaces. Bertrand Meyer bertrand@eiffel.com
jwg1@bunny.gte.com (James W. Gish) (03/16/89)
Bertrand, I think you missed one part of the problem: what if I want to have more than one action that may be applied to one type. For example, "action" may be either "double" or "halve" or "triple" on sets of integers. I want to be able to do this without repeating the basic form of the iteration through the set. -- Jim Gish GTE Laboratories, Inc., Waltham, MA CSNET: jgish@gte.com UUCP: ..!harvard!bunny!jwg1
florman@randvax.UUCP (Bruce Florman) (03/18/89)
In article <JWG1.89Mar15114250@bunny.gte.com> jwg1@bunny.gte.com (James W. Gish) writes: >Bertrand, I think you missed one part of the problem: what if I want >to have more than one action that may be applied to one type. For example, >"action" may be either "double" or "halve" or "triple" on sets of integers. > >I want to be able to do this without repeating the basic form of the >iteration through the set. >-- >Jim Gish >GTE Laboratories, Inc., Waltham, MA >CSNET: jgish@gte.com UUCP: ..!harvard!bunny!jwg1 The class defined below implements a variation on the solution that I proposed last week. It knows how to apply an arbitrary action to every item in a list. DEFERRED CLASS APPLICATOR [T] EXPORT apply_to_all FEATURE apply_to_one (item: T): T IS DEFERRED END; apply_to_all (set: LIST [T]) IS REQUIRE NOT set.Void; DO FROM set.start UNTIL set.offright LOOP set.change_value(apply_to_one(set.value)); set.forth; END; END; END -- CLASS APPLICATOR Now, in order to implement your "actions" you will define heirs to APPLICATOR and define apply_to_one routines which do the appropriate thing. For example, a class that knows how to perform your doubling action would look like this: CLASS INT_DOUBLER EXPORT apply_to_all INHERIT APPLICATOR [INTEGER] FEATURE apply_to_one (item: INTEGER): INTEGER IS DO Result := 2 * item END; END -- CLASS INT_DOUBLER To use this class you will have to declare some entities like these somewhere in your system: doubler: INT_DOUBLER; list1: LINKED_LIST [INTEGER]; list2: FIXED_LIST [INTEGER]; And you will then have instructions like the following somewhere within your system's executable code: doubler.apply_to_all(list1); doubler.apply_to_all(list2); As I said before, creating a whole new class to perform each action is a more verbose solution than that which could be achieved in languages which support first class routines. However, since these classes will each be quite small, it's not all that bad. Disclaimer: I ran these two classes through the compiler and got no error messages, but I haven't bothered to use them anywhere. -Bruce Florman florman@rand.org
bertrand@eiffel.UUCP (Bertrand Meyer) (03/20/89)
From <JWG1.89Mar15114250@bunny.gte.com>, jwg1@bunny.gte.com (James W. Gish): > Bertrand, I think you missed one part of the problem: what if I want > to have more than one action that may be applied to one type. For example, > "action" may be either "double" or "halve" or "triple" on sets of integers. > > I want to be able to do this without repeating the basic form of the > iteration through the set. Mr. Gish is right. I missed part of the problem. This message introduces the generalization of the technique described in my previous posting <114@eiffel.UUCP> to the case he mentions. *Repeated* inheritance is the mechanism precisely meant for such cases. If you want to have the same pattern applied to different operations, then you can simply inherit twice or more from the class that describes the common pattern, and use renaming to distinguish between variations on this pattern. Repeated inheritance is discussed in section 11.6 of ``Object-oriented software construction''. Consider a class NEW which inherits from a class ANCESTOR in more than one way, directly or indirectly (e.g. NEW could list ANCESTOR more than once in its own inheritance clause, or it could have two or more parents that are themselves descendants of ANCESTOR.) Consider now a feature f of ANCESTOR. Its status in NEW is determined by the following rule (p. 277 of OOSC): - If f has not been renamed along any of the inheritance paths between NEW and ANCESTOR, then f is considered to represent just one feature of NEW (*sharing* case). - Any renaming of f along any of the inheritance paths yields a separate version of f in NEW (*duplication* case.) Warning: In release 2.1, this is correctly implemented for routines only, not attributes (as explained on p. 97 of the User's Manual). Release 2.2 treats all cases correctly. For the case under discussion only routines are needed, so this is not a problem. This possibility provides the flexibility required by the problem under discussion, as shown by the example below. For simplicity I have written an iterator mechanism in which the iterated action simply identifies itself (``I am variant number n''). First the general iteration mechanism is described by a class GENERAL which, inevitably, is deferred. Note, however, that the iterator routine, called ``iteration'', is *not* deferred. This is typical of the use of partially deferred classes for ``CAPTURING COMMON BEHAVIORS'', emphasized (again) in a previous posting. --------------------------- BEGIN: GENERAL ----------------------------- -- Instances of this class represent objects -- to which an iteration is applicable. -- The iteration scheme and the action -- to be performed at each step -- are left unimplemented; they must -- be filled in by descendants. deferred class GENERAL export action feature action is -- The action to be performed at each iteration step deferred end; -- action advance is -- Move on to next step of iteration deferred end; -- advance start is -- Begin iteration deferred end; -- start over: BOOLEAN is -- Is iteration over? deferred end; -- over iteration is -- Perform a complete iteration do from start until over loop action; advance end end -- iteration end -- class GENERAL --------------------------- END: GENERAL ----------------------------- Next, we have an effective (= non-deferred) class SPECIFIC which, as Mr. Gish suggests, needs two variants of the iteration mechanism, represented by two different implementations of routine ``action''. To achieve this, SPECIFIC inherits twice from GENERAL and ensures that the two variants are renamed differently (action1 and action2). Then each variant is defined to a different algorithm. The commonality of the iterator mechanism remains, however, because routine ``iteration'' has not been renamed and hence is shared. Note the flexibility of the mechanism. In this example, everything is shared except ``action'', which is duplicated. However we could also, if necessary, have duplicated some of the iteration controls (``start'', ``over'', ``advance''), or even ``iteration'' (which could be redefined in one case but not in the other). --------------------------- BEGIN: SPECIFIC ---------------------------- -- Instances of this class represent objects -- to which TWO different iterations are applicable. -- These are accessible through exported routines -- iteration1 and iteration2. -- The iteration scheme is the same, but the action -- to be performed at each step is different. class SPECIFIC export iteration1, iteration2 inherit GENERAL rename action as action1, iteration as iteration1 redefine action1; GENERAL rename action as action2, iteration as iteration2 redefine action2; STD_FILES feature i: INTEGER; -- Iteration control minval: INTEGER is 1; maxval: INTEGER is 5; -- Iteration bounds advance is -- Move on to next step of iteration do i := i+1 end; -- advance action1 is -- Identify the action as number 1 do putstring ("This is action 1 executed for i = "); putint (i); new_line end; -- action1 action2 is -- Identify the action as number 2 do putstring ("This is action 2 executed for i = "); putint (i); new_line end; -- action2 start is -- Begin iteration do i := minval end; -- start over: BOOLEAN is -- Is iteration over? do Result := (i > maxval) end; -- over end -- class SPECIFIC --------------------------- END: SPECIFIC ----------------------------- To exercise the above two classes, we build a small system with the following class, called SYSTEM, as root: --------------------------- BEGIN: SYSTEM ----------------------------- class SYSTEM feature Create is do try end; -- Create try is local s: SPECIFIC do s.Create; s.iteration1; s.iteration2 end -- try end -- class SYSTEM --------------------------- END: GENERAL ----------------------------- We assemble the system (compilation is reproduced below) and execute the resulting executable file, ``system''. Here is the result: --------------------------- BEGIN: EXECUTION --------------------------- This is action 1 executed for i = 1 This is action 1 executed for i = 2 This is action 1 executed for i = 3 This is action 1 executed for i = 4 This is action 1 executed for i = 5 This is action 2 executed for i = 1 This is action 2 executed for i = 2 This is action 2 executed for i = 3 This is action 2 executed for i = 4 This is action 2 executed for i = 5 --------------------------- END: EXECUTION ---------------------------- This is the desired effect. The system's assembly (compilation plus linking) had proceeded as follows. You had typed es system and got the following: --------------------------- BEGIN: SYSTEM ASSEMBLY --------------------- Eiffel configuration manager (version 2.1) Pass 1 on class system Pass 1 on class specific Pass 1 on class general Pass 2 on class system Pass 2 on class general Pass 2 on class specific "specific", 13: Warning: Routine action1 was deferred; no need to include it in redefine clause. "specific", 16: Warning: Routine action2 was deferred; no need to include it in redefine clause. Repeated inheritance - Shared feature: advance (Original feature: advance in class general ) Repeated inheritance - Shared feature: start (Original feature: start in class general ) Repeated inheritance - Shared feature: over (Original feature: over in class general ) Repeated inheritance - Duplicated feature: iteration2 (Original feature: iteration in class general ) Pass 3 on class system Pass 3 on class general Pass 3 on class specific Pass 4 on class system Pass 4 on class general Pass 4 on class specific C-compiling system C-compiling general C-compiling specific Generating and C-compiling temporary main file. Linking system (9 classes) es: System assembly complete --------------------------- END: SYSTEM ASSEMBLY ---------------------- Two comments here: - Uses of repeated inheritance give rise to warnings. I feel uneasy about warnings in general, because of an argument that goes somewhat like this: either there is an error and you cannot compile; or there is no error and you shut up. Pragmatically, however, it seems useful to tell the user something in cases like the one above, where he is using an advanced feature of the language, repeated inheritance. He could be using it inadvertently, in which case the message will serve as an alert; if he is using it on purpose, information on whether a feature is shared or duplicated should be useful for double-checkibg. In subsequent versions of Eiffel (not 2.2) only a short message will be displayed, and the user will be able to click on an ``EXPLAIN'' button to see the detailed messages in another window. - Class SPECIFIC lists action1 and action2 in its redefine subclauses. It is not necessary to explicitly include deferred routines in such clauses, hence the message. But in version 2.1 duplication in repeated inheritance will not work for deferred routines unless you do list them as redefined. This was a bug (blush) which has been corrected for 2.2. If you use the above technique with 2.1, you should include the ``redefine'' for deferred routines duplicated under repeated inheritance, such as action1 and action2, even though they were deferred in the first place. (Also, remember not to try duplication for attributes in 2.1.) -- Bertrand Meyer bertrand@eiffel.com