Covariance and contravariance (computer science): Difference between revisions

Content deleted Content added
Citation bot (talk | contribs)
Removed parameters. | Use this bot. Report bugs. | Suggested by Abductive | #UCB_webform 2175/3850
m Grammar and punctuation fixes
Line 11:
A programming language designer will consider variance when devising [[typing rule]]s for language features such as [[Array (data type)|array]]s, [[Inheritance (object-oriented programming)|inheritance]], and [[generic datatype]]s. By making type constructors covariant or contravariant instead of '''invariant''', more programs will be accepted as well-typed. On the other hand, programmers often find contravariance unintuitive, and accurately tracking variance to avoid [[runtime errors|runtime type errors]] can lead to complex typing rules.
 
In order to keep the type system simple and allow useful programs, a language may treat a type constructor as invariant even if it would be safe to consider it variant, or treat it as covariant even though that could violate type safety.
 
== Formal definition ==
Line 30:
* {{C sharp|Action<Animal>}} is a subtype of {{C sharp|Action<Cat>}}. The subtyping is reversed because {{C sharp|Action<T>}} is '''contravariant''' on {{C sharp|T}}.
* Neither {{C sharp|IList<Cat>}} nor {{C sharp|IList<Animal>}} is a subtype of the other, because {{C sharp|IList<T>}} is '''invariant''' on {{C sharp|T}}.
The variance of a C# generic interface is declared by placing the {{C sharp|out}} (covariant) or {{C sharp|in}} (contravariant) attribute on (zero or more of) its type parameters.<ref name=Skeet>{{cite book |last=Skeet|first=Jon|title= C# in Depth |date=23 March 2019 |publisher= Manning |isbn= 978-1617294532}}</ref>{{rp|144}} The above interfaces are declared as {{C sharp|IEnumerable<out T>}}, {{C sharp|Action<in T>}}, and {{C sharp|IList<T>}}. Types with more than one type parameter may specify different variances on each type parameter. For example, the delegate type {{C sharp|Func<in T, out TResult>}} represents a function with a '''contravariant''' input parameter of type {{C sharp|T}} and a '''covariant''' return value of type {{C sharp|TResult}}.<ref>[http://msdn.microsoft.com/en-us/library/bb549151.aspx Func<T, TResult> Delegate] - MSDN Documentation</ref><ref name=Skeet />{{rp|145}} The compiler checks that all types are defined and used consistently with their annotations, and otherwise signals a compilation error.
 
The [[#Interfaces|typing rules for interface variance]] ensure type safety. For example, an {{C sharp|Action<T>}} represents a first-class function expecting an argument of type {{C sharp|T}},<ref name=Skeet />{{rp|144}} and a function that can handle any type of animal can always be used instead of one that can only handle cats.
Line 42:
* invariant: an {{java|Animal[]}} is not a {{java|Cat[]}} and a {{java|Cat[]}} is not an {{java|Animal[]}}.
 
If we wish to avoid type errors, then only the third choice is safe. Clearly, not every {{java|Animal[]}} can be treated as if it were a {{java|Cat[]}}, since a client reading from the array will expect a {{java|Cat}}, but an {{java|Animal[]}} may contain e.g. a {{java|Dog}}. So, the contravariant rule is not safe.
 
Conversely, a {{java|Cat[]}} cannot be treated as an {{java|Animal[]}}. It should always be possible to put a {{java|Dog}} into an {{java|Animal[]}}. With covariant arrays this cannot be guaranteed to be safe, since the backing store might actually be an array of cats. So, the covariant rule is also not safe&mdash;the array constructor should be ''invariant''. Note that this is only an issue for mutable arrays; the covariant rule is safe for immutable (read-only) arrays. Likewise, the contravariant rule would be safe for write-only arrays.
Likewise, the contravariant rule would be safe for write-only arrays.
 
=== Covariant arrays in Java and C# ===
Line 110 ⟶ 109:
 
== Inheritance in object-oriented languages ==
When a subclass [[Method overriding|overrides]] a method in a superclass, the compiler must check that the overriding method has the right type. While some languages require that the type exactly matches the type in the superclass (invariance), it is also type safe to allow the overriding method to have a "better" type. By the usual subtyping rule for function types, this means that the overriding method should return a more specific type (return type covariance), and accept a more general argument (parameter type contravariance). In [[Unified Modeling Language|UML]] notation, the possibilities are as follows (where Class B is the subclass that extends Class A which is the superclass):
 
<gallery perrow="5" heights="190" caption="Variance and method overriding: overview">
Line 188 ⟶ 187:
</syntaxhighlight>
 
This is not type safe. By up-casting a {{java|CatShelter}} to an {{java|AnimalShelter}}, one can try to place a dog in a cat shelter. That does not meet {{java|CatShelter}} parameter restrictions, and will result in a runtime error. The lack of type safety (known as the "catcall problem" in the Eiffel community, where "cat" or "CAT" is a Changed Availability or Type) has been a long-standing issue. Over the years, various combinations of global static analysis, local static analysis, and new language features have been proposed to remedy it,<ref>{{cite conference|title=Static Typing |author=Bertrand Meyer |book-title=OOPSLA 95 (Object-Oriented Programming, Systems, Languages and Applications), Atlanta, 1995. |date=October 1995 |url=http://se.ethz.ch/~meyer/publications/acm/typing.pdf}}</ref><ref name="competentCompilers">{{cite web|title=Type-safe covariance: Competent compilers can catch all catcalls |first1=Mark |last1=Howard |first2=Eric |last2=Bezault |first3=Bertrand |last3=Meyer |first4=Dominique |last4=Colnet |first5=Emmanuel |last5=Stapf |first6=Karine |last6=Arnout |first7=Markus |last7=Keller |date=April 2003 |access-date=23 May 2013 |url=http://se.ethz.ch/~meyer/ongoing/covariance/recast.pdf}}</ref> and these have been implemented in some Eiffel compilers.
 
Despite the type safety problem, the Eiffel designers consider covariant parameter types crucial for modeling real world requirements.<ref name="competentCompilers"/> The cat shelter illustrates a common phenomenon: it is ''a kind of'' animal shelter but has ''additional restrictions'', and it seems reasonable to use inheritance and restricted parameter types to model this. In proposing this use of inheritance, the Eiffel designers reject the [[Liskov substitution principle]], which states that objects of subclasses should always be less restricted than objects of their superclass.