Without /except
Research: This page describes research about Eiffel, not the actual language specification.
Contents
Introduction
By allowing covariant feature redefinition and hiding of features, the Eiffel language introduces the problem of cat-calls (Changed Availability or Type). The restrict mechanism prevents this by introducing new derived types whenever a feature is covariantly redefined or the export status is restricted. These derived types are then used to prevent cat-calls through the conformance rules to the original types.
This mechanism is a variant on forget/keep as originally envisaged by Mark Howard. This variant was worked out by us when he explained to me his ideas about forget/keep.
Example classes
The following classes will be used for illustration:
class ANIMAL feature eat (f: FOOD) sleep end |
class CAT inherit ANIMAL redefine eat export {NONE} sleep end feature eat (f: CAT_FOOD) end |
class FOOD end class CAT_FOOD inherit FOOD end |
class LIST [G] feature has (g: G): BOOLEAN put (g: G) item: G end |
The problem
The problem of cat-calls has two variants, one because a feature is covariantly redefined:
local a: ANIMAL c: CAT food: FOOD do a := c -- illegal since `eat' for type CAT takes arguments only of type CAT_FOOD a.eat (food) end
And one because the visibility of features change:
local a: ANIMAL c: CAT do a := c -- `sleep' is hidden for type CAT and should not be called a.sleep end
The idea
To solve the problem of cat-calls, the idea is that whenever you covariantly redefine a feature of a parent type, you actually inherit from a (virtual) parent type which does not have this feature at all, but you then re-introduce the feature to the new class (with restricted export status). The same goes for changing the export status.
As a consequence of this, the types as we know them don't necessarily conform to each other. If the type CAT
inherits from the type ANIMAL
but covariantly redefines the feature eat
then the type CAT
does not conform to ANIMAL
anymore, but only to a type ANIMAL
without the feature eat
.
Restrict mechanism
The restrict mechanism introduces a new keyword restrict
which can be used to covariantly redefine features. By removing features from the class interface, a new type is introduced. The new type has all the features that the original type had, except those listed in the restrict clause - these latter features are present in the class, but exported to {NONE}. If restrict all
is used, all features in the original will be removed from the new type. This produces a type with no features at all, which isn't very useful on its own. But it is useful if you want to lose all except a few of the features. We introduce another new keyword except
which can only follow restrict all
(therefore it does not need to be a completely reserved word).
Default behavior
In the restrict mechanism the normal declaration of a type will have all the features of a type from which it inherits. This implies that all subtypes which reduce an export status or covariantly redefine a feature don't conform to this type anymore.
local -- normal declaration means we have all the features animal: ANIMAL cat: CAT do -- legal since the feature `eat' is in the normal type animal.eat (food) -- legal since the feature `sleep' is in the normal type animal.sleep -- illegal since CAT does not conform to ANIMAL anymore. -- N.B. The declaration of CAT given above is illegal under this proposal. -- Adding the restrict keyword will make it legal again, and emphasize the lack of conformance. animal := cat end
Syntax
-- normal declaration is an object with all features and no -- subtype which has a covariantly redefined feature conforms to this type a1: ANIMAL -- a type which lacks any exported features, thus all -- subtypes conform to this type. It is not a very useful type. a2: ANIMAL restrict all end -- a type which only restricts the features `eat' and `sleep'. All subtypes which only redefine or change export status -- of `eat' or `sleep' will conform to this type a3: ANIMAL restrict eat export {NONE} sleep end -- a type which lacks any exported features other than eat. a4: ANIMAL restrict all except eat end
Covariant feature redefinition
Cat-call problem
With covariant feature redefinition you run into cat-call problems as this example shows:
local a: ANIMAL c: CAT food: FOOD do a := c -- eat for type CAT takes arguments only of type CAT_FOOD a.eat (food) end
Restrict mechanism
Using the restrict mechanism the default behavior for a type is to keep all features. Types which want to have covariant redefined features will not conform to the original type. You do not mention these features in a redefine clause.
Now the cat-call example with the new restrict types:
local a: ANIMAL c: CAT do -- illegal assignment, ANIMAL and CAT don't conform -- since CAT inherits from ANIMAL restrict eat a := c a.eat (food) end
Feature hiding
Cat-call problem
By restricting the export status of features, a cat-call problem is introduced:
local a: ANIMAL c: CAT do a := c -- sleep is hidden for type CAT and should not be called a.sleep end
Restricting the export status
Types which reduce the export status of features will not conform to the original type.
Now the cat-call example:
local a: ANIMAL c: CAT do -- illegal assignment, ANIMAL and CAT don't conform a := c a.sleep end
local a: ANIMAL export {NONE} sleep end c: CAT do -- legal, CAT conforms to ANIMAL export {NONE} sleep a := c -- illegal, ANIMAL export {NONE} sleep doesn't export feature sleep a.sleep end
Note the declaration of a. It is desirable that any sort of inheritance restriction (such as listing creation procedures) be allowed on declarations.
Generics
Conformance of generic classes (two generic classes conform if their base classes conform and their generic parameters conform) introduces another kind of covariantly redefined features. Every feature which has a generic argument, e.g. put (g: G)
in class LIST [G]
can be regarded as covariantly redefined since put
in LIST [ANY]
takes an argument of type ANY
and put
in LIST [ANIMAL]
takes an argument of type ANIMAL
. But LIST [ANIMAL]
conforms to LIST [ANY]
, thus the feature put
is actually covariantly redefined in LIST [ANIMAL]
.
Cat-call problem
The conformance rules of generics also introduce cat-call problems:
local -- feature `put' has argument type ANY any_list: LIST [ANY] -- feature `put' has argument type ANIMAL animal_list: LIST [ANIMAL] do -- animal_list conforms to any_list, but arguments of the feature -- `put' are different any_list := animal_list -- since any type can be put into any_list this will add -- an integer to the animal_list any_list.put (5) end
Conformance
A generic class X [G] conforms to another generic class X [H] if G and H are the same class, or if G conforms to H and H does not export any features taking generic arguments, or any features that in turn (recursively) make use of features taking generic arguments.
Using restrict mechanism
local any_list: LIST [ANY] animal_list: LIST [ANIMAL] do -- illegal since animal_list does not conform to any_list anymore any_list := animal_list any_list.put (5) end
local any_list: like usable_list animal_list: LIST [ANIMAL] animal: ANIMAL do -- legal since LIST [ANIMAL] has `put' animal_list.put (animal) -- legal since animal_list conforms to `usable_list' any_list := animal_list -- illegal since `usable_list' does not have `put' any_list.put (animal) -- illegal, since `usable_list' does not have `has' any_list.has (animal) end feature {NONE} -- Anchors usable_list: LIST [ANY] restrict put, has, append, etc. etc. end -- Anchor - in practice this anchor is defined in a class which is imported using non-conforming inheritance
--Colin Adams and Mark Howard