Экзистенциальные типы в Python - PullRequest
4 голосов
/ 07 января 2020

У меня есть универсальный c класс (скажем, Automata[S]), и я бы хотел, чтобы другой не универсальный c класс (скажем, Test) имел Automata[S] в качестве поля для некоторого S ( т.е. я хотел бы иметь ∃S. Automata[S] в качестве поля).

Моя первая попытка - использовать неподписанный тип Automata для аннотирования поля следующим образом. Но применение delta и is_finale не является безопасным от типа, поскольку параметр типа S становится Any.

import abc
from typing import Generic, TypeVar


S = TypeVar('S')


class Automata(Generic[S], metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def initial_state(self) -> S:
        raise NotImplementedError()

    @abc.abstractmethod
    def delta(self, state: S, x: int) -> S:
        raise NotImplementedError()

    @abc.abstractmethod
    def is_final(self, state: S) -> bool:
        raise NotImplementedError()


class Counter(Automata[int]):
    def initial_state(self) -> int:
        return 0

    def delta(self, state: int, x: int) -> int:
        return state + x

    def is_final(self, state: int) -> bool:
        return state >= 10


class Test:
    automata: Automata

    def __init__(self, automata: Automata[S]):
        self.automata = automata

    def test(self) -> bool:
        s0 = self.automata.initial_state()
        # reveal_type(self.automata) #=> Automata[Any]
        # reveal_type(s0) #=> Any
        s1 = self.automata.delta(s0, 1)
        s2 = self.automata.delta(s1, 2)
        s3 = self.automata.delta(s2, 3)
        return self.automata.is_final(s3)


test = Test(Counter())
test.test()

Моя другая попытка состоит в использовании изоморфизма ∃X. F(X) ≅ ∀R. (∀X. F(X) → R) → R следующим образом. Выглядит как безопасный для типов, так как раскрываемый тип - это не Any, но подкласс Cont для продолжения записи каждый раз утомителен и немного сложен.

import abc
from typing import Generic, TypeVar


S = TypeVar('S')
R = TypeVar('R')


class Automata(Generic[S], metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def initial_state(self) -> S:
        raise NotImplementedError()

    @abc.abstractmethod
    def delta(self, state: S, x: int) -> S:
        raise NotImplementedError()

    @abc.abstractmethod
    def is_final(self, state: S) -> bool:
        raise NotImplementedError()


class Counter(Automata[int]):
    def initial_state(self) -> int:
        return 0

    def delta(self, state: int, x: int) -> int:
        return state + x

    def is_final(self, state: int) -> bool:
        return state >= 10


class Cont(Generic[R], metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __call__(self, automata: Automata[S]) -> R:
        raise NotImplementedError()


class EAutomata(metaclass=abc.ABCMeta):
    """
    ∃S. Automata[S]
    """

    @abc.abstractmethod
    def unpack(self, cont: Cont[R]) -> R:
        raise NotImplementedError()

    @staticmethod
    def pack(automata: Automata[S]) -> 'EAutomata':
        return EAutomataBody[S](automata)


class EAutomataBody(EAutomata, Generic[S]):
    def __init__(self, automata: Automata[S]):
        self._automata = automata

    def unpack(self, cont: Cont[R]) -> R:
        return cont(self._automata)


class Test:
    automata: EAutomata

    def __init__(self, automata: Automata[S]):
        self.automata = EAutomata.pack(automata)

    def test(self) -> bool:
        class C(Cont[bool]):
            def __call__(self, automata: Automata[S]) -> bool:
                s0 = automata.initial_state()
                # reveal_type(automata) #=> Automata[S`-1]
                # reveal_type(s0) #=> S`-1
                s1 = automata.delta(s0, 1)
                s2 = automata.delta(s1, 2)
                s3 = automata.delta(s2, 3)
                return automata.is_final(s3)
        return self.automata.unpack(C())


test = Test(Counter())
test.test()

Есть ли способ упростить вышеуказанный способ? , или есть лучший способ использовать экзистенциальные типы в Python?

...