Source code for discopy.python.function
# -*- coding: utf-8 -*-
"""
The category of Python functions with sequential composition.
Summary
-------
.. autosummary::
:template: class.rst
:nosignatures:
:toctree:
Function
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from contextlib import contextmanager
from discopy.cat import Composable
from discopy.utils import (
Whiskerable, assert_iscomposable, assert_isinstance,
tuplify, untuplify, classproperty)
[docs]
@dataclass
class Function(Composable[type], Whiskerable):
"""
Python function with sequential composition.
Parameters:
inside : The callable Python object inside the function.
dom : The domain of the function, i.e. its input type.
cod : The codomain of the function, i.e. its output type.
.. admonition:: Summary
.. autosummary::
id
then
"""
inside: Callable
dom: type
cod: type
type_checking = True
def __init__(self, inside: Callable, dom: type, cod: type):
dom, cod = map(tuplify, (dom, cod))
self.inside, self.dom, self.cod = inside, dom, cod
[docs]
@classmethod
def id(cls, dom: type) -> Function:
"""
The identity function on a given tuple of types :code:`dom`.
Parameters:
dom (type) : The typle of types on which to take the identity.
"""
return cls(lambda *xs: untuplify(xs), tuplify(dom), tuplify(dom))
[docs]
def then(self, other: Function) -> Function:
"""
The sequential composition of two functions, called with :code:`>>`.
Parameters:
other : The other function to compose in sequence.
"""
assert_isinstance(other, type(self))
assert_iscomposable(self, other)
return type(self)(
lambda *args: other(*tuplify(self(*args))), self.dom, other.cod)
@classproperty
@contextmanager
def no_type_checking(cls):
tmp, cls.type_checking = cls.type_checking, False
try:
yield
finally:
cls.type_checking = tmp
def __call__(self, arg):
if self.type_checking:
assert_isinstance(arg, self.dom)
result = self.inside(arg)
if self.type_checking:
assert_isinstance(result, self.cod)
return result