Difference between revisions of "Forget / Keep Mechanism"

m (Arguments with like)
(Generics)
Line 601: Line 601:
 
==Generics==
 
==Generics==
  
...
+
===Cat-call problem===
 +
 
 +
The inheritance rules for generics introduce also cat-call problems:
 +
 
 +
<e>
 +
local
 +
    -- feature `put' and `has' have argument type ANY
 +
  any_list: LIST [ANY]
 +
    -- feature `put' and `has' have argument type ANIMAL
 +
  animal_list: LIST [ANIMAL]
 +
do
 +
    -- animal_list conforms to any_list, but arguemnts of the features
 +
    -- `put' and `has' 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
 +
</e>
 +
 
 +
===With forget mechanism===
 +
 
 +
Default is ''keep all'' types and features with generic arguments are treated as covariantly redefined.
 +
 
 +
<e>
 +
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
 +
</e>
 +
 
 +
<e>
 +
local
 +
  any_list: LIST [ANY] forget all end
 +
  animal_list: LIST [ANIMAL]
 +
  animal: ANIMAL
 +
do
 +
    -- legal since animal_list conforms to LIST [ANY] forget all
 +
  any_list := animal_list
 +
 
 +
    -- illegal since the forget all list does not have any features
 +
    -- with generic arguments anymore
 +
  any_list.put (5)
 +
 
 +
    -- illegal, same reason as above
 +
  any_list.has (animal)
 +
 
 +
end
 +
</e>
 +
 
 +
===With keep mechanism===
 +
 
 +
Default is ''forget all'' types and features with generic arguments are treated as covariantly redefined.
 +
 
 +
<e>
 +
local
 +
  any_list: LIST [ANY]
 +
  animal_list: LIST [ANIMAL]
 +
  animal: ANIMAL
 +
do
 +
    -- legal since any_list is 'forget all' and has no problematic features
 +
  any_list := animal_list
 +
 
 +
    -- illegal since the feature 'put' is treated as covariantly redefined
 +
    -- and thus not present in the 'forget all' type
 +
  any_list.put (5)
 +
 
 +
    -- illegal, same reason as above
 +
  any_list.has (animal)
 +
 
 +
end
 +
</e>
 +
 
 +
<e>
 +
local
 +
  any_list: LIST [ANY] keep all end
 +
  animal_list: LIST [ANIMAL]
 +
do
 +
    -- illegal since animal_list does not conform to
 +
    -- any_list keep all anymore
 +
  any_list := animal_list
 +
 
 +
  any_list.put (5)
 +
 
 +
  any_list.has (animal)
 +
 
 +
end
 +
</e>
  
 
==Consequences==
 
==Consequences==

Revision as of 16:25, 20 February 2007

Warning.png Warning: Warning: Article under development

Introduction

By allowing covariant feature redefinition and hiding of features, the Eiffel language introduces the problem of cat-calls (Changed Availability or Type). The forget / keep 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.

Example

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 SET [G]
 
feature
 
  has (g: G): BOOLEAN
 
  put (g: G)
 
  item: G
 
end
class LIST [G]
 
