Here are the technical docs about typeclass
and how to use it.
Typeclasses for Python.
Basic usage
The first and the simplest example of a typeclass is just its definition:
>>> from classes import typeclass
>>> @typeclass
... def example(instance) -> str:
... '''Example typeclass.'''
>>> example(1)
Traceback (most recent call last):
...
NotImplementedError: Missing matched typeclass instance for type: int
In this example we work with the default implementation of a typeclass.
It raises a NotImplementedError
when no instances match.
And we don’t yet have a special case for int
,
that why we fallback to the default implementation.
It works almost like a regular function right now.
Let’s do the next step and introduce
the int
instance for our typeclass:
>>> @example.instance(int)
... def _example_int(instance: int) -> str:
... return 'int case'
>>> assert example(1) == 'int case'
Now we have a specific instance for int
which does something different from the default implementation.
What will happen if we pass something new, like str
?
>>> example('a')
Traceback (most recent call last):
...
NotImplementedError: Missing matched typeclass instance for type: str
Because again, we don’t yet have
an instance of this typeclass for str
type.
Let’s fix that.
>>> @example.instance(str)
... def _example_str(instance: str) -> str:
... return instance
>>> assert example('a') == 'a'
Now it works with str
as well. But differently.
This allows developer to base the implementation on type information.
So, the rule is clear: if we have a typeclass instance for a specific type, then it will be called, otherwise the default implementation will be called instead.
Protocols
We also support protocols. It has the same limitation as Generic
types.
It is also dispatched after all regular instances are checked.
To work with protocols, one needs
to pass protocol
named argument to instance:
>>> from typing import Sequence
>>> @example.instance(protocol=Sequence)
... def _sequence_example(instance: Sequence) -> str:
... return ','.join(str(item) for item in instance)
>>> assert example([1, 2, 3]) == '1,2,3'
But, str
will still have higher priority over Sequence
:
>>> assert example('abc') == 'abc'
We also support user-defined protocols:
>>> from typing_extensions import Protocol
>>> class CustomProtocol(Protocol):
... field: str
>>> @example.instance(protocol=CustomProtocol)
... def _custom_protocol_example(instance: CustomProtocol) -> str:
... return instance.field
Now, let’s build a class that match this protocol and test it:
>>> class WithField(object):
... field: str = 'with field'
>>> assert example(WithField()) == 'with field'
See our official docs to learn more!
General case function to create typeclasses.
Bases: Generic
[classes._typeclass._InstanceType
]
Base class for all associated types.
How to use? Just import and subclass it:
>>> from classes import AssociatedType, typeclass
>>> class Example(AssociatedType):
... ...
>>> @typeclass(Example)
... def example(instance) -> str:
... ...
It is special, since it can be used as variadic generic type
(generic with any amount of type variables),
thanks to our mypy
plugin:
>>> from typing import TypeVar
>>> A = TypeVar('A')
>>> B = TypeVar('B')
>>> C = TypeVar('C')
>>> class WithOne(AssociatedType[A]):
... ...
>>> class WithTwo(AssociatedType[A, B]):
... ...
>>> class WithThree(AssociatedType[A, B, C]):
... ...
At the moment of writing,
https://www.python.org/dev/peps/pep-0646/
is not accepted and is not supported by mypy
.
Right now it does nothing in runtime, but this can change in the future.
Bases: Generic
[classes._typeclass._AssociatedTypeDef
]
Used to specify that some value is a part of a typeclass.
For example:
>>> from classes import typeclass, Supports
>>> class ToJson(object):
... ...
>>> @typeclass(ToJson)
... def to_json(instance) -> str:
... ...
>>> @to_json.instance(int)
... def _to_json_int(instance: int) -> str:
... return str(instance)
>>> def convert_to_json(instance: Supports[ToJson]) -> str:
... return to_json(instance)
>>> assert convert_to_json(1) == '1'
>>> convert_to_json(None)
Traceback (most recent call last):
...
NotImplementedError: Missing matched typeclass instance for type: NoneType
You can also annotate values as Supports
if you need to:
>>> my_int: Supports[ToJson] = 1
But, this will fail in mypy
:
my_str: Supports[ToJson] = 'abc'
# Incompatible types in assignment
# (expression has type "str", variable has type "Supports[ToJson]")
Warning
Supports
only works with typeclasses defined with associated types.