Usage-site variance

Revision as of 13:24, 23 June 2007 by Seilerm (Talk | contribs) (Agents)

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 to LIST [T]. Note that LINKED_LIST [T] also conforms to LIST [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 to LIST [+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 to LIST [-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:

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.

Vision 2 Example

Conclusion

The usage-site variance is a mechanism which allows both expressive and type-safe generics. It can model agents and comparator types correctly.