This is the first in a series of posts exploring how we might implement generic co- and contra-variance in a hypothetical future version of VB. This is not a promise about the next version of VB; it's just one possible proposal, written up here to get early feedback from potential users.
Look at the above code. You'd think it should work. It's a common enough scenario: there's a library function which handles some kind of data type, but you've inherited from that type for your own purposes. How can you pass a collection of your own inherited type into the library function?
We're considering a VB language feature to support this kind of conversion. The topic is called "Co- and contra-variance", or just "variance" for short. Variance has actually been in the CLR since 2005 or so, but no one's yet released a .net language that uses it. There are other languages with it, though. Here are some links to what people have written on the topic.
Eric Lippert's blog about how one might add variance to C#
[PDF] Contravariance for the rest of us, a technical report from HP
[PDF] Conflict without a cause, a pivotal 1995 theory paper on it
Rick Byers about variance in the CLR
Bruce Eckel's thoughts on contravariance
Co- and contravariance in java, through "wildcard generics"
I'll talk about how you could use variance practically in VB, where it could make your code easier or cleaner, and what problems it might solve if we implement it. There's much more to variance than just converting apples into fruit, and it gets trickier as the above articles show, but I think the practical syntax and examples that we're proposing for VB could demystify it.
Here's a practical problem I had just yesterday that could have been solved by variance:
For this first article, though, we'll stick to just fruit.
We're thinking of using contextual keywords "Out" and "In" to introduce variance:
When the interface declares its type parameter as "Out", it makes a promise to only ever use that type for function returns (* or other places where it outputs data). The interface will be held to that promise: if it tries to do "Sub f(ByVal x As T)" then it's a compile-time error. (A lot of the design is constrained by how the CLR uses and represents variance; we want compatibility with other .Net languages.)
It's this "Out" promise that lets the CLR convert the interface:
In general, if you have a generic interface IReadOnly(Of Out T), then you can cast from it from "Of T" to something that T converts to. And it's typesafe, for obvious reasons.
Variance conversions are typesafe and efficient. It takes only a single IL instruction to do a variance conversion. There are NO runtime checks required. (This differs from arrays, which have to do a runtime type-check every time you put something into the array.)
Interfaces with "Out" parameters are called covariant in the literature.
"In" parameters are the opposite. When an interface declares one of its type parameter T as "In", it's promising only ever to use T for ByVal arguments (* or other places where the interface takes data in). Again the interface will be held to that promise: if it tries to do "Function f() as T" then it's a compile-time error.
And "In" parameters let you do the opposite kinds of conversion:
Interfaces with "In" parameters are called contravariant in the literature.
"In" and "Out" together
Up until the early 1990s, people used to argue about whether "In" or "Out" parameters were the right thing to have. We now know that they're both right! The first convincing argument for this was in 1995 in Giuseppe Castagna's 1995 research paper "Conflict Without A Cause" [PDF].
Here are two examples for why they're both right, and how they both work together:
Pipes: using "In" and "Out" for internal and external contracts
We are eager for customer feedback as we consider whether to add this feature to the VB language, and think about how it might work. Please add your comments.
I'll be writing more on variance (a lot more) in the weeks to come.
PS. As for the title of this article, here's what we envisage...