The book I am reading just introduced the concepts of subsumption, covariance, contravariance and its implications for the design of programming languages. Now, in the section about method specialization, I'm getting into trouble.
The book points out that, when we override methods in subclasses, parameter types may be generalized, and result types may be specialized. However, the book argues, that for methods that aren't overridden but inherited, another type of method specialization is at play where parameter types are specialized, and result types are generalized, because we can interpret the self keyword as an argument:
"There is another form of method specialization that happens implicitly by inheritance. The occurrences of self in the methods of C can be considered of type C, in the sense that all objects bound to self have type C or a subtype of C. When the methods of C are inherited by C', the same occurrences of self can similarly be considered of type C'. Thus the type of self is silently specialized on inheritance (covariantly!)."
And here is my problem: Can't we consider the self keyword a covariant argument in overridden methods as well? Then we would end up with the this keyword as a covariant argument, even though we just established that, as a consequence of the substitution principle, arguments of overridden methods need to be contravariant. Am I overlooking something?
Thanks for your help!
...when we override methods in subclasses, parameter types may be generalized (covariance), and result types may be specialized (contravariance)
Even though this can be true, it depends on the specific language, whether it actually implements this functionality. I have found examples on wiki:
C++ and Java implement only covariant return types and the method argument types are invariant.
C# does not implement either variancy (so both are invariant).
There is an example of a language (Sather) with contravariant argument type and covariant return type - this is what you mentioned.
However, there is also one (Eiffel) with covariant return & argument type, but this can normally cause runtime errors.
There is also a nice paragraph dividing arguments between controlling arguments and "left-over" arguments. The controlling ones are covariant and the non-controlling ones are contravariant. This is regarding multiple dispatch langauges, although most certainly you were referring to a single dispatch language. But even there is a single controlling argument (self
/this
).
Here is the paragraph (I did not have time to study the paper it is referring to, please feel free to read it if you have the time and post your findings):
Giuseppe Castagna[3] observed that in a typed language with multiple dispatch, a generic function can have some arguments which control dispatch and some "left-over" arguments which do not. Because the method selection rule chooses the most specific applicable method, if a method overrides another method, then the overriding method will have more specific types for the controlling arguments. On the other hand, to ensure type safety the language still must require the left-over arguments to be at least as general. Using the previous terminology, types used for runtime method selection are covariant while types not used for runtime method selection of the method are contravariant.
Conventional single-dispatch languages like Java also obey this rule: there only one argument is used for method selection (the receiver object, passed along to a method as the hidden argument
this
), and indeed the type ofthis
is more specialized inside overriding methods than in the superclass.
According to the paragraph I assume that the self
argument in its nature is not a regular method argument (which may be contravariant), because self
is an another kind of argument - controlling argument - which are covariant.
...even though we just established that, as a consequence of the substitution principle, arguments of overridden methods need to be contravariant.
Well, it looks like not all of them.