tobgu / pyrsistent

Persistent/Immutable/Functional data structures for Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unify dynamic and static types

fritzo opened this issue · comments

It would be great if pyrsistent provided a single type interface for use both in dynamic dispatch (via isinstance or @singledispatch) and static type checking (via mypy or similar). I'm enjoying the pyrsistent so far, but I'm finding that I need to write code with two different types, which feels cumbersome, and which is unnecessary with the competing collection immutables.Map.

Example

Here's some pattern matching code to extract leaves from an immutable JSON-like data structure in Python 3.11

from collections.abc import Iterator
from typing import TypeAlias
import pyrsistent
import pyrsistent.typing

# This static type hint requires types from pyrsistent.typing:
JSON: TypeAlias = (
    None | bool | int | float | str
    | pyrsistent.typing.PVector["JSON"]
    | pyrsistent.typing.PMap[str, "JSON"]
)

# These isinstance(-,-) guards require types from pyrsistent:
def iter_leaves(tree: JSON) -> Iterator[None | bool | int | float | str]:
    if isinstance(tree, pyrsistent.PVector):
        for item in tree:
            yield from iter_leaves(item)
    elif isinstance(tree, pyrsistent.PMap):
        for item in tree.values():
            yield from iter_leaves(item)
    else:
        yield tree

Solution

I'm unsure how to fix the C implementations, but I believe for the Python implementations we could simply ensure PVector and PMap inherit from generic base classes, e.g.:

from collections.abc import Mapping, Sequence
from typing import Generic, TypeVar

K = TypeVar("K")
V_co = TypeVar("V_co", covariant=True)

class PVector(Sequence[V_co], Generic[V]):
    # actual implementation here

class PVector(Mapping[K, V_co], Generic[K, V_co]):
    # actual implementation here

Backwards compatibility

I think we'd need to import all the new unified types in pyrsistent.typing to ensure backwards compatibility for users who use that module.

this is such the norm now that if I had not found this issue I would not have known what to do, all the built in types work this way now without any typing imports, dict[k, v], list[v], etc.

@fritzo looking at the code I think your suggestion would work for both the pure python implementation and the C one out of the box because they are both subclasses of the same base class. E.g. PVector is actually a base class, with the derived classes PythonPVector and pvectorc.pvector. A runtime check inside the python_pvector constructor function decides which one gets instantiated each time, but its return type could just be PVector[T] and the fact either subclass could actually be being used underneath is abstracted away from the user.