[comp.lang.eiffel] Simple Eiffel Feature Call Question

ajk@wren.cs.rmit.OZ.AU (Alan Kent) (05/07/91)

This may be a naive question, but I dont have a compiler installed here
at the moment to experiment with. I am having a little trouble working out
how to handle each of the following three cases in Eiffel.

Consider three classes A, B and C where (the syntax might not be quite right)
    class A export f ...
    class B export f inherit A redefine f ...
    class C export f inherit B redefine f ...

Assume Current is an instance of class C. In feature f of class B I would
like to be able to...
(1) Call version C of method f (the definition in the class of the object)
(2) Call version B of method f (the definition in the current class)
(3) Call version A of method f (the parent's definition)

As I understand Eiffel, it will always do approach (1).

The reason I would like to be able to do (2) is for recursion. I dont want my 
definition of a recursive algorithm in B to be changed by some decendant class.
The only way I can think of doing this is to make f call another feature
f_secret where f_secret is not exported (and so can be recursive safely).

The reason I would like to be able to do (3) is that my decendant class
just wants to add a little bit more to the parent class routine definition.
The only way I can see of doing this is by copying the parent routine
definition in the child with the few extra lines I want added. In other
languages that I know of this is achieved using a keyword like "super".
Renaming does not seem to do what I want
eg: class C export f, B_f inherit B rename f as B_f
as I can no longer invoke version C of feature f when the type is B or A
(ie. its no longer a redefine).

Am I missing something, in particular with case (3)? Thanks.

Alan Kent
ajk@goanna.cs.rmit.oz.au

rick@tetrauk.UUCP (Rick Jones) (05/08/91)

In article <5583@goanna.cs.rmit.oz.au> ajk@wren.cs.rmit.OZ.AU (Alan Kent) writes:
>This may be a naive question, but I dont have a compiler installed here
>at the moment to experiment with. I am having a little trouble working out
>how to handle each of the following three cases in Eiffel.
>
>Consider three classes A, B and C where (the syntax might not be quite right)
>    class A export f ...
>    class B export f inherit A redefine f ...
>    class C export f inherit B redefine f ...
>
>Assume Current is an instance of class C. In feature f of class B I would
>like to be able to...
>(1) Call version C of method f (the definition in the class of the object)
>(2) Call version B of method f (the definition in the current class)
>(3) Call version A of method f (the parent's definition)
>
>As I understand Eiffel, it will always do approach (1).

Correct in the normal case, although I think your terminology is a little
confused.

Approach (3) is quite easy - so I'll answer that first.  This requires one of
the less obvious combinations of renaming and redefinition.  In class B you
must declare:

class B
inherit A
	rename f as a_f
	redefine f

The redefinition of f works the same is if the rename were not there, but the
rename means that the parent feature is also available under its new name.
(this is one of the aspects which is going to be considerably tidied up under
version 3 - it's caused much confusion for many people)  Thus the new
definition of f will be something like:

	f is
	do
		a_f ;
		-- additional local code
	end

Now the more difficult bit :-)  Your problem statement seems to confuse the
type of a variable with the type of an object.  Take for example:

	var_b: B ;	-- variable of type B
	var_c: C ;	-- variable of type C

	var_b.Create ; var_c.Create ;	-- create objects of the two types

(1)	var_b.f ;	-- applies feature f of class B to object of type B
(2)	var_c.f ;	-- applies feature f of class C to object of type C

	var_b := var_c ; -- assigns object of type C to variable of type B
(3)	var_b.f ;	-- applies feature f of class B to object of type C

In statements (1) & (2), the class of the variable and the object are the same,
so the result is clear: in (1) the feature f as coded in class B is called, in
(2) the one coded in class C is called.

In (3) however, dynamic binding has to be considered.  Although the variable is
declared of class B, the actual object is type C.  The actual feature called is
the one which C inherits as f, and redefines.  In other words, the code for
feature f in class B is never invoked.

It is a basic property of Eiffel that a class cannot prevent a descendant from
redefining any of its features.  This includes non-exported as well as exported
ones.  There is a presumption that inheritance is used responsibly, and
assertions (especially the class invariant, which cannot be overridden, only
added to) can trap any gross violations which a descendant may try to perpetrate.

One approach to recursion or iteration where the algorithm is always the same,
but the actual operation on each loop may need to be different in descendants,
is to have the recursion feature call a local routine on each loop.  This
routine would probably not be exported and can be empty, or even deferred, in
the class which defines the recursion.  Descendants then only need to define
(or redefine) this routine, and it will only be required to deal with one
instance on each call.

-- 
Rick Jones, Tetra Ltd.  Maidenhead, Berks, UK
rick@tetrauk.uucp

Any fool can provide a solution - the problem is to understand the problem

sakkinen@jyu.fi (Markku Sakkinen) (05/10/91)

In article <1160@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:
>In article <5583@goanna.cs.rmit.oz.au> ajk@wren.cs.rmit.OZ.AU (Alan Kent) writes:
>> ...
>>Consider three classes A, B and C where (the syntax might not be quite right)
>>    class A export f ...
>>    class B export f inherit A redefine f ...
>>    class C export f inherit B redefine f ...
>>
>>Assume Current is an instance of class C. In feature f of class B I would
>>like to be able to...
>>(1) Call version C of method f (the definition in the class of the object)
>>(2) Call version B of method f (the definition in the current class)
>>(3) Call version A of method f (the parent's definition)
>>
>>As I understand Eiffel, it will always do approach (1).
>
>Correct in the normal case, although I think your terminology is a little
>confused.

Yes, the intent would be clearer if both 'feature f' and 'method f'
above were substituted by 'routine f'.

>Approach (3) is quite easy - so I'll answer that first.  This requires one of
>the less obvious combinations of renaming and redefinition.  In class B you
>must declare:
> ...  [A good explanation of how (3) is indeed possible, contrary to
        Kent's doubt.]

>Now the more difficult bit :-)  Your problem statement seems to confuse the
>type of a variable with the type of an object.  Take for example:
> ...

No!  Variables were not even mentioned in the original posting.
You did not quote the salient paragraph in it:

-The reason I would like to be able to do (2) is for recursion. I dont want my 
-definition of a recursive algorithm in B to be changed by some decendant class.
-The only way I can think of doing this is to make f call another feature
-f_secret where f_secret is not exported (and so can be recursive safely).

This is a perfectly reasonable wish.  I regard it as a definite defect in
Eiffel (and Smalltalk and many other OOPL's) that there is _no way_
to achieve it.  The following part of Jones' posting tells why even
the work-around suggested above will not give absolute security - although
it will work if all descendant class programmers respect the intent
of 'f_secret'.

>It is a basic property of Eiffel that a class cannot prevent a descendant from
>redefining any of its features.  This includes non-exported as well as exported
>ones.  There is a presumption that inheritance is used responsibly, and
>assertions (especially the class invariant, which cannot be overridden, only
>added to) can trap any gross violations which a descendant may try to perpetrate.

>One approach to recursion or iteration where the algorithm is always the same,
>but the actual operation on each loop may need to be different in descendants,
>is to have the recursion feature call a local routine on each loop.  This
>routine would probably not be exported and can be empty, or even deferred, in
>the class which defines the recursion.  Descendants then only need to define
>(or redefine) this routine, and it will only be required to deal with one
>instance on each call.

This suggestion can be relevant in several situations, but it does not help
a bit in the exact problem posed by Kent.  Read it as a side note.

BTW, even a slight generalisation of the original problem is relevant,
and a "perfect" OOPL should be able to support it:
In a routine f of class B, invoke different versions of routine g
(possibly _different_ from f):  (1) from the (most specific) class of
the current object,  (2) from the same class,  (3) from ancestor class.

Among well-known OOPL's, the explicit class qualification possibility
of C++ allows both (2) and (3).  For case (3), it is clumsier than
'super' in Smalltalk and many other languages.  The Eiffel way of
renaming the ancestor class routine looks even clumsier in simple cases,
but is probably better for reuse: e.g. if a routine can be copied
(possibly with small modifications) from a class to another,
unrelated class.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)