[comp.lang.eiffel] Posting on behalf of CNET users Answer, part 1

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