These examples should be considered for all catcall solutions:
The default semantics of the catcall solution should work for generics like the following:
local any_list: LIST [ANY] string_list: LIST [STRING] any_linked_list: LINKED_LIST [ANY] do -- Should be allowed with default semantics any_list := any_linked_list any_list.put ("abc") any_linked_list.put (5) -- Has to be forbidden with default semantics any_list := string_list end
Faces of covariance
The following example shows the difference between generic covariance and non-generic covariance.
class A [G] feature f (t: T) do end put (g: G) do end end class B [G] inherit A [G] redefine f end feature f (u: U) do end end local a_any: A [ANY] a_string: A [STRING] b_any: B [ANY] b_string: B [STRING] t: T do a_any := a_string a_any.put (5) -- catcall for generic feature a_any.f (t) -- no catcall a_any := b_any a_any.put (5) -- no catcall a_any.f (t) -- catcall for non-generic feature a_any := b_string a_any.put (5) -- catcall for generic feature a_any.f (t) -- catcall for non-generic feature end
A safe and expressive solution should have a way to allow all valid cases and prohibit the invalid ones. This needs a way to address the generic and non-generic features indiviually.
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_any_comparator: COMPARATOR [ANY] do -- Should be allowed to sort the string list with an -- ANY comparator l_string_sorter.sort (l_list, l_any_comparator) end end
Solutions which pass this test are likely to be expressive for agents as well.
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:
local an_agent: PROCEDURE [ANY, TUPLE [T]] -- An agent which takes an argument of type T. do -- The following calls are currently valid and should remain valid. an_agent.call ([T]) an_agent.call ([U]) an_agent.call ([T, ...]) an_agent.call ([U, ...]) end
These basic cases mostly rely on the tuple conformance rules to work properly.
To allow anything that is safe is more tricky. It requires the ability to model contravariance.
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 as they are the same type. an_agent := agent_t -- The following assignments are save but prohibited by the current implementation. -- If they were allowed it would benefit event driven programming in Eiffel a lot. -- As agents currently are modeled over the generic mechanism any solution which -- solves the "comparator problem" is likely to pass here too. an_agent := agent_empty an_agent := agent_any -- The following assignments are currently permitted. This is not the correct -- behavior 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