Variance is a topic that comes up fairly often in type systems and can be a bit
confusing the first time you hear it. Let’s walk through each form of variance.
First we’ll setup a couple of classes that extend one another.
1
2
3
|
class Noun {}
class City extends Noun {}
class SanFrancisco extends City {}
|
{"value":"class Noun {}\nclass City extends Noun {}\nclass SanFrancisco extends City {}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"Noun","line":1,"start":6,"end":10},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":11,"end":12},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":12,"end":13},{"type":"T_CLASS","context":"normal","value":"class","line":2,"start":14,"end":19},{"type":"T_IDENTIFIER","context":"normal","value":"City","line":2,"start":20,"end":24},{"type":"T_EXTENDS","context":"normal","value":"extends","line":2,"start":25,"end":32},{"type":"T_IDENTIFIER","context":"normal","value":"Noun","line":2,"start":33,"end":37},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":38,"end":39},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":39,"end":40},{"type":"T_CLASS","context":"normal","value":"class","line":3,"start":41,"end":46},{"type":"T_IDENTIFIER","context":"normal","value":"SanFrancisco","line":3,"start":47,"end":59},{"type":"T_EXTENDS","context":"normal","value":"extends","line":3,"start":60,"end":67},{"type":"T_IDENTIFIER","context":"normal","value":"City","line":3,"start":68,"end":72},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":73,"end":74},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":74,"end":75}],"errors":[]}
We’ll use these classes to write a method that has each kind of variance.
Note: The *variantOf
types below are not a part of Flow, they are being
used to explain variance.
Invariance
1
2
3
4
5
|
function method(value: InvariantOf<City>) {...}
method(new Noun());
method(new City());
method(new SanFrancisco());
|
{"value":"function method(value: InvariantOf<City>) {...}\n\nmethod(new Noun()); // error...\nmethod(new City()); // okay\nmethod(new SanFrancisco()); // error...\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":1,"start":9,"end":15},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":1,"start":16,"end":21},{"type":"T_COLON","context":"type","value":":","line":1,"start":21,"end":22},{"type":"T_IDENTIFIER","context":"type","value":"InvariantOf","line":1,"start":23,"end":34},{"type":"T_LESS_THAN","context":"type","value":"<","line":1,"start":34,"end":35},{"type":"T_IDENTIFIER","context":"type","value":"City","line":1,"start":35,"end":39},{"type":"T_GREATER_THAN","context":"type","value":">","line":1,"start":39,"end":40},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":40,"end":41},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":42,"end":43},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":1,"start":43,"end":46},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":46,"end":47},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":3,"start":49,"end":55},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":55,"end":56},{"type":"T_NEW","context":"normal","value":"new","line":3,"start":56,"end":59},{"type":"T_IDENTIFIER","context":"normal","value":"Noun","line":3,"start":60,"end":64},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":64,"end":65},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":65,"end":66},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":66,"end":67},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":67,"end":68},{"type":"Line","context":"comment","value":"// error...","line":3,"start":77,"end":88},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":4,"start":89,"end":95},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":95,"end":96},{"type":"T_NEW","context":"normal","value":"new","line":4,"start":96,"end":99},{"type":"T_IDENTIFIER","context":"normal","value":"City","line":4,"start":100,"end":104},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":104,"end":105},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":105,"end":106},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":106,"end":107},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":107,"end":108},{"type":"Line","context":"comment","value":"// okay","line":4,"start":117,"end":124},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":5,"start":125,"end":131},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":131,"end":132},{"type":"T_NEW","context":"normal","value":"new","line":5,"start":132,"end":135},{"type":"T_IDENTIFIER","context":"normal","value":"SanFrancisco","line":5,"start":136,"end":148},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":148,"end":149},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":149,"end":150},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":150,"end":151},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":151,"end":152},{"type":"Line","context":"comment","value":"// error...","line":5,"start":153,"end":164}],"errors":[]}
- Invariance does not accept supertypes.
- Invariance does not accept subtypes.
Covariance
1
2
3
4
5
|
function method(value: CovariantOf<City>) {...}
method(new Noun());
method(new City());
method(new SanFrancisco());
|
{"value":"function method(value: CovariantOf<City>) {...}\n\nmethod(new Noun()); // error...\nmethod(new City()); // okay\nmethod(new SanFrancisco()); // okay\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":1,"start":9,"end":15},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":1,"start":16,"end":21},{"type":"T_COLON","context":"type","value":":","line":1,"start":21,"end":22},{"type":"T_IDENTIFIER","context":"type","value":"CovariantOf","line":1,"start":23,"end":34},{"type":"T_LESS_THAN","context":"type","value":"<","line":1,"start":34,"end":35},{"type":"T_IDENTIFIER","context":"type","value":"City","line":1,"start":35,"end":39},{"type":"T_GREATER_THAN","context":"type","value":">","line":1,"start":39,"end":40},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":40,"end":41},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":42,"end":43},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":1,"start":43,"end":46},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":46,"end":47},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":3,"start":49,"end":55},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":55,"end":56},{"type":"T_NEW","context":"normal","value":"new","line":3,"start":56,"end":59},{"type":"T_IDENTIFIER","context":"normal","value":"Noun","line":3,"start":60,"end":64},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":64,"end":65},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":65,"end":66},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":66,"end":67},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":67,"end":68},{"type":"Line","context":"comment","value":"// error...","line":3,"start":77,"end":88},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":4,"start":89,"end":95},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":95,"end":96},{"type":"T_NEW","context":"normal","value":"new","line":4,"start":96,"end":99},{"type":"T_IDENTIFIER","context":"normal","value":"City","line":4,"start":100,"end":104},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":104,"end":105},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":105,"end":106},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":106,"end":107},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":107,"end":108},{"type":"Line","context":"comment","value":"// okay","line":4,"start":117,"end":124},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":5,"start":125,"end":131},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":131,"end":132},{"type":"T_NEW","context":"normal","value":"new","line":5,"start":132,"end":135},{"type":"T_IDENTIFIER","context":"normal","value":"SanFrancisco","line":5,"start":136,"end":148},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":148,"end":149},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":149,"end":150},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":150,"end":151},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":151,"end":152},{"type":"Line","context":"comment","value":"// okay","line":5,"start":153,"end":160}],"errors":[]}
- Covariance does not accept supertypes.
- Covariance does accept subtypes.
Contravariance
1
2
3
4
5
|
function method(value: ContravariantOf<City>) {...}
method(new Noun());
method(new City());
method(new SanFrancisco());
|
{"value":"function method(value: ContravariantOf<City>) {...}\n\nmethod(new Noun()); // okay\nmethod(new City()); // okay\nmethod(new SanFrancisco()); // error...\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":1,"start":9,"end":15},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":1,"start":16,"end":21},{"type":"T_COLON","context":"type","value":":","line":1,"start":21,"end":22},{"type":"T_IDENTIFIER","context":"type","value":"ContravariantOf","line":1,"start":23,"end":38},{"type":"T_LESS_THAN","context":"type","value":"<","line":1,"start":38,"end":39},{"type":"T_IDENTIFIER","context":"type","value":"City","line":1,"start":39,"end":43},{"type":"T_GREATER_THAN","context":"type","value":">","line":1,"start":43,"end":44},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":44,"end":45},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":46,"end":47},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":1,"start":47,"end":50},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":50,"end":51},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":3,"start":53,"end":59},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":59,"end":60},{"type":"T_NEW","context":"normal","value":"new","line":3,"start":60,"end":63},{"type":"T_IDENTIFIER","context":"normal","value":"Noun","line":3,"start":64,"end":68},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":68,"end":69},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":69,"end":70},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":70,"end":71},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":71,"end":72},{"type":"Line","context":"comment","value":"// okay","line":3,"start":81,"end":88},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":4,"start":89,"end":95},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":95,"end":96},{"type":"T_NEW","context":"normal","value":"new","line":4,"start":96,"end":99},{"type":"T_IDENTIFIER","context":"normal","value":"City","line":4,"start":100,"end":104},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":104,"end":105},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":105,"end":106},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":106,"end":107},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":107,"end":108},{"type":"Line","context":"comment","value":"// okay","line":4,"start":117,"end":124},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":5,"start":125,"end":131},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":131,"end":132},{"type":"T_NEW","context":"normal","value":"new","line":5,"start":132,"end":135},{"type":"T_IDENTIFIER","context":"normal","value":"SanFrancisco","line":5,"start":136,"end":148},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":148,"end":149},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":149,"end":150},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":150,"end":151},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":151,"end":152},{"type":"Line","context":"comment","value":"// error...","line":5,"start":153,"end":164}],"errors":[]}
- Contravariance does accept supertypes.
- Contravariance does not accept subtypes.
Bivariance
1
2
3
4
|
function method(value: BivariantOf<City>) {...}
method(new Noun());
method(new City());
method(new SanFrancisco());
|
{"value":"function method(value: BivariantOf<City>) {...}\nmethod(new Noun()); // okay\nmethod(new City()); // okay\nmethod(new SanFrancisco()); // okay\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":1,"start":9,"end":15},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":1,"start":16,"end":21},{"type":"T_COLON","context":"type","value":":","line":1,"start":21,"end":22},{"type":"T_IDENTIFIER","context":"type","value":"BivariantOf","line":1,"start":23,"end":34},{"type":"T_LESS_THAN","context":"type","value":"<","line":1,"start":34,"end":35},{"type":"T_IDENTIFIER","context":"type","value":"City","line":1,"start":35,"end":39},{"type":"T_GREATER_THAN","context":"type","value":">","line":1,"start":39,"end":40},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":40,"end":41},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":42,"end":43},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":1,"start":43,"end":46},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":46,"end":47},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":48,"end":54},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":54,"end":55},{"type":"T_NEW","context":"normal","value":"new","line":2,"start":55,"end":58},{"type":"T_IDENTIFIER","context":"normal","value":"Noun","line":2,"start":59,"end":63},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":63,"end":64},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":64,"end":65},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":65,"end":66},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":66,"end":67},{"type":"Line","context":"comment","value":"// okay","line":2,"start":76,"end":83},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":3,"start":84,"end":90},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":90,"end":91},{"type":"T_NEW","context":"normal","value":"new","line":3,"start":91,"end":94},{"type":"T_IDENTIFIER","context":"normal","value":"City","line":3,"start":95,"end":99},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":99,"end":100},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":100,"end":101},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":101,"end":102},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":102,"end":103},{"type":"Line","context":"comment","value":"// okay","line":3,"start":112,"end":119},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":4,"start":120,"end":126},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":126,"end":127},{"type":"T_NEW","context":"normal","value":"new","line":4,"start":127,"end":130},{"type":"T_IDENTIFIER","context":"normal","value":"SanFrancisco","line":4,"start":131,"end":143},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":143,"end":144},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":144,"end":145},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":145,"end":146},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":146,"end":147},{"type":"Line","context":"comment","value":"// okay","line":4,"start":148,"end":155}],"errors":[]}
- Bivariance does accept supertypes.
- Bivariance does accept subtypes.
As a result of having weak dynamic typing, JavaScript doesn’t have any of
these, you can use any type at any time.
Variance in Classes
To get a sense of when and why the different kinds of variance matters, let’s
talk about methods of subclasses and how they get type checked.
We’ll quickly set up our BaseClass
which will define just one method that
accepts an input value with the type City
and a returned output also with
the type City
.
1
2
3
|
class BaseClass {
method(value: City): City { ... }
}
|
{"value":"class BaseClass {\n method(value: City): City { ... }\n}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"BaseClass","line":1,"start":6,"end":15},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":16,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":20,"end":26},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":26,"end":27},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":27,"end":32},{"type":"T_COLON","context":"type","value":":","line":2,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":34,"end":38},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":38,"end":39},{"type":"T_COLON","context":"type","value":":","line":2,"start":39,"end":40},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":41,"end":45},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":46,"end":47},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":48,"end":51},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":52,"end":53},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":54,"end":55}],"errors":[]}
Now, let’s walk through different definitions of method()
in a couple
different subclasses.
To start, we can define a SubClass that extends our BaseClass. Here you can see
that the value and the return type are both City just like in BaseClass:
1
2
3
|
class SubClass extends BaseClass {
method(value: City): City { ... }
}
|
{"value":"class SubClass extends BaseClass {\n method(value: City): City { ... }\n}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"SubClass","line":1,"start":6,"end":14},{"type":"T_EXTENDS","context":"normal","value":"extends","line":1,"start":15,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"BaseClass","line":1,"start":23,"end":32},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":37,"end":43},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":43,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":44,"end":49},{"type":"T_COLON","context":"type","value":":","line":2,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":51,"end":55},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":55,"end":56},{"type":"T_COLON","context":"type","value":":","line":2,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":58,"end":62},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":63,"end":64},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":65,"end":68},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":69,"end":70},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":71,"end":72}],"errors":[]}
This is okay because if something else in your program is using SubClass
as
if it were a BaseClass
, it would still be using a City
and wouldn’t cause
any issues.
More specific outputs — Good
Next, we’ll have a different SubClass
that returns a more specific type:
1
2
3
|
class SubClass extends BaseClass {
method(value: City): SanFrancisco { ... }
}
|
{"value":"class SubClass extends BaseClass {\n method(value: City): SanFrancisco { ... }\n}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"SubClass","line":1,"start":6,"end":14},{"type":"T_EXTENDS","context":"normal","value":"extends","line":1,"start":15,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"BaseClass","line":1,"start":23,"end":32},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":37,"end":43},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":43,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":44,"end":49},{"type":"T_COLON","context":"type","value":":","line":2,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":51,"end":55},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":55,"end":56},{"type":"T_COLON","context":"type","value":":","line":2,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"type","value":"SanFrancisco","line":2,"start":58,"end":70},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":71,"end":72},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":73,"end":76},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":77,"end":78},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":79,"end":80}],"errors":[]}
This is also okay because if something is using SubClass
as if it were a
BaseClass
they would still have access to the same interface as before
because SanFrancisco
is just a City
with a little more information.
Less specific outputs — Bad
Next, we’ll have a different SubClass
that returns a less specific type:
1
2
3
|
class SubClass extends BaseClass {
method(value: City): Noun { ... }
}
|
{"value":"class SubClass extends BaseClass {\n method(value: City): Noun { ... } // ERROR!!\n}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"SubClass","line":1,"start":6,"end":14},{"type":"T_EXTENDS","context":"normal","value":"extends","line":1,"start":15,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"BaseClass","line":1,"start":23,"end":32},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":37,"end":43},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":43,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":44,"end":49},{"type":"T_COLON","context":"type","value":":","line":2,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":51,"end":55},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":55,"end":56},{"type":"T_COLON","context":"type","value":":","line":2,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"type","value":"Noun","line":2,"start":58,"end":62},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":63,"end":64},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":65,"end":68},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":69,"end":70},{"type":"Line","context":"comment","value":"// ERROR!!","line":2,"start":71,"end":81},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":82,"end":83}],"errors":[]}
In Flow this will cause an error because if you are expecting to get a return
value of a City
, you may be using something that doesn’t exist on Noun
,
which could easily cause an error at runtime.
Next, we’ll have another SubClass that accepts a value of a less specific type.
1
2
3
|
class SubClass extends BaseClass {
method(value: Noun): City { ... }
}
|
{"value":"class SubClass extends BaseClass {\n method(value: Noun): City { ... }\n}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"SubClass","line":1,"start":6,"end":14},{"type":"T_EXTENDS","context":"normal","value":"extends","line":1,"start":15,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"BaseClass","line":1,"start":23,"end":32},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":37,"end":43},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":43,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":44,"end":49},{"type":"T_COLON","context":"type","value":":","line":2,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"type","value":"Noun","line":2,"start":51,"end":55},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":55,"end":56},{"type":"T_COLON","context":"type","value":":","line":2,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":58,"end":62},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":63,"end":64},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":65,"end":68},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":69,"end":70},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":71,"end":72}],"errors":[]}
This is perfectly fine because if we pass in a more specific type we’ll still
have all the information we need to be compatible with Noun
.
Finally, we’ll have yet another SubClass
that accepts a value of a more
specific type.
1
2
3
|
class SubClass extends BaseClass {
method(value: SanFrancisco): City { ... }
}
|
{"value":"class SubClass extends BaseClass {\n method(value: SanFrancisco): City { ... } // ERROR!!\n}\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"SubClass","line":1,"start":6,"end":14},{"type":"T_EXTENDS","context":"normal","value":"extends","line":1,"start":15,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"BaseClass","line":1,"start":23,"end":32},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":37,"end":43},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":43,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":44,"end":49},{"type":"T_COLON","context":"type","value":":","line":2,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"type","value":"SanFrancisco","line":2,"start":51,"end":63},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":63,"end":64},{"type":"T_COLON","context":"type","value":":","line":2,"start":64,"end":65},{"type":"T_IDENTIFIER","context":"type","value":"City","line":2,"start":66,"end":70},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":71,"end":72},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":73,"end":76},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":77,"end":78},{"type":"Line","context":"comment","value":"// ERROR!!","line":2,"start":79,"end":89},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":90,"end":91}],"errors":[]}
This is an error in Flow because if you are expecting a SanFrancisco
and you
get a City
you could be using something that only exists on SanFrancisco
which would cause an error at runtime.
All of this is why Flow has contravariant inputs (accepts less specific types
to be passed in), and covariant outputs (allows more specific types to be
returned).