inherit 
 
  SET [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 don't actually inherit this feature and redefine it, but you inherit from a parent type which does not have this feature at all. The same goes for changing the export status. When you restrict the export status of a feature, you don't inherit this feature from the parent but introduce it in the current type and inherit from a parent which does not know this feature at all.

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 than the type CAT does not conform to ANIMAL anymore, but only to a type ANIMAL without the feature eat.

This gives two corner cases, the first one is a type which keeps all features even if they get covarianlty redefined by other types. We call this type the keep all type since it keeps all features. All features defined in the class can be called on an instance of this type, but all subtypes which have a covariantly redefined feature or change an export status of a feature don't conform to the keep all type anymore.

The second case is a type which looses all feature which get covariantly redefined later on or whose export status is restricted. This type is called the forget all type since it forgets all problematic features. All subtypes will conform to this type even if they covariantly redefine a feature or change the export status. Since this type forgets all of these features, they cannot be called on such an instance.

Forget mechanism

The forget mechanism introduces a new keyword forget which can be used to remove covarianlty redefined features and features which change the export status. By removing features, a new type is introduced. The new type has all features the original type has except those listed in the forget clause. If forget all is used, all features which are covariantly redefined or have a changed export status will be removed from the new type.

Default behavior

In the forget mechanism the normal declaration of a type will keep all covariantly redefined features or features whose export status is restricted (keep all type). This implies that all subtypes which change an export status or covariantly redefine a feature don't conform to this type anymore.

local
    -- normal declaration means 'keep all'
  animal: ANIMAL
  cat: CAT
do
    -- legal since the feature `eat' is kept in the normal type
  animal.eat (food)
 
    -- legal since the feature `sleep' is kept in the normal type
  animal.sleep
 
    -- illegal since CAT does not conform to ANIMAL (keep all) anymore
  animal := cat
end

Syntax

-- normal declaration is an object with all features (keep all) and no
  -- subtype which has a covariantly redefined feature conforms to this type
a1: ANIMAL
 
  -- a type marked as `poly' will loose all covarianlty redefined features
  -- thus all subtypes conform to this type
a2: poly ANIMAL
 
  -- a type which only forgets the features `eat' and `sleep' but not other
  -- covarianlty redefined features or features whose export status is
  -- restricted. all subtypes which only redefine or change export status 
  -- of`eat' or `sleep' will conform to this type
a3: ANIMAL forget eat, sleep end
 
  -- a type which forgets all covariantly redefined features. equivalent to `poly'
a4: ANIMAL forget all end

Conformance

The conformance between ANIMAL, ANIMAL forget eat, ANIMAL forget sleep and ANIMAL forget all is as follows:

local
  normal_animal: ANIMAL
  forget_eat_animal:   ANIMAL forget eat end
  forget_sleep_animal: ANIMAL forget sleep end
  forget_all_animal:   ANIMAL forget all end
 
    -- this is equivalent to the forget all clause as long as
    -- no other features of ANIMAL are covarianlty redefined
  forget_eat_sleep_animal: ANIMAL forget eat, sleep end
do
    -- these assignments are legal since all features present in the
    -- forget types are also present in the normal type
  forget_eat_animal   := normal_animal
  forget_sleep_animal := normal_animal
  forget_all_animal   := normal_animal
 
    -- these assignment are illegal since the forget types lack
    -- at least one feature and thus cannot be used as an ANIMAL
  normal_animal := forget_eat_animal
  normal_animal := forget_sleep_animal
  normal_animal := forget_all_animal
 
    -- these assignments are legal since the forget all type
    -- has fewer or equal features than the other forget types
  forget_all_animal := forget_eat_animal
  forget_all_animal := forget_sleep_animal
 
    -- these assignments are illegal since the forget all type
    -- lacks one feature that the other types has (eat or sleep
    -- respectively)
  forget_eat_animal := forget_all_animal
  forget_sleep_animal := forget_all_animal
 
    -- these assignments are illegal since both types lack
    -- a feature which the other type has
  forget_eat_animal := forget_sleep_animal
  forget_sleep_animal := forget_eat_animal
 
end

Keep mechanism

The keep mechanism is the contrary to the forget mechanism. It introduces a new keyword keep which can be used to keep covarianlty redefined features and features which change the export status.

Default behavior

In the keep mechanism the normal declaration of a type will loose all covariantly redefined features or features whose export status is restricted (forget all type). This implies that all subtypes still conform to a normally declared type, but all covariantly redefined features will not be available.

local
    -- normal declaration means 'forget all'
  animal: ANIMAL
  cat: CAT
do
    -- illegal since the feature `eat' is covariantly redefined
  animal.eat (food)
 
    -- illegal since the feature `sleep' changes export status
  animal.sleep
 
    -- legal since animal has no problematic features
  animal := cat
end

Syntax

-- normal declaration is an object which forgets all covariantly
  -- redefined features. all subtypes conform to this type
b1: ANIMAL
 
  -- a type which keeps all features and looses conformance from subtypes which
  -- covariantly redefine features
b2: mono ANIMAL
 
  -- a type where all subtypes conform except those who covariantly redefine
  -- feature `eat'
b3: ANIMAL keep eat end
 
  -- a type which keeps all features. equivalent to `mono'
b4: ANIMAL keep all end

Conformance

The conformance between ANIMAL, ANIMAL keep eat, ANIMAL keep sleep and ANIMAL keep all is as follows:

local
  normal_animal: ANIMAL
  keep_eat_animal:   ANIMAL keep eat end
  keep_sleep_animal: ANIMAL keep sleep end
  keep_all_animal:   ANIMAL keep all end
 
    -- this is equivalent to the keep all clause as long as
    -- no other features of ANIMAL are covarianlty redefined
  keep_eat_sleep_animal: ANIMAL keep eat, sleep end
do
 
    -- these assignments are illegal since the normal ANIMAL type
    -- does not have the eat or sleep features
  keep_eat_animal   := normal_animal
  keep_sleep_animal := normal_animal
  keep_all_animal   := normal_animal
 
    -- these assignment are legal since the normal type has
    -- fewer features than the keep types
  normal_animal := keep_eat_animal
  normal_animal := keep_sleep_animal
  normal_animal := keep_all_animal
 
    -- these assignments are illegal since the keep all type
    -- has more features than the other keep types
  keep_all_animal := keep_eat_animal
  keep_all_animal := keep_sleep_animal
 
    -- these assignments are legal since the keep all type
    -- has more features than the other keep types
  keep_eat_animal := keep_all_animal
  keep_sleep_animal := keep_all_animal
 
    -- these assignments are illegal since both types lack
    -- a feature which the other type has
  keep_eat_animal := keep_sleep_animal
  keep_sleep_animal := keep_eat_animal
 
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

With forget mechanism

With the forget mechanism the default behavior for a type is to keep all problematic features. Types which have covariant redefined features will not be conform to the normal type. A feature which is covariantly redefined changes its inherit clause implicitly to inherit from a parent class which has the redefined feature in a forget clause.

what you write what is implied
class CAT
 
inherit
 
  ANIMAL
    redefine
      eat
    end
 
feature
 
  eat (f: CAT_FOOD)
 
end
class CAT
 
inherit
 
  ANIMAL forget eat
    redefine
      eat
    end
 
feature
 
  eat (f: CAT_FOOD)
 
end

Now the cat-call example with the new forget types:

local
  a: ANIMAL
  c: CAT
do
    -- illegal assignment, ANIMAL and CAT don't conform
    -- since CAT implicitly inherits from ANIMAL forget eat
  a := c
  a.eat (food)
 
end
local
  a: ANIMAL forget all end
  c: CAT
do
    -- legal, CAT conforms to ANIMAL forget all
  a := c
 
    -- illegal, ANIMAL forget all doesn't have a feature eat
  a.eat (food)
 
end

With keep mechanism

With the keep mechanism the default behavior of a type is to loose all problematic features. Thus when a type covariantly redefines a feature, this implicitly changes the parent to loose this feature by default.

what you write what is implied
class CAT
 
inherit
 
  ANIMAL
    redefine
      eat
    end
 
feature
 
  eat (f: CAT_FOOD)
 
end

feature eat will not be present anymore in type ANIMAL. Only ANIMAL keep eat end or ANIMAL keep all end will have the feature eat.

Now the cat-call example with the new types:

local
  a: ANIMAL
  c: CAT
do
  a := c
    -- illegal call since the type ANIMAL does not have a feature `eat' anymore
  a.eat (food)
 
end
local
  a: ANIMAL keep all end
  c: CAT
do
    -- illegal, CAT does not conform to ANIMAL keep all
  a := c
 
    -- legal, ANIMAL keep all still has the feature `eat'
  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

With forget mechanism

With the forget mechanism the default behavior for a type is to keep all problematic features. Types which change the export status of features will not be conform to the normal type. A feature which has a restricted export status changes its inherit clause implicitly to inherit from a parent class which has the feature in a forget clause.

what you write what is implied
class CAT
 
inherit
 
  ANIMAL
    export
      {NONE} sleep
    end
 
end
class CAT
 
inherit
 
  ANIMAL forget sleep
    export
      {NONE} sleep
    end
 
end

Now the cat-call example:

local
  a: ANIMAL
  c: CAT
do
    -- illegal assignment, ANIMAL and CAT don't conform
    -- since CAT implicitly inherits from ANIMAL forget sleep
  a := c
  a.sleep
 
end
local
  a: ANIMAL forget all end
  c: CAT
do
    -- legal, CAT conforms to ANIMAL forget all
  a := c
 
    -- illegal, ANIMAL forget all doesn't have a feature sleep
  a.sleep
 
end

With keep mechanism

With the keep mechanism the default behavior of a type is to loose all problematic features. Thus when a type changes the export status of a feature, this implicitly changes the parent to loose this feature by default.

what you write what is implied
class CAT
 
inherit
 
  ANIMAL
    export
      {NONE} sleep
    end
 
end

feature sleep will not be present anymore in type ANIMAL. Only ANIMAL keep sleep end or ANIMAL keep all end will have the feature sleep.

Now the cat-call example:

local
  a: ANIMAL
  c: CAT
do
  a := c
    -- illegal call since the type ANIMAL does not have a feature `sleep' anymore
  a.sleep
 
end
local
  a: ANIMAL keep all end
  c: CAT
do
    -- illegal, CAT does not conform to ANIMAL keep all
  a := c
 
    -- legal, ANIMAL keep all still has the feature `sleep'
  a.sleep
 
end

Generics

Cat-call problem

The inheritance rules for generics introduce also cat-call problems:

local
    -- feature `put' and `has' have argument type ANY
  any_list: LIST [ANY]
    -- feature `put' and `has' have argument type ANIMAL
  animal_list: LIST [ANIMAL]
do
    -- animal_list conforms to any_list, but arguemnts of the features
    -- `put' and `has' 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

With forget mechanism

Default is keep all types and features with generic arguments are treated as covariantly redefined.

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: LIST [ANY] forget all end
  animal_list: LIST [ANIMAL]
  animal: ANIMAL
do
    -- legal since animal_list conforms to LIST [ANY] forget all
  any_list := animal_list
 
    -- illegal since the forget all list does not have any features
    -- with generic arguments anymore
  any_list.put (5)
 
    -- illegal, same reason as above
  any_list.has (animal)
 
end

With keep mechanism

Default is forget all types and features with generic arguments are treated as covariantly redefined.

local
  any_list: LIST [ANY]
  animal_list: LIST [ANIMAL]
  animal: ANIMAL
do
    -- legal since any_list is 'forget all' and has no problematic features
  any_list := animal_list
 
    -- illegal since the feature 'put' is treated as covariantly redefined
    -- and thus not present in the 'forget all' type
  any_list.put (5)
 
    -- illegal, same reason as above
  any_list.has (animal)
 
end
local
  any_list: LIST [ANY] keep all end
  animal_list: LIST [ANIMAL]
do
    -- illegal since animal_list does not conform to 
    -- any_list keep all anymore
  any_list := animal_list
 
  any_list.put (5)
 
  any_list.has (animal)
 
end

Consequences

Arguments with like

A declaration like Current is a covariant redefinition for all subtypes. Thus the mechanism affects all types which have such arguments and their subtypes (see next section).

ANY

ANY has the features ANY.is_equal and ANY.copy with a like Current argument:

class ANY
 
feature
 
  is_equal (other: like Current): BOOLEAN
 
  copy (other: like Current)
 
end

Since all types are descendants of ANY, all types have covariantly redefined features. The choice of default for the forget mechanism has thus an impact on the whole type hierarchy:

If the default is forgetting all features (forget all types) than you will be able to assign all types to ANY, but the features is_equal and copy will not be available since they are covariantly redefined.

If the default is keeping all features (keep all types) than no type will conform to ANY and thus all assignments to ANY from any other type are illegal. Since the like argument changes with any type, no subtype will conform to it's parent per default since at least one feature is covariantly redefined.

Solution: Remove the like Current arguments from ANY. Change the argument of is_equal and copy to type ANY. Then either introduce a precondition to require conformance of argument and object or add a type checks to the actual implementation of the features.

export {NONE} all

...

SET.has

...