Novariance
Research: This page describes research about Eiffel, not the actual language specification.
Contents
Introduction
Novariance can be looked at for non-generics and generics:
- For non-generics, it just disallows covariant redeclaration of feature arguments. Since a covariant feature redeclaration is actually a strengthening of the precondition - strengthening of the type - the safest way is to disallow it. Also it is not really consistent in allowing strengthening of the precondition on the type system level but not on normal preconditions.
- For generics, you know from the beginning that for two different generic derivations of the same type, the generic arguments of features will differ. This is an inherent property of generics and you cannot disallow the change of type in the arguments. But again, since this is in general a strengthening of the precondition (from LIST [ANY] to LIST [STRING]) the direct solution is to disallow assignments between these types and thus removing conformance between generic derivations based on the conformance of the generic parameter. Conformance is only available on the base type if the generic parameters match exactly.
Syntax
No new syntax is necessary.
Semantics
The change in semantics as described above is:
- Covariant redefinition of feature arguments is not allowed.
- Generics only conform if the base types conform and the generic parameters are exactly the same.
Examples
See the introduction to examples for information about the classes used.
Cat
To see how the CAT example can be modeled without covariance, see the article about covariance through renaming.
Agents
For agents, we will look at an example with the type T
and a subtype U
. We will see an agent declaration and then check which arguments that are allowed:
Calling agents
local an_agent: PROCEDURE [ANY, TUPLE [T]] -- An agent which takes an argument of type T. do -- In the case of novariant generics, the only permitted call is an_agent.call ([T]) -- None of the following calls is permitted, although it would be safe to -- pass a tuple with more arguments or a tuple which consists of subtypes -- of the required argument. an_agent.call ([T, ...]) an_agent.call ([U]) an_agent.call ([U, ...]) end
The observations made are that it is not possible to pass an argument which has a different type than the exact required argument. This means that if you want to pass an argument of type U
to an agent which requires an argument T
you have to do an assignment to a variable of type T
first and then pass this variable instead or use the adapt routine of the TYPE class, i.e. {T} [U]
.
Assigning agents
local an_agent: PROCEDURE [ANY, TUPLE [T]] -- An agent which takes an argument of type T. do agent_empty := agent () do end --> PROCEDURE [ANY, TUPLE []] agent_any := agent (a: ANY) do end --> PROCEDURE [ANY, TUPLE [ANY]] agent_t := agent (t: T) do end --> PROCEDURE [ANY, TUPLE [T]] agent_u := agent (u: U) do end --> PROCEDURE [ANY, TUPLE [U]] agent_tt := agent (t: T; t: T) do end --> PROCEDURE [ANY, TUPLE [T, T]] -- The only legal assignment is an agent with the exact same generic parameters. an_agent := agent_t -- None of the follwing assignments is permitted, although it would be safe -- to do so. The `empty_agent' would just discard the T argument passed, and the -- `any_agent' also works if a T is passed. an_agent := agent_empty an_agent := agent_any -- The following assignments are not permitted by the solution. This is the correct -- behaviour as you could either create a catcall by passing a T argument to the -- `agent_u' or pass the wrong number of arguments by passing a [T] tuple to the -- `agent_tt'. an_agent := agent_u an_agent := agent_tt end
Again, not all assignments which would be safe are allowed by the type system.
Conclusion
In the non-generic case this solution limits the direct modeling ability as you cannot express covariant changes in the arguments. Since covariant redefinition is a precondition strengthening this can be justified on a mathematical basis, especially since covariant argument redefinition can be simulated through renaming.
In the generic case the possibilities are severely limited. It is not possible to use a list with a more general type even for reading (e.g. LIST [ANY]) without copying all elements over from a more specialized list (e.g. LIST [STRING]).