Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Mapping covariant #510

Closed
JukkaL opened this issue Aug 30, 2016 · 1 comment
Closed

Make Mapping covariant #510

JukkaL opened this issue Aug 30, 2016 · 1 comment

Comments

@JukkaL
Copy link
Contributor

JukkaL commented Aug 30, 2016

I think that Mapping[KT, VT] should be covariant in both KT and VT. The argument for this is that it doesn't have any mutation operations. Mypy might complain (or may eventually start to complain) about having covariant type variables in argument types but in this case it's okay so we can probably just # type: ignore them.

(It wouldn't be safe since somebody can write a get method that actually mutates the mapping, but consenting adults...)

@gvanrossum
Copy link
Member

Indeed mypy complains if I make this change, "Cannot use a covariant type variable as a parameter". But adding a # type: ignore works, so I'll do that.

gvanrossum pushed a commit that referenced this issue Aug 30, 2016
Fixes #510.

This requires a `# type: ignore` on `__getitem__` and `get` because
mypy complains about covariant parameters.

FWIW typing.py needs to be changed too.  (It was covariant in the
value but invariant in the key -- typeshed was invariant in both.)
gvanrossum pushed a commit that referenced this issue Aug 30, 2016
Fixes #510.

This requires a `# type: ignore` on `__getitem__` and `get` because
mypy complains about covariant parameters.

FWIW typing.py needs to be changed too.  (It was covariant in the
value but invariant in the key -- typeshed was invariant in both.)
gvanrossum pushed a commit that referenced this issue Sep 2, 2016
Fixes #510.

This requires a `# type: ignore` on `__getitem__` and `get` because
mypy complains about covariant parameters.

FWIW typing.py needs to be changed too.  (It was covariant in the
value but invariant in the key -- typeshed was invariant in both.)
JukkaL pushed a commit that referenced this issue Sep 5, 2016
* Make Mapping covariant.

Fixes #510.

This requires a `# type: ignore` on `__getitem__` and `get` because
mypy complains about covariant parameters.

FWIW typing.py needs to be changed too.  (It was covariant in the
value but invariant in the key -- typeshed was invariant in both.)

* Delete outdated comment.

* Backpeddle a bit -- Mapping key type should not be covariant.
facebook-github-bot pushed a commit to facebook/pyre-check that referenced this issue Mar 8, 2019
Summary:
Typing out a full summary here for the rationale behind this error, mostly because I want a fully-fleshed one to copy into documentation when this lands.

-----

In brief, read-only data types can be covariant, write-only data types can be contravariant, and data types that support both reads and writes must be invariant.

If a data type implements any functions accepting parameters of that type, we cannot guarantee that writes are not happening. If a data type implements any functions returning values of that type, we cannot guarantee that reads are not happening.

For example (note: `int` is a subclass of `float` in the type system and in these examples):

Writes taking covariants:

```
_T_co = typing.TypeVar("_T_co", covariant=True)

class MyList(typing.Generic[_T_co]):
    def write(self, element: _T_co) -> None:
        ... # adds element to list

def takes_float_list(float_list: MyList[float]) -> None:
    float_list.write(1.0)

int_list: MyList[int] = ...
takes_float_list(int_list)  # this call is OK because MyList is covariant: MyList[int] < MyList[float]

```
Reads returning contravariants:
```
_T_cont = typing.TypeVar("_T_cont", contravariant=True)

class MyList(typing.Generic[_T_cont]):
    def read(self) -> _T_cont:
        ... # returns first element from list

def takes_int_list(int_list: MyList[int]) -> int:
   return int_list.read()

float_list: MyList[float] = ...
takes_int_list(float_list)  # this call is OK because MyList is contravariant: MyList[float] < MyList[int]
```

Note: covariance doesn't mean anything in a free function like this. MyPy still errors that covariant type var cannot be in the parameter (same as above), but I suppose that's fine because this really shouldn't be covariant to begin with.
```
_T_co = typing.TypeVar("_T_co", covariant=True)
def a(x: _T_co) -> _T_co:
    return x
```

-----

Other interesting notes:

See discussion on adding a covariant param to Mapping's getitem:
python/typeshed@87e0f0a
python/typeshed#510

If you're writing an immutable data structure, the parameter inputs should be the entire container rather than the bare covariant type variable.

For example, this is fine:
```
class tuple(Sequence[_T_co], Generic[_T_co]):
    def __add__(self, x: Tuple[_T_co, ...]) -> Tuple[_T_co, ...]: ...
```

Reviewed By: dkgi

Differential Revision: D14360850

fbshipit-source-id: 3c4365164e0a1bc65068c7e4a8ff72d10a18964b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants