Source code for returns.primitives.hkt

from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Generic,
    NoReturn,
    Protocol,
    TypeVar,
)

_InstanceType = TypeVar('_InstanceType', covariant=True)
_TypeArgType1 = TypeVar('_TypeArgType1', covariant=True)
_TypeArgType2 = TypeVar('_TypeArgType2', covariant=True)
_TypeArgType3 = TypeVar('_TypeArgType3', covariant=True)

_FunctionDefType = TypeVar(
    '_FunctionDefType',
    bound=Callable,
    covariant=True,  # This is a must! Otherwise it would not work.
)
_FunctionType = TypeVar(
    '_FunctionType',
    bound=Callable,
)

_UpdatedType = TypeVar('_UpdatedType')

_FirstKind = TypeVar('_FirstKind')
_SecondKind = TypeVar('_SecondKind')


[docs]class KindN( Generic[_InstanceType, _TypeArgType1, _TypeArgType2, _TypeArgType3], ): """ Emulation support for Higher Kinded Types. Consider ``KindN`` to be an alias of ``Generic`` type. But with some extra goodies. ``KindN`` is the top-most type for other ``Kind`` types like ``Kind1``, ``Kind2``, ``Kind3``, etc. The only difference between them is how many type arguments they can hold. ``Kind1`` can hold just two type arguments: ``Kind1[IO, int]`` which is almost equals to ``IO[int]``. ``Kind2`` can hold just two type arguments: ``Kind2[IOResult, int, str]`` which is almost equals to ``IOResult[int, str]``. And so on. The idea behind ``KindN`` is that one cannot write this code: .. code:: python from typing import TypeVar T = TypeVar('T') V = TypeVar('V') def impossible(generic: T, value: V) -> T[V]: return generic(value) But, with ``KindN`` this becomes possible in a form of ``Kind1[T, V]``. .. note:: To make sure it works correctly, your type has to be a subtype of ``KindN``. We use a custom ``mypy`` plugin to make sure types are correct. Otherwise, it is currently impossible to properly type this. We use "emulated Higher Kinded Types" concept. Read the whitepaper: https://bit.ly/2ABACx2 ``KindN`` does not exist in runtime. It is used just for typing. There are (and must be) no instances of this type directly. .. rubric:: Implementation details We didn't use ``ABCMeta`` to disallow its creation, because we don't want to have a possible metaclass conflict with other metaclasses. Current API allows you to mix ``KindN`` anywhere. We allow ``_InstanceType`` of ``KindN`` to be ``Instance`` type or ``TypeVarType`` with ``bound=...``. See also: - https://arrow-kt.io/docs/0.10/patterns/glossary/#higher-kinds - https://github.com/gcanti/fp-ts/blob/master/docs/guides/HKT.md - https://bow-swift.io/docs/fp-concepts/higher-kinded-types - https://github.com/pelotom/hkts """ __slots__ = () if TYPE_CHECKING: # noqa: WPS604 # pragma: no cover def __getattr__(self, attrname: str): """ This function is required for ``get_attribute_hook`` in mypy plugin. It is never called in real-life, because ``KindN`` is abstract. It only exists during the type-checking phase. """
#: Type alias for kinds with one type argument. Kind1 = KindN[_InstanceType, _TypeArgType1, Any, Any] #: Type alias for kinds with two type arguments. Kind2 = KindN[_InstanceType, _TypeArgType1, _TypeArgType2, Any] #: Type alias for kinds with three type arguments. Kind3 = KindN[_InstanceType, _TypeArgType1, _TypeArgType2, _TypeArgType3]
[docs]class SupportsKindN( KindN[_InstanceType, _TypeArgType1, _TypeArgType2, _TypeArgType3], ): """ Base class for your containers. Notice, that we use ``KindN`` / ``Kind1`` to annotate values, but we use ``SupportsKindN`` / ``SupportsKind1`` to inherit from. .. rubric:: Implementation details The only thing this class does is: making sure that the resulting classes won't have ``__getattr__`` available during the typechecking phase. Needless to say, that ``__getattr__`` during runtime - never exists at all. """ __slots__ = () __getattr__: None # type: ignore
#: Type alias used for inheritance with one type argument. SupportsKind1 = SupportsKindN[ _InstanceType, _TypeArgType1, NoReturn, NoReturn, ] #: Type alias used for inheritance with two type arguments. SupportsKind2 = SupportsKindN[ _InstanceType, _TypeArgType1, _TypeArgType2, NoReturn, ] #: Type alias used for inheritance with three type arguments. SupportsKind3 = SupportsKindN[ _InstanceType, _TypeArgType1, _TypeArgType2, _TypeArgType3, ]
[docs]def dekind( kind: KindN[_InstanceType, _TypeArgType1, _TypeArgType2, _TypeArgType3], ) -> _InstanceType: """ Turns ``Kind1[IO, int]`` type into real ``IO[int]`` type. Should be used when you are left with accidental ``KindN`` instance when you really want to have the real type. Works with type arguments of any length. We use a custom ``mypy`` plugin to make sure types are correct. Otherwise, it is currently impossible to properly type this. In runtime it just returns the passed argument, nothing really happens: .. code:: python >>> from returns.io import IO >>> from returns.primitives.hkt import Kind1 >>> container: Kind1[IO, int] = IO(1) >>> assert dekind(container) is container However, please, do not use this function unless you know exactly what you are doing and why do you need it. """ return kind # type: ignore
# Utils to define kinded functions # ================================ # TODO: in the future we would be able to write a custom plugin # with `transform_kind(T) -> T'` support. # It would visit all the possible `KindN[]` types in any type and run `dekind` # on them, so this will be how it works: # in: => Callable[[KindN[IO[Any], int]], KindN[IO[Any], str]] # out: => Callable[[IO[int]], IO[str]] # This will allow to have better support for callable protocols and similar. # Blocked by: https://github.com/python/mypy/issues/9001
[docs]class Kinded(Protocol[_FunctionDefType]): # type: ignore """ Protocol that tracks kinded functions calls. We use a custom ``mypy`` plugin to make sure types are correct. Otherwise, it is currently impossible to properly type this. """ __slots__ = () #: Used to translate `KindN` into real types. __call__: _FunctionDefType def __get__( self, instance: _UpdatedType, type_, ) -> Callable[..., _UpdatedType]: """Used to decorate and properly analyze method calls."""
[docs]def kinded(function: _FunctionType) -> Kinded[_FunctionType]: """ Decorator to be used when you want to dekind the function's return type. Does nothing in runtime, just returns its argument. We use a custom ``mypy`` plugin to make sure types are correct. Otherwise, it is currently impossible to properly type this. Here's an example of how it should be used: .. code:: python >>> from typing import TypeVar >>> from returns.primitives.hkt import KindN, kinded >>> from returns.interfaces.bindable import BindableN >>> _Binds = TypeVar('_Binds', bound=BindableN) # just an example >>> _Type1 = TypeVar('_Type1') >>> _Type2 = TypeVar('_Type2') >>> _Type3 = TypeVar('_Type3') >>> @kinded ... def bindable_identity( ... container: KindN[_Binds, _Type1, _Type2, _Type3], ... ) -> KindN[_Binds, _Type1, _Type2, _Type3]: ... return container # just do nothing As you can see, here we annotate our return type as ``-> KindN[_Binds, _Type1, _Type2, _Type3]``, it would be true without ``@kinded`` decorator. But, ``@kinded`` decorator dekinds the return type and infers the real type behind it: .. code:: python >>> from returns.io import IO, IOResult >>> assert bindable_identity(IO(1)) == IO(1) >>> # => Revealed type: 'IO[int]' >>> iores: IOResult[int, str] = IOResult.from_value(1) >>> assert bindable_identity(iores) == iores >>> # => Revealed type: 'IOResult[int, str]' The difference is very clear in ``methods`` modules, like: - Raw :func:`returns.methods.bind.internal_bind` that returns ``KindN`` instance - User-facing :func:`returns.methods.bind.bind` that returns the container type You must use this decorator for your own kinded functions as well. """ return function # type: ignore