Covariance and Contravariance
mikeash.com
mikeash.com
Say that you want a function which returns a Reader:
type ReaderMaker func() io.Reader
A function which returns a concrete type which implements Reader cannot be substituted. This makes sense because interface types are always a two-word tuple, and a concrete type could be a one-word pointer, a big struct, etc. var invalid ReaderMaker = func() *io.PipeReader { return &io.PipeReader{} }
More subtly, a function which returns a different but compatible interface type also won't work. // This assignment is invalid because although anything that implements
// ReadCloser also implements Reader, a ReadCloser itable won't necessarily
// have Read() at the same offset as a Reader itable.
var invalid ReaderMaker = func() io.ReadCloser { return &io.PipeReader{} }
http://play.golang.org/p/5PPpVZhap_* edit: fixed swapped types in comment
Some of what you gain by its lauded "simplicity", you give back with type system wrestling.
I regard that (class names ending in "er") as an antipattern, and have seen them again and again in Java programs. I call them "thingers". Except in simulation programs, objects should just sit there passively and have things done to them by methods. For example, rather than a parser class you should have a parse method, which belongs to a grammar class, and takes a list of tokens as its argument, and returns a parse tree.
Here, it's simpler just to have a method/function/procedure called read() which can be an method belonging to whatever classes of object you want to read from. And if you want to make something, just call new rather than have a factory class.
I'm not the only one who doesn't like thingers: see http://steve-yegge.blogspot.co.uk/2006/03/execution-in-kingd...
Now, before you argue that I don't grok object orientation, I'll tell you that I've added object orientation to Prolog, and to a Lisp dialect (twice). (I've also got several years of OO application programming, in Java and Lisp, behind me.) Perhaps you could call me an OO skeptic though - I can accept that.
I guess I do that anyway, since it feels cleaner, though just as often there isn't one, or it's already taken (like [Input|Output]Stream in my example), and I can't say I really feel that bad about nounifying a verb.
In fact, when you nounify a verb (in other words: turn a function into an object) you can add so much to it. For instance, you could track how long it takes to execute, you can replace some of its constituent components (e.g. GOF Strategy Pattern), you could tune parameters that aren't part of its computational input (something like buffer size, or enabling caching, etc)... these are all things that would otherwise have to be communicated through a lengthy list of parameters, and turning them into object state frees up your actual method signature for the parameters that really matter.
http://blog.ezyang.com/2014/11/tomatoes-are-a-subtype-of-veg...
http://www.businessinsider.com/supreme-court-tomato-is-veget...
You can't make jam from tomatoes. Or at least it wouldn't be called jam. You can make jam from rhubarb, so in some sense that is a fruit.
Similarly, you can make soup out of tomatoes, but not out of rhubarb.
So, from the point of view of a cook, tomatoes can be considered vegetables and rhubarb fruit.
I think we need fuzzy classes. :-)
attributed to Miles Kington
Thanks to his lecture [1], I'll never forget "Function subtyping is contravariant in its argument types and covariant in its return type" - even though I have to think to remember whether the subtype is the contravariant or covariant, I remember the principle very very clearly due to his antics.
In math you often have a class of objects and functions on those objects (say, groups with group homomorphisms, or simpler, types in a programming language with functions that take one type to another). Then in math one often learns about an object by associating another object with it. For example, a topological space can be associated with a specific kind of group that tells you about the space's structure.
Then you can ask how this "association" behaves with the functions on the object. I'll use notation now, and say that for an object X the "associated" object is G(X). Often G carries over to functions, so that if f is a function X -> Y then G(f) is a function G(X) to G(Y). Such an "association" is called covariant because it preserves the ordering of domain -> codomain. The "association" is instead called contravariant if it reverses the order. E.g., f: X -> Y becomes G(f): G(Y) -> G(X). Of course, I'm using "association" but the real word for G is functor.
Like you, I'd be interested to know whether there's a deeper connection in the terminology.
The inner product lets you view vector spaces as spaces of transformations already so the same sort of idea comes out now viewing vector spaces as maps in their own right.
The computer science meaning is inspired by category theory (and can be seen as a simple instance of it). But the vector/functor meanings seem to be unrelated.
There is a bit of a connection, because the operation ٭ which takes a vector space V to the set of 1-forms V٭ is a contravariant functor. (I.e., linear maps V→U give maps of 1-forms U٭→V٭). But in physics, we say that the members of V are contravariant and the members of V٭ are covariant, i.e. the physics notion is about individual spaces, not about mappings between spaces.
Specifically you need the category where the objects are types and the morphisms indicate inheritance (i.e. A -> B means that A is a subtype of B).
Then a functor F is covariant if A -> B implies F(A) -> F(B). Which is precisely what it means to be a covariant generic type.
Note: you shouldn't confuse this category with the category where the objects are types and the morphisms are functions that map values of one type to values of another type.
('<:' means subtype and '>:' supertype )
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+...
I now work in a language that doesn't really support building "types" in this way and I can't say I miss thinking about this sort of thing nor consider its lack to be a problem. Types in this sense strike me as more of a boondoggle of overhead that gets in the way more than helps achieve the goal of whatever I'm writing.
What language are you working in now? Some really don't support variance (Go, apparently, and C would be an obvious example) but many others support it but just don't enforce it (Python and all the other dynamically-typed duck-typed languages).
More expressive type systems let you write programs that are more internally consistent (and, by proxy, more correct), but at the cost of more work to write the appropriate types, and to read and understand such types written by others.
A lot of PL research is going into advanced static type systems, and I think that’s a good thing, but it doesn’t necessarily translate into better programmer ergonomics or productivity.
Maybe we just need new notations for expressing types (and code!)—notations that are more in line with how people naturally think about these things.
That said, I always love seeing other perspectives, so a comment on the article describing how Scala gets it right would be welcome.
E.g.
let f1: A -> B = ...
let f2: C -> D = f1
(...)
Applied here, the above code works if A is a subtype of C, and if B is a supertype of D.
Put concretely:
let f1: Animal -> Animal = ...
let f2: Cat -> Thing = f1
So now Animal is a subtype of a Cat, and a supertype of Thing?But I finally understand what covariance and contravariance means, so thanks!
--
Also, related, could somebody more proficient in English than I am help me understand which direction does the substitution go here?
"It says, in short, that an instance of a subclass can always be substituted for an instance of its superclass."
Is it "an instance of subclass can replace an instance of superclass", or "it can be replaced by"? Or is it context-dependent?
I ask because I'm not sure about the grammar aspect, and in my native language (Polish) we have a few ambiguities like that, which I sometimes jokingly write down with an arrow, e.g. "A can be ---substitutes for--> B" or "A can be <--substituted for-- B", to make it damn explicit which direction the word works.
Cat < Animal < Thing
So if the function takes any animal, of course it can take a cat (so its covariant in argument). And because all animals are things, we can say the output type of f is Thing.. its just less accurate than Animal but still correct - that is the contravariance in the result.
Cat extends Animal means "Cat can do everything Animal can, plus maybe more stuff". So all cats are animals, but not all animals are cats. So Cat is a subtype of Animal.
For the second question,
"an instance of subclass can replace an instance of superclass" is the right one.
Really it makes a lot of sense intuitively. e.g say Int is a subclass of Number (a simpler way to read is just "Int is a Number") . then anything you can do with numbers, you can do with ints too so we can put an int in place of a number.
Thx for the answer to the second question :). I think my mind put an additional "by" in that sentence and got confused.
Thing -> Cat-------------------------
I think it's confusing for 2 reasons: passive voice and prepositions.
1) Passive voice - instead of saying A substitutes for B (active voice) it says A can be substituted for B (passive voice.)
2) Prepositions - "be substituted" can collocate with "for" or "by" with the opposite meaning!
A <-- can be substituted by <-- B (use B where you would expect A)
A --> can be substituted for --> B (use A where you would expect B)
The basic premise behind subtypes is that an object of a subtype can be used anywhere that an object of its supertype can be used. Just as an example, if you replace f1 : Animal -> Animal with f2 : Cat -> Thing, there might be some animals that aren't cats, so f2 can't replace f1 everywhere. This is the reason that functions are contravariant on their input.
Another example is "subscribes" - I had a lot of trouble understanding Rx (Reactive Extensions) until I realized that they use "subscription" in the reverse way I'm used to. When I hear subscription, I think of how I might subscribe or have a subscription to a magazine. But they mean subscribe/subscription in terms of how a magazine company might "subscribe" a customer. Which completely flips the arrow.
"This is a key rule: function return values can changed to subtypes, moving down the hierarchy, whereas function parameters can be changed to supertypes, moving up the hierarchy."
The part allowing a super type in an overridden methods parameters stood out. I tried this out in scala and it did not compile. Intuitively it doesn't make sense to me to allow wider types.
Thing -> Animal -> Dog Thing -> Animal -> Cat Thing -> Instrument -> Tuba
Lets say Thing has the following properties: - IsVisible - Mass - CanMove
Lets say Tube has the following properties: - PlayNote
And lets say that Cat has: - Meow
Lets take the following contrived example for overriding:
Class Debugger {
void debug(Cat cat);
}
Class ThingDebugger : Debugger {
void debug(Thing thing);
}
Going "wider" to Thing works because Cats, Dogs, and Tuba all have the properties that a Thing has, because they are instances of Thing. Going "narrower" works for the same reason: Class Wrangler {
Animal wrangle();
}
Class DogWrangler : Wrangler {
Dog wrangle();
}
A Dog can do anything an Animal can do because it is an Animal.The type system is doing its best to save you from the following:
Class Janky {
void ThisIsOK();
Animal PrepareForCrash();
}
Class AlsoJanky : Janky {
void ThisIsOK();
Instrument PrepareForCrash();
}
Class StillJanky : Janky {
void ThisIsOK();
Thing PrepareForCrash();
}
In this case any mixing of PrepareForCrash will leave you with returned types that don't completely share an interface. If the compiler let you do that, you'd probably do this at some point start mixing the subtypes of Janky and calls to PrepareForCrash would return Tubas when you expect Cats and then all hell would break loose. Anyone who's used scripting languages should be familiar with something like this happening at one point or another.first you can't call super() in that method without a downcast. how does that count as overriding the method?
it's a big problem if you have another subclass of Debugger that does not change the parameter type. you now have 2 subclassed of Debugger, one has a method debug(cat: Cat). the other has a method debug(cat: Thing). that accepts cats, dogs, and tubas. these subtypes of Debugger are not safely interchangeable.
this does not compile in scala, I'm surprised swift would allow it.
https://en.wikipedia.org/wiki/Covariance_and_contravariance_...
> The use of both terms in the modern context of multilinear algebra is a specific example of corresponding notions in category theory.
[1]http://stackoverflow.com/questions/2723397/java-generics-wha...
A covariant object transforms like the gradient of a scalar.
I guess this is really simple to people with the nessecary maths background to understand this, but for me I just don't see the connection between vectors/gradients and type systems.
There isn't really one besides the terminology.