Usage-site variance
Contents
Introduction
The usage-site variance allows the programmer to choose which kind of variance he wants to use when a generic derivation is declared. This allows to use every generic as novariant, covariant or contravariant in contrast to the definition-site variance where it is set in the class definition.
Syntax
The syntax used here to specify the variance of a generic is simple and may be changed to something more verbose but also clearer:
- To specify a novariant generic, the normal syntax can be used:
LIST [ANY]
- To specify a covariant generic, we use a plus sign:
LIST [+ANY]
- To specify a contravariant generic, we use a minus sign:
LIST [-ANY]
Semantics
See the introduction to examples for information about the classes used.
Conformance rules
Depending on the variance of the generic, the conformance rules differ:
- A generic conforms to a novariant generic if it has the exact same generic parameter. Thus
LIST [T]
only conforms toLIST [T]
. Note thatLINKED_LIST [T]
also conforms toLIST [T]
as long as the generic parameter matches. - A generic conforms to a covariant generic if its generic parameter conforms to the generic parameter of the covariant generic. Thus
LIST [U]
conforms toLIST [+T]
. - A generic conforms to a contravariant generic if its generic parameter is a parent of the generic parameter of the contravariant generic. Thus
LIST [T]
conforms toLIST [-U]
.
For the conformance of tuples, we keep that a tuple with more elements conforms to a tuple with less elements as long as the common elements conform:
-
TUPLE [T, U]
conforms toTUPLE [T]
-
TUPLE [U, U]
conforms toTUPLE [+T]
-
TUPLE [T, U]
conforms toTUPLE [-U]
Applicable features
Depending on the variance of the generic, the applicable features differ:
- On a novariant generic, all features can be used.
- On a covariant generic, only features which have the formal generic as a result type can be used. If the formal generic appears in an argument this feature is invalid.
- On a contravariant generic, only feature which have the formal generc as an argument type can be used. If the formal generic appears as a result type this feature is invalid.
Examples
See the introduction to examples for information about the classes used.
A simple generic algorithm
It is easy to declare an algorithm over generics as long as only read-access is needed. Just declare input to the algorithm as covariant list.
class PERSON_PRINTER feature print_all (a_list: LIST [+PERSON]) -- Print all attributes of each person in the list. do from a_list.start until a_list.after do -- Reading is valid on a covariant list a_list.item.print end end end class EXAMPLE feature make local l_students: LIST [STUDENT] l_professors: LIST [PROFESSOR] l_person_printer: PERSON_PRINTER do create l_person_printer -- LIST [STUDENT] conforms to LIST [+PERSON] l_person_printer.print_all (l_students) -- LIST [PROFESSPR] conforms to LIST [+PERSON] l_person_printer.print_all (l_professor) end end
Comparator
In the example of sorting a list with a comparator, contravariant generics can be used. Due to the use of COMPARATOR [-G]
when sorting a LIST [G]
, we allow to use a comparator which can sort more generally than just objects of type G
. This allows to use a comparator for ANY
objects to sort a list of STRINGS
.
class SORTER [G] feature sort (a_list: LIST [G]; a_comparator: COMPARATOR [-G]) do -- Somewhere in the loop: a_comparator.compare (l_string_1, l_string_2) end end class EXAMPLE feature make local l_list: LIST [STRING] l_string_sorter: SORTER [STRING] l_string_comparator: COMPARATOR [STRING] l_any_comparator: COMPARATOR [ANY] do -- COMPARATOR [STRING] conforms to COMPARATOR [-STRING] l_string_sorter.sort (l_list, l_string_comparator) -- COMPARATOR [ANY] conforms to COMPARATOR [-STRING] l_string_sorter.sort (l_list, l_any_comparator) end end
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 -- The following calls are surely permitted (and also correct) since -- the tuple generic inside the tuple is covariant and thus allows -- the tuple of type U to be passed. an_agent.call ([T]) an_agent.call ([U]) -- Due to the tuple conformance, the following calls are also -- permitted. Note that they are both correct. an_agent.call ([T, ...]) an_agent.call ([U, ...]) end
We see that this solution allows the full range of applicable arguments for calling agents.
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; t2: T) do end --> PROCEDURE [ANY, TUPLE [+T, +T]] -- This assignment is naturally allowed. an_agent := agent_t -- This assignment is allowed since the tuple is declared contravariant -- and the empty tuple is an ancestor of TUPLE [+T] an_agent := agent_empty -- This assignment is allowed and correct. The reason is that TUPLE [+ANY] -- is an ancestor of TUPLE [+T]. 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
We see that this solution allows all possible agents to be assigned.
Since declaring an agent can be difficult (where did I need to put the "+" and the "-"?), an alternate syntax for agents could be useful where the compiler can derive the correct type for an agent.
Conclusion
The usage-site variance is a mechanism which allows both expressive and type-safe generics. It can model agents and comparator types correctly.