PHPGenerics / php-generics-rfc

Mirror of https://wiki.php.net/rfc/generics for easier collaboration
186 stars 1 forks source link

Covariance and contravariance #33

Closed iluuu1994 closed 6 years ago

iluuu1994 commented 6 years ago

First of all, I'm excited this is being worked on! Thanks guys ❤️

When reading the RFC I noticed you're allowing covariance when using instanceof: https://github.com/mindplay-dk/php-generics-rfc/blob/master/generics.txt#L388-L389

I'm not sure if this is a good idea. Let's look at the following example:

class Box<T> {
    public setValue(T $value) {}
}

class HeadGear {}
class Hat extends HeadGear {}
class Helmet extends HeadGear {}

// Hypothetical cast
$box = (Box<HeadGear>) new Box<Hat>();
$box->setValue(new Helmet()); // Type error

I hope you get the point. Box<Hat> cannot be treated as Box<HeadGear> because setValue will only ever accept values of type Hat but not any HeadGear.

Whether or not a type is covariant/contravariant depends on how it is used. If the type is only ever returned but never accepted as a parameter it is covaraint. If the type is only ever accepted as a parameter but never returned is contravariant. If the type is both returned and accepted as a parameter the type it invariant (which means neither covariant nor contravariant).

In C# you can use the in and out keywords to specify covariance and contravariance. When flagging a generic type with out/in the compiler prevents you from using it as a parameter type or return type respectively.


Covariant

interface RandomValueGenerator<out T> {
    public function getRandomValue(): T;
}

var_dump(RandomValueGenerator<int> instanceof RandomValueGenerator<mixed>); // true
var_dump(RandomValueGenerator<mixed> instanceof RandomValueGenerator<int>); // false

Contravariant

interface VarDumper<in T> {
    public function dump(T $value);
}

var_dump(VarDumper<int> instanceof VarDumper<mixed>); // false
var_dump(VarDumper<mixed> instanceof VarDumper<int>); // true

Invariant

interface Box<T> {
    public function setValue(T $value);
    public function getValue(): T;
}

var_dump(Box<int> instanceof Box<mixed>); // false
var_dump(Box<mixed> instanceof Box<int>); // false
morrisonlevi commented 6 years ago

I haven't read the RFC in a while; we've been discussing things weekly via conference calls. We do plan on using in and out variance. I think the RFC is simply out-of-date here.

iluuu1994 commented 6 years ago

@morrisonlevi Good to hear! Should I close the issue or leave it as a reminder?

natebrunette commented 6 years ago

Feel free to close, this is something that's definitely on our radar.

iluuu1994 commented 6 years ago

@natebrunette Gotcha!

teohhanhui commented 4 years ago

Can't the covariant / contravariant / invariant nature of the generic type be inferred from its presence / absence as return type and/or parameter type?

mindplay-dk commented 4 years ago

Can't the covariant / contravariant / invariant nature of the generic type be inferred from its presence / absence as return type and/or parameter type?

It is in TypeScript and other languages, so yeah.

Apparently, there are some use-cases where that's not enough - I've never encountered them myself though, but people much smarter than me seem to think it's necessary to have control over this.

They've tried to explain why, and I've mostly failed to understand it. My needs are pretty basic, I guess - TypeScript and Dart both work fine for me, though I have seen a few people asking the maintainers of those languages to add operators to control variance, so... I don't know.