Source code for haskpy.types.uncurried

"""Uncurried functions that are similar to normal Python functions

.. autosummary::
   :toctree:

   UncurriedFunction
   UncurriedFunctionMonoid

.. autosummary::
   :toctree:

   uncurried

"""
import attr
import functools
import inspect
from hypothesis import strategies as st

from haskpy.typeclasses._monad import Monad
from haskpy.typeclasses._monoid import Monoid
from haskpy.typeclasses._semigroup import Semigroup

from haskpy.internal import (
    immutable,
    class_property,
    class_function,
)
from haskpy.testing import eq_test
from haskpy import testing


[docs]def UncurriedFunctionMonoid(monoid): """Create a function type that has a Monoid instance""" @immutable class _UncurriedFunctionMonoid(Monoid, UncurriedFunction): """Function type with Monoid instance added""" @class_property def empty(cls): return _UncurriedFunctionMonoid(lambda *args, **kwargs: monoid.empty) @class_function def sample_monoid_type(cls): t = monoid.sample_monoid_type() return t.map(lambda b: cls.sample_value(None, b)) return _UncurriedFunctionMonoid
[docs]@immutable class UncurriedFunction(Monad, Semigroup): """Monad instance for normal Python functions The functions can take any number of positional and keyword arguments, so the signature can be anything supported by Python. The function must be called by passing all arguments at the same time, so it doesn't support automatic partial evaluation like :py:class:`.Function`. One way to think of this in type level is that the collection of all positional and keyword arguments are represented by type ``Args``. So, the type of ``UncurriedFunction`` is ``Args -> a``. This is a bit notational abuse as ``Args`` cannot be an output of a function, only an input. So, type ``a -> Args`` makes no sense. This means that one cannot compose these functions in a way that would pass ``Args`` to the next function because a function can only output a single value, not many values. .. note:: Monoid instance of UncurriedFunction requires the knowledge of the contained monoid type in order to be able to create ``empty``. The contained type is not known because Function class can be used to create functions of any type. This is just convenience and simpler user interface. If you need Monoid instance of UncurriedFunction, use UncurriedFunctionMonoid function to create such a class. Note though that the Semigroup instance is available in this Function without needing to use UncurriedFunctionMonoid. """ __f = attr.ib(validator=lambda _, __, f: callable(f))
[docs] def __attrs_post_init__(self): object.__setattr__(self, "__qualname__", self.__f.__qualname__) object.__setattr__(self, "__module__", self.__f.__module__) object.__setattr__(self, "__doc__", self.__f.__doc__) object.__setattr__(self, "__name__", self.__f.__name__) #object.__setattr__(self, "__annotations__", self.__f.__annotations__) object.__setattr__(self, "__defaults__", None) object.__setattr__(self, "__kwdefaults__", None) return
@property def __code__(self): return self.__f.__code__ @property def __signature__(self): return inspect.signature(self.__f)
[docs] @class_function def pure(cls, x): return cls(lambda *_, **__: x)
[docs] def map(f, g): """(Args -> b) -> (b -> c) -> (Args -> c)""" return UncurriedFunction(lambda *args, **kwargs: g(f(*args, **kwargs)))
[docs] def apply(f, g): """(Args -> b) -> (Args -> b -> c) -> Args -> c""" return UncurriedFunction( lambda *args, **kwargs: g(*args, **kwargs)(f(*args, **kwargs)) )
[docs] def bind(f, g): """(Args -> b) -> (b -> Args -> c) -> Args -> c""" return UncurriedFunction( lambda *args, **kwargs: g(f(*args, **kwargs))(*args, **kwargs) )
[docs] def append(f, g): """Semigroup b => (Args -> b) -> (Args -> b) -> (Args -> b)""" return f.map(lambda x: lambda y: x.append(y)).apply_to(g)
[docs] def __call__(self, *args, **kwargs): return self.__f(*args, **kwargs)
def __repr__(self): return repr(self.__f)
[docs] def __pow__(self, x): # We need to implement __pow__ for UncurriedFunction objects because # otherwise composing UncurriedFunction objects wouldn't be possible: # If f and g are UncurriedFunction objects in f ** g, Python will try # f.__pow__(g) and if it fails, it won't try g.__rpow__(f) because it # already concluded that UncurriedFunction object doesn't support pow # operation with a UncurriedFunction object. return x.__rpow__(self)
[docs] def __get__(self, obj, objtype): """Support instance methods. See: https://stackoverflow.com/a/3296318 """ if obj is not None: # Instance method, bind the first argument fp = functools.partial(self, obj) # Keep the docstring untouched fp.__doc__ = self.__doc__ return UncurriedFunction(fp) else: # Class method return self
[docs] def __eq_test__(self, g, data, input_strategy=st.integers()): # NOTE: This is used only in tests when the function input doesn't # really matter so any hashable type here is ok. The type doesn't # matter because the functions are either _TestFunction or created with # pure. x = data.draw(input_strategy) return eq_test(self(x), g(x), data)
@class_function def sample_value(cls, _, b): return testing.sample_function(b).map(cls) sample_type = testing.create_type_sampler( testing.sample_hashable_type(), testing.sample_type(), ) sample_semigroup_type = testing.create_type_sampler( testing.sample_hashable_type(), testing.sample_semigroup_type(), ) sample_monoid_type = testing.create_type_sampler( testing.sample_hashable_type(), testing.sample_monoid_type(), ) sample_functor_type_constructor = testing.create_type_constructor_sampler( testing.sample_hashable_type(), ) sample_apply_type_constructor = sample_functor_type_constructor sample_applicative_type_constructor = sample_functor_type_constructor sample_monad_type_constructor = sample_functor_type_constructor
[docs]def uncurried(f): """Decorator for transforming functions into monads This can be useful for converting value constructors into function monads, so that one can use ``map`` and other methods of a function monad. For instance, ``List`` is a value constructor but doesn't have monadic methods. By wrapping it with ``uncurried``, you can treat it like a normal uncurried function: .. code-block:: python >>> from haskpy import List, Maybe, Just >>> MaybeList = uncurried(List).map(lambda xs: xs.sequence(Maybe)) >>> MaybeList(Just(10), Just(20), Just(30)) Just(List(10, 20, 30)) The above example converted ``List`` constructor into a special constructor that converts ``List (Maybe a)`` into ``Maybe (List a)`` automatically. This can be useful so that you can easily create lists inside your own custom monads. """ # Don't wrap twice return f if isinstance(f, UncurriedFunction) else UncurriedFunction(f)