By making type constructors covariant or contravariant instead of invariant, more programs will be accepted as well-typed.
For example, in C#, if Cat is a subtype of Animal, then: The variance of a C# generic interface is declared by placing the out (covariant) or in (contravariant) attribute on (zero or more of) its type parameters.
If there is a mismatch, an ArrayStoreException (Java)[4]: 126 or ArrayTypeMismatchException (C#) is thrown: In the above example, one can read from the array (b) safely.
One drawback to this approach is that it leaves the possibility of a run-time error that a stricter type system could have caught at compile-time.
With the addition of generics, Java[4]: 126–129 and C# now offer ways to write this kind of polymorphic function without relying on covariance.
This rule was first stated formally by John C. Reynolds,[5] and further popularized in a paper by Luca Cardelli.
For complicated types it can be confusing to mentally trace why a given type specialization is or isn't type-safe, but it is easy to calculate which positions are co- and contravariant: a position is covariant if it is on the left side of an even number of arrows applying to it.
Adding the covariant return type was one of the first modifications of the C++ language approved by the standards committee in 1998.
One other instance of a mainstream language allowing covariance in method parameters is PHP in regards to class constructors.
Other language features can provide the apparent benefits of covariant parameters while preserving Liskov substitutability.
parametric polymorphism) and bounded quantification, the previous examples can be written in a type-safe way.
Similarly, in recent versions of Java the Comparable interface has been parameterized, which allows the downcast to be omitted in a type-safe way: Another language feature that can help is multiple dispatch.
On the other hand, to ensure type safety the language still must require the left-over parameters to be at least as general.
In C#, each type parameter of a generic interface can be marked covariant (out), contravariant (in), or invariant (no annotation).
For example, we can define an interface IEnumerator
Second, in order to get unique best solutions the type system must allow bivariant parameters (which are simultaneously co- and contravariant).
And finally, the variance of type parameters should arguably be a deliberate choice by the designer of an interface, not something that just happens.
For example, consider an OCaml datatype T which wraps a function The compiler will automatically infer that T is contravariant in the first parameter, and covariant in the second.
Java provides use-site variance annotations through wildcards, a restricted form of bounded existential types.
extends T>, there are three ways to form a subtype: by specializing the class C, by specifying a tighter bound T, or by replacing the wildcard ?
The mnemonic for Producer Extends, Consumer Super (PECS), from the book Effective Java by Joshua Bloch gives an easy way to remember when to use covariance and contravariance.
super T conveys the information that max calls only contravariant methods from the Comparable interface.
However, they have been criticized for the complexity they add to the language, leading to complicated type signatures and error messages.
[19] This design works well with declaration-site annotations, but the large number of interfaces carry a complexity cost for clients of the library.
And modifying the library interface may not be an option—in particular, one goal when adding generics to Java was to maintain binary backwards compatibility.
In a conference presentation[20] Joshua Bloch criticized them as being too hard to understand and use, stating that when adding support for closures "we simply cannot afford another wildcards".
[21] Later versions of Scala added Java-style existential types and wildcards; however, according to Martin Odersky, if there were no need for interoperability with Java then these would probably not have been included.
[22] Ross Tate argues[23] that part of the complexity of Java wildcards is due to the decision to encode use-site variance using a form of existential types.
This can make error messages harder to read, because they refer to type variables that the programmer did not directly write.
extends Animal> will give an error like Since both declaration-site and use-site annotations can be useful, some type systems provide both.