Default and explicit variance
Research: This page describes research about Eiffel, not the actual language specification.
Contents
Introduction
This solution enables covariance and contravariance redefinition. The default behavior is detected automatically. When there is a catcall risk the programmer must explicitly declare the variance.
Default variance
Feature redefinition
- Covariant redefinition of request result is allowed
- Contravariant redefinition of feature arguments is allowed
The first is a weaker precondition and the second is a stronger postcondition. Only the second rule is a new possibility in Eiffel.
- Contravariant redefinition of request result is allowed for non-conforming inheritance
- Covariant redefinition of feature arguments is allowed for non-conforming inheritance
Generic conformance
Generic conformance is close from redefinition.
- Generic used only on feature argument is contravariant.
- Generic used only on request result is covariant.
- Generic used both on feature argument and request result is novariant.
And then generic not used is covariant and contravariant.
The first checkpoint "Generic lists" is checked. As explained for Usage-site variance the generic 'OPEN_ARGS' from ROUTINE, PROCEDURE, FUNCTION, PREDICATE must be contravariant.
However 'OPEN_ARGS' is novariant. Indeed the generic is used on request type and feature arguments.
The class must probably be redesigned. Indeed if contravariant is allowed then there may be a new contravariant cat-call on request result.
In these classes there are only two requests using the generic as type:
operands: detachable OPEN_ARGS empty_operands: OPEN_ARGS do create Result ensure ... end
The second request is not used internally and may be problematic for creation: How create a tuple of not self-initialized and attached types? The first is used in these features : 'target', 'is_equal', 'set_operands', 'copy', 'apply'
'is_equal', 'set_operands' and 'copy' rely on data model. 'apply' and 'target' are more sensitive. Indeed the problem is when there is an opened target.
'apply' could be restricted for no opened arguments.
apply require no_opened_operands: open_count = 0 do call (Void) end
'target' could be modfied:
target: ANY require is_target_closed do -- ... end
Or maybe it is better to separate opened target and closed target in two abstractions.
The redisgn of agent classes is an opportunity to solve agent problems (see Minor-ECMA-problems, Agents in SCOOP).
Another solution will be mentioned later.
Note: The generic 'RESULT_TYPE' of FUNCTION class is used only on request result. Then it is a covariant generic. It is an expected point.
Sub-conclusion
The default semantic use no new keyword.
The lack of expressivity for generic variance can be reduced with a prefixed mark for a formal genric (see below).
The addition in TYPE class for reflexivity and dynamic object test is little. Indeed two simple booleans "is_contravariant" and "is_covariant" for each generic is required.
generic_parameter_contravariant (i: INTEGER): BOOLEAN -- Is `i'-th generic parameter contravariant? generic_parameter_covariant (i: INTEGER): BOOLEAN -- Is `i'-th generic parameter covariant?
Variant typing
What is the advantages of covariance compared to a novariant typing?
- explicit and adaptative interface
- One routine
Feature redefinition
These rules concern the conforming inheritance.
- Covariant redefinition of feature argument requires a 'variant' typing on redfined feature or first feature definition.
- Contravariant redefinition of request result requires a 'variant' typing on first feature definition.
The contravariant redefinition has no interests for non-generic and little interests for generics. It can be avoided with a right abstraction.
Variant typing for Covariant redefinition:
- A variant type argument has the most restricted type: The type of the previous definition
- A variant type argument requires a simple object test to use the argument with the expected type.
- A variant type argumpent can be assigned to a formal and variant type argument if the most restricted type of the argument is conform to the msot restricted type of the formal argument.
example 1: covariant redefinition of feature argument with 'variant' typing on redfined feature
Note that the type is not repeated in the object test. In the interface of the current type the 'variant' is removed. Indeed, it is not possible to call 'eat' on a COW instance with a parameter of type FOOD.
class ANIMAL feature -- Access last: FOOD faeture -- Eating eat (f: like last) require True do last := f ensure True end end
class COW inherit ANIMAL redefine all end feature -- Access last: GRASS faeture -- Eating eat (f: variant like last) -- or eat (f: variant FOOD) require else True -- 'f' type = expected type. Here: GRASS do -- 'f' type = type of the previous definition. Here: FOOD if attached f as g then -- 'g' type = expected type. Here: GRASS last := g end ensure then True -- 'f' type = type of the previous definition. Here: FOOD end end
example 2: covariant redefinition of feature argument with 'variant' typing on first feature definition.
class ANIMAL feature -- Access last: FOOD faeture -- Eating eat (f: variant like last) do if attached f as safe then last := safe end end end
class COW inherit ANIMAL redefine last end feature -- Access last: GRASS end
example 3: contravariant redefinition
Z inherits of Y
Y inherits of X
class A feature -- Access something: variant Z feature -- Other do_something do -- 'something' type = ANY if attached something as expected then -- 'expected' type = feature result type. Here: Z end end end
class B inherit A redefine something end feature -- Access something: variant Y end
class C inherit B redefine something, do_something end feature -- Access something: X feature -- Other do_something do -- 'something' type = X end end
Generic conformance
The variant typing can be used to change the default variance of generic type.
- Generic used only on feature argument or request result with variant typing is contravariant.
- Generic used only on request result or feature argument with variant typing is covariant.
example: agents Then for agents it is possible to decalre requests 'operands' and 'empty_operands' as variant.
operands: variant detachable OPEN_ARGS empty_operands: variant OPEN_ARGS do create Result ensure ... end
'apply' and 'target' should be write again. For example:
apply do -- 'operands' type = constrained inheritance. Here: detachable TUPLE if attached operands as args then -- 'operands' type = request result type. Here: OPEN_ARGS call (args) else call (Void) end end
Sub-conclusion
This proposition reuses an existing keyword. The contravariant redefinition of request result can be remote for simplicity.
The lack of expressivity for generic variance can be reduced with a prefixed mark for a formal genric (see just below).
Ensure the generic variance
Sometimes the programmer wishes ensure a certain behavior. For example for agents: it is expected 'OPEN_ARGS' be a contravariant generic.
A formal generic prefixed with the 'variant' keyword cannot be novariant. It can be covariant or contravariant or both. This mark is optional (Backward compatibility and simplicity).
example: agents
class ROUTINE [BASE_TYPE, variant OPEN_ARGS -> detachable TUPLE create default_create end] -- ... end
The compiler must ensure 'OPEN_ARGS' is not novariant.
Discussions
Greater flexibility for generics
A lot of generics could be novariant, encouraging to propose a solution to have a new flexibility, but safe.
Wildcard generics
The request result type is the constrained inheritance type. And the type of the feature argument is (attached) NONE. The new semantic of 'Void' is considered: Void is not a NONE instance.
local a: ARRAY [?]; b: ARRAY [STRING] o: ANY do -- ... a := b o := a.item (1) a.put ("try") -- invalid call. The type expected is NONE. end
However the wildcard generics will be used on request result or feature argument. A more power and elegent solution could be the parametrized routines.
Parametrized routines
do_something [G] (a: ARRAY [G]): G require a.count >= 1 do Result := a.item (1) end
Right abstraction
The wildcard generics and the parametrized routines introduce new constructs for Eiffel. Is there another solution avoiding this?
With a right abstraction it is possible to have flexible classes. It is comparable to the imuutable cocncept.
example: V_CONTAINER class of Eiffel Base 2 To obtain the genric covariance behavior it is necessary to have: - V_ITERATOR must have a covariant generic. - An immutable abstraction of tuple IMMUTABLE_TUPLE should be wrote.
In V_CONTAINER class only two features should use the variant typing:
occurrences (v: variant G): INTEGER do -- G -> ANY then 'v' type = ANY across Current as it loop if it.item = v then Result := Result + 1 end end end has (v: variant G) local it: like new_cursor do it := new_cursor it.search_forth (v) -- {V_ITERATOR [G]}.search_forth (v: variant G) Result := not it.after ensure occurrences (v) = 1 end
In V_ITERATOR only two features should use the variant typing without object test!
With these changes the next code is valid:
local a: V_CONTAINER [ANY]; b: V_CONTAINER [STRING] do a := b end
Sub-conclusion
With a right abstraction and a smart use of avraint typing for generics, it is possible to obtain a greater flexibility keeping a fully aivailable interface.
Export status restrictions
Since the ECMA Eiffel Standard forbids the export restriction with conforming inheritance, it is not a problem. However, the semantic can be changed to enable this restriction on conforming inheritance.
Restrict exportation should not cause a catcall. The mechanism could be used just to change the class interface.
deferred class ANIMAL feature is_vegetarian: BOOLEAN deferred end end
class COW inherit ANIMAL export {NONE} is_vegetarian end feature {NONE} is_vegetarian: BOOLEAN = True end
local an_animal: ANIMAL; a_cow: COW b: BOOLEAN do create a_cow b := a_cow.is_vegetarian -- invalid call an_animal := a_cow b := an_animal .is_vegetarian -- valid call end
The class interface is more simple and readable.
Conclusion
The proposition uses no new keyword and solve the cat-call problem. It passes all Catcall checkpoints.
The default generic conformance give a natural safe and flexible static typing. The variant typing enables to create adaptive interfaces keeping a safe static typing.
With a fine abstraction and the use of variant typing for generic, the genric conformance flexibility is little restricted. The contravariant for request result can then be discarded for simplicity.
There is no interface restriction (Interval types or Usage-site variance). Class interfaces are fully available.