Questions tagged [contravariance]

Within the type system of a programming language, covariance and contravariance refers to the ordering of types from narrower to wider and their interchangeability or equivalence in certain situations (such as parameters, generics, and return types)

Within the type system of a programming language, a typing rule or a type conversion operator is:

covariant if it preserves the ordering, ≤, of types, which orders types from more specific to more generic;

contravariant if it reverses this ordering, which orders types from more generic to more specific;

invariant if neither of these applies.

These terms come from category theory, which has a general definition of covariance and contravariance that unifies the computer science definition of these terms with the definition used in vector spaces.

This distinction is important in considering argument and return types of methods in class hierarchies. In object-oriented languages such as Python, if class B is a subtype of class A, then all member functions of B must return the same or narrower set of types as A; the return type is said to be covariant. On the other hand, if the member functions of B take the same or broader set of arguments compared with the member functions of A, the argument type is said to be contravariant. The problem for instances of B is how to be perfectly substitutable for instances of A. The only way to guarantee type safety and substitutability is to be equally or more liberal than A on inputs, and to be equally or more strict than A on outputs. Note that not all programming languages guarantee both properties in every context, and that some are unnecessarily strict; they are said not to support covariance or contravariance in a given context; the behavior of some programming languages is discussed below.

Typical examples:

The operator which constructs array types from element types is usually covariant on the base type: since String ≤ Object then ArrayOf(String) ≤ ArrayOf(Object). Note that this is only correct (i.e. type safe) if the array is immutable; if insert and remove operators are permitted, then the insert operator is covariant (e.g. one can insert a String into an ArrayOf(Object)) and the remove operator is contravariant (e.g. one can remove an Object from an ArrayOf(String)). Since the mutators have conflicting variance, mutable arrays should be invariant on the base type.

Let f be a function with a parameter of type T and let g be a function with a parameter of type S, both with the same return type. If T ≤ S, then g ≤ f. g can replace f anywhere, since it cares less about the type of its parameter, and both return the same type. Because the subtype relation between the argument type and the functions is reversed, the function type is said to be contravariant on its argument type.

Let f be a function that returns a value of type T and let g be a function that returns a value of type S, both with the same parameter type. If T ≤ S, then f ≤ g. f can replace g anywhere, since it returns only a subset of all possible values returned by g, and both take the same argument. Because the subtype relation between the argument type and the functions is preserved, the function type is said to be covariant on its return type.

In object-oriented programming, substitution is also implicitly invoked by overriding methods in subclasses: the new method can be used where the old method was invoked in the original code. Programming languages vary widely on their allowed forms of overriding, and on the variance of overridden methods' types.

540 questions
11
votes
2 answers

Can I implement an interface that contains a property that is of child type to what is required by the interface?

I am receiving the following error: ClassName.PropertyName cannot implement IClassType.PropertyName because it does not have the matching return type of IBasePropertyType Now, for the code: public class ClassName : IClassType { public…
Mark Avenius
  • 13,679
  • 6
  • 42
  • 50
11
votes
1 answer

Scala - Co/Contra-Variance as applied to implicit parameter selection

I've got a trait like this: trait CanFold[-T, R] { def sum(acc: R, elem: T): R def zero: R } With a function that works with it like this: def sum[A, B](list: Traversable[A])(implicit adder: CanFold[A, B]): B = …
Alexandru Nedelcu
  • 8,061
  • 2
  • 34
  • 39
10
votes
2 answers

Contravariance invalid when using interface's delegate as a parameter type

Consider the contravariant interface definition with a delegate: public interface IInterface { delegate int Foo(int x); void Bar(TInput input); void Baz(TInput input, Foo foo); } The definition of Baz fails with an…
V0ldek
  • 9,623
  • 1
  • 26
  • 57
10
votes
5 answers

General 'map' function for Scala tuples?

I would like to map the elements of a Scala tuple (or triple, ...) using a single function returning type R. The result should be a tuple (or triple, ...) with elements of type R. OK, if the elements of the tuple are from the same type, the mapping…
Stefan Endrullis
  • 4,150
  • 2
  • 32
  • 45
10
votes
3 answers

Why is C# 4.0's covariance/contravariance limited to parameterized interface and delegate types?

Is this a limitation of the CLR or are there compatibility concerns with existing code? Is this related to the messed up variance of delegate combining in C# 4.0? Edit: Would it be possible to have a language using co-/contravariance without that…
soc
  • 27,983
  • 20
  • 111
  • 215
10
votes
2 answers

Is it possible to implement the "virtual constructor" pattern in C# without casts?

I'm writing a program that writes C# that eventually gets compiled into an application. I would like each of the generated types to provide a "deep clone" function which copies the entire tree of data. That is, I want someone to be able to do: var x…
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
10
votes
3 answers

Variance rules in C#

The Exact rules for variance validity are a bit vague and not specific. I'm going to list the rules for what makes a type valid-covariantly, and attach some queries and personal annotations to each of those rules. A type is valid covariantly if it…
Garrett Biermann
  • 537
  • 1
  • 4
  • 12
10
votes
2 answers

Scala: Ordering contravariance

Is there any reason why Scala's Ordering trait is not contravariant? A motivating example follows. Suppose I want to perform an ordered insert. I may have a function with the signature def insert[A, B >: A](list: List[A], item: A)(implicit ord:…
Duarte Nunes
  • 852
  • 7
  • 16
9
votes
2 answers

I really don't understand this co/contravariance thing... I cannot have both generic get and set methods?

I think I'll explain my problems with some examples.. interface IModel {} class MyModel : IModel {} interface IRepo where T: IModel { } class Repo : IRepo { } // Cannot implicitly convert.. An explicit convertion exists. Missing…
simendsjo
  • 4,739
  • 2
  • 25
  • 53
9
votes
1 answer

Is there a way to determine the Variance of an Interface / Delegate in C# 4.0?

So now that we have generic Covariance and Contravariance on interfaces and delegates in C#, I was just curious if given a Type, you can figure out the covariance/contravariance of its generic arguments. I started trying to write my own…
BFree
  • 102,548
  • 21
  • 159
  • 201
9
votes
1 answer

Internal Implementation of AsEnumerable() in LINQ

I have two questions: Question 1 Background : I noticed when looking at the implementation of 'AsEnumerable()' method in LINQ from Microsoft, which was: public static IEnumerable AsEnumerable(this IEnumerable source) { …
RaM
  • 1,126
  • 10
  • 25
9
votes
6 answers

Generic constraint for Action doesn't work as expected

I am having some trouble understanding why the following snippet does not give me an error public void SomeMethod(T arg) where T : MyInterface { MyInterface e = arg; } But this one, which I would expect to work due to the generic type…
Jim Jeffries
  • 9,841
  • 15
  • 62
  • 103
8
votes
1 answer

Why is the concept in template template argument not verified?

C++20 allows the program to specify concept for template template argument. For example, #include template concept Char = std::same_as; template struct S {}; template