SourceForge.net LogoThe Eiffel Compiler / Interpreter (tecomp)

doc/papers/lang/improved non conforming inheritance

Outdated proposal for non conforming inheritance

Abstract

In Eiffel there are two possibilities to inherit: Conforming and non-conforming. Non-conforming inheritance is a more restricted form of inheritance which cuts off polymorphic attachments from descendant objects to entities of a non-conforming parent.

Since a polymorphic attachment is one necessary condition to get a catcall (the other is covariant redefinition of an argument or export restrictions of a feature), non-conforming inheritance can be used to mitigate the catcall problem in Eiffel.

The proposal below describes some minor extensions to the language rules which make non-conforming inheritance a more powerful tool to combat catcalls. The extensions of the language rules should not break any existing code.

Basics of inheritance

Why do we use inheritance?

Let's assume that type TD is a descendant of type TB. If the inheritance path from TD to TB is conforming. This gives us the following:

  1. The class TD has all features of TB (maybe renamed).
  2. If due to repeated inheritance a feature of TB is replicated in TD (i.e. there is more than one version in TD), proper selects guarantee that one of the replica is the primary version (in ECMA Eiffel talk: the dynamic bind version).
  3. A feature from TB is inherited with all its clients or more (according to ECMA Eiffel an inherited feature can only win clients and not loose).
  4. The type TD can be used as an actual generic in a class CG which has the specification class CG[G->TB] ... end.
  5. An object of type TD can be attached to an entity of type TB.

If there is no conformant inheritance path between TD and TB, we get the following:

  1. Same as with conforming inheritance.
  2. Not enforced by the language rules, but not forbidden either.
  3. ECMA Eiffel allows export restrictions with non-conformant inheritance
  4. Not possible with non-conforming inheritance. If TD has only non-conforming inheritance paths to TB and CG has the specification class CG[G->TB] ... end then CG[TD] is not a valid type.
  5. Not possible.

The basic idea of the proposal: Make #4 in some way valid with non-conforming inheritance and forbid only #5.

Definition: "behaves like"

We say a type TD behaves like a type TB if and only if:

  1. TB is an ancestor type of TD.
  2. If due to repeated inheritance a feature of TB is replicated in TD (i.e. there is more than one version in TD), proper selects guarantee that one of the replica is the primary version.
  3. All primary versions of features of TB in TD have the same or more clients as their precursors in TB (i.e. not export restrictions).

Clearly all types TD conforming to TB also behave like TB in the sense of the above definition.

The important thing: There is a one to one correspondence between all features of TB and their corresponding primary version in TD.

Definition: Behavioural constraint

The class text

class CG[G -> like TB]
    ...
end
 

constrains any actual generic for the formal generic G to behave like TB as opposed to conforms to TB with the usual syntax class CG[G->TB].

Because of the one to one correspondance between the features of TB and the type of the actual generic, it is always clear which feature to call. Because export restrictions are not allowed with behaves like, for all features from TB available to CG the corresponding features in the actual generic are available to CG as well.

Note: With the above declaration the formal generic G does not conform to TB, it behaves like TB and conforms (due to the universal conformance principle) only to ANY.

Note: Multiple constraints like class CG[G->{like TB,A}] do not seem to cause problems. G behaves like TB and conforms to A and any actual generic has to behave like TB and has to conform to A.

What can be done with that?

First of all, the above extension does not break any code and it opens up some possibilities to avoid catcalls.

The possibilities can be best explained with an example using some kernel library classes. Note that the modifications of the kernel library types are not part of the proposal, they are used for illustration purposes.

In current Eiffel you can write the valid code

   c,d: COMPARABLE
 
   ...
 
   c := "My string"
   d := 100
   if c < d then  -- catcall!
      ...
   end
 

which gives a runtime error at execution, because the routine is_less alias "<" of the class STRING expects an object of type STRING to be compared with and the above code gives it an INTEGER (which does not conform to STRING).

Actually it is not a good idea to assign descendants of COMPARABLE to entities of type COMPARABLE because any feature call (feature from COMPARABLE) will result in a catcall, if different descendants are attached to the entities.

Non-conforming inheritance would allow us to make the above code invalid. E.g. the declarations

class STRING  inherit ANY inherit {NONE} COMPARABLE ... end
 
class INTEGER inherit ANY inherit {NONE} COMPARABLE ... end
 

do not allow the above code to compile.

For simple classes this would be ok. But with some generic classes the non-conformant inheritance has some bad consequences. E.g. the class

class SORTED_LIST[G->COMPARABLE]  ... end
 

would make the type SORTED_LIST[STRING] an invalid type.

If you use the behavioural constraint

class SORTED_LIST[G -> like COMPARABLE]  ... end
 

instead of the usual constraint, the type SORTED_LIST[STRING] is again a valid type and fully usable. You can put any object of type STRING or conformant descendant of STRING into the sorted list.

The following is invalid

   lc: SORTED_LIST[COMPARABLE]
   ls: SORTED_LIST[STRING]
   ...
   lc := ls   -- invalid: STRING does not conform to COMPARABLE!
 

You can design conformance better suited to your needs. E.g. if you have the types E and F where an object of type E can be safely compared to an object of type F you can use the inheritance hierarchy

class B inherit ANY inherit {NONE} COMPARABLE ... end
class E inherit B ... end
class F inherit B ... end
 

with the consequences

   lb: SORTED_LIST[B]
   ...
   lb.extend ( create E.make( ... ) ) -- valid
   lb.extend ( create F.make( ... ) ) -- valid
 
   lb.extend ( "My string" ) -- invalid, because STRING does not conform to B
   ...
 

Summary

The behavioural conformance as a restricted form of conformance gives the user more possibilities to express his intentions. He has more possibilities to use non-conformant inheritance without bad consequences in generic types.

With the extended use of non-conformant inheritance the unwanted polymorphic attachs can be ruled out, especially for classes similar to COMPARABLE and NUMERIC having deferred (or effective) features like

class C feature
  f ( a: like Current )
  ...
end
 

To the best of my knowledge the introduction of the extended language rules will not break any code.

The introduction of inheritance relations used above for INTEGER and STRING might break code. But if INTEGER and STRING are left as before, no bad consequences arise, but the advantages of this proposal cannot be exploited for these classes either (but maybe for others).

A migration strategy:

Discussion

Feel free to comment on that topic at http://sourceforge.net/projects/tecomp/forums/forum/1034481.

 Local Variables: 
 mode: outline
 coding: iso-latin-1
 outline-regexp: "=\\(=\\)*"
 End:
Table of contents

- Abstract

- Basics of inheritance

- Definition: "behaves like"

- Definition: Behavioural constraint

- What can be done with that?

- Summary

- Discussion


ip-location