7 ms·
Covariance and contravariance are just monotonicity and anti-monotonicity, applied to types ordered by subtyping. That is: if we have a function on types, say,
by rntz 9y ago
Covariance and contravariance are just monotonicity and anti-monotonicity, applied to types ordered by subtyping.
That is: if we have a function on types, say, the function `f` defined by:
f(x) = Int -> x
Then we say `f` is covariant because it is monotone: it preserves the subtype ordering on its argument. That is:
if x <: y, then f(x) <: f(y)
Similarly, if we consider `f` defined by:
f(x) = x -> Int
then this `f` is contravariant because it is anti-monotone: it reverses the subtype ordering on its argument. That is:
if x <: y, then f(y) <: f(x)
People tend to find covariance more intuitive than contravariance; unless the issue is pointed out, they tend to assume everything is covariant. They see a type, say:
dog -> dog
and they assume "oh, every dog is an animal, so I can put 'animal' in place of 'dog' and it'll be more general (i.e. a supertype)". This is false, as the article points out.
- dheera 9y agoWhat about covfefe?
- techno_modus 9y ago> People tend to find covariance more intuitive than contravariance; unless the issue is pointed out, they tend to assume everything is covariant. I can easily understand where covariance is useful and meaningful while contravariance is somehow counterintuitive for me. Therefore I doubt if it is really needed (except for being a nice formal counterpart of covariance). In other words, would it be more natural to simply use covariance as a default rule while contravariance either being not available at all or used as a kind of exception (where you need to know what you are doing and understand the consequences)?
- alipang 9y agoIf you ever want function types you will always need both co- and contravariance as pointed out in OP's articles. Using covariance as a default is also not sound. Many things are "invariant", meaning neither co- or contravariant. This is already a sensible default. (It is however possible to automatically derive either property in some cases)
- Sean1708 9y ago> Therefore I doubt if it is really needed Well the article seems to suggest that it absolutely is needed for function arguments.
- Sharlin 9y agoIt is not essential, and neither is covariance. For instance, C and C++ function types are invariant (but C++ virtual member functions are properly variant, and so is std::function). If a language does not support variance, it can always be emulated with wrapper functions. Eg. given types A :> B :> C fn foo: (B -> B) -> () fn bar: A -> C foo(b -> bar(b)) where the lambda has (inferred) type B -> B.
- techno_modus 9y agoYou are right, I probably did not express my idea clearly. What I meant is that there is only one meaningful pattern: returns are covariant and arguments are contravariant. If it is so (and the articles seems to confirm this) then why do we need special annotations? The system could itself assume that any argument (by default) is contravariant and any return (by default) is covariant. Does it make sense?
- lmm 9y ago> The system could itself assume that any argument (by default) is contravariant and any return (by default) is covariant. I guess languages could infer variance for generic parameters on methods/functions, and perhaps should - it would align with how type systems generally work. But that's a small part of the use case - variance mainly matters when you have generic values. If we have a Frobnicator[A, B] it would be very difficult for the language to infer whether variance for frobnicator values should run like for A => B, like for B => A, or some other way.
- renox 9y agoExcept that in many languages you have also input/ouput parameters that should be invariant, I agree.
- Klockan 9y agoBoth are just as common. The most trivial cases are getters (covariant) and setters (contravariant). A getter which returns a dog is obviously a sub type of a getter which returns an arbitrary animal since the returned dog can do everything that an animal can. However it is different for setters, a setter which consumes an animal can also consume dogs, hence it is a subtype of a setter which consumes dogs. The reverse isn't true since a setter which consumes dogs can't also take animals which means that you can't pass it as an animal setter.
- techno_modus 9y agoSo we could (by default) assume that all getter (returns) are invariant, and all setter (arguments) are contravariant. If we forget about invariance for a moment then this means essentially that there is only one default pattern and hence no need in explicitly distinguishing and annotating covariance and contravariance - everybody simply knows this default rule. (I am simply reasoning after reading the article - I do not know if it can be done or makes sense.)
- rntz 9y agoIt can mostly be done. The OCaml programming language does exactly this - it infers co/contravariance. However, when describing a generic interface which has as part of it a function on types (usually called a "type constructor"), you need to say whether that type constructor is supposed to be covariant, contravariant, or invariant. For example, if I were describing a sequence interface, I would write module type SEQ = sig (* the "+" means that seq must be covariant *) type +'a seq val empty : 'a seq val singleton : 'a -> 'a seq val append : 'a seq -> 'a seq -> 'a seq val lookup : int -> 'a seq -> 'a option exception IndexOutOfBounds end;; See https://blogs.janestreet.com/a-and-a/ https://blogs.janestreet.com/a-and-a/ for a nice explanation.
- lmm 9y agoThe most common cases are that anything that "receives" or "absorbs" is contravariant, while anything that "emits" or "produces" is covariant. If you're working with streams, Source is covariant and Sink is contravariant. A read-only collection is covariant, a write-only collection (sounds stupid I know, but it can be a good way to model e.g. an audit log) is contravariant. A function A => B is covariant in B but contravariant in A. Contravariance is probably the least common, sure, but covariance should probably be opt-in too. A lot of languages treat things as covariant-by-default even for mutable types that are actually invariant, and so suffer from some variant of this unsoundness bug: val dogs: List[Dog] = List[Dog]() val animals: List[Animal] = dogs val cat: Cat = Cat() animals.append(cat) val dog: Dog = dogs(0) // dog is now a Cat
- jitl 9y agoWow this explanation makes so much more sense! Thanks, Math Professor.
- arcbyte 9y agoYou're just making up symbols at this point. What is "<:" supposed to represent?
- ivanche 9y agoLiterally 5th line from the article "A <: B means A is a subtype of B."
- arcbyte 9y agoThe 5th line does not say that. Now you're making things up.
- JD557 9y agox <: y usually means "x is a subtype of y" This is the notation used in Scala, although I've seen it being used in more informal settings.
- Xcelerate 9y agoI would have Googled before making the strong claim that someone is making stuff up. "<:" is widely used notation for "is a subtype of". I'm familiar with its usage in Julia.
- arcbyte 9y agoI definitely googled it. Bing reported no results. The only relevant google result is something in Scala that doesn't seem to apply.
- techno_modus 9y ago> Covariance and contravariance are just monotonicity and anti-monotonicity I am confused. It seems that monotonicity is a property of the function itself, that is, how it is implemented and what (type) it returns depending on what (type) it gets. In other words, we vary input value (real) type and observe what happens with the output value (real) type. Only one function is involved - monotonicity is its inherent property. But covariance/contravariance is about '<:' relation between function signatures (the contract, not how it is implemented). Once this relation has been somehow defined, we can check, for example, if one function can be safely applied instead of another function. In other words, we vary signatures by changing input/output declared types and then decide about the relation among them. Why do you say that monotonicity is similar or equivalent to covariance? They seem to be different properties.
- mmarx 9y agoMonotonicity is a property of functions that is relative to some ordering relation (e.g., f(x) = x + 42 as a function on natural numbers is monotone with respect to the natural ordering). In this case, that ordering relation is <:, and covariant functions are precisely those function that are monotone with respect to <:; as are contravariant functions the antitone functions with respect to <:.