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