import functools
import itertools
from operator import (attrgetter,
methodcaller)
from typing import (Callable,
Iterable,
List,
Tuple)
from paradigm import signatures
from reprit import seekers
from reprit.base import generate_repr
from . import left
from .functional import (ApplierBase,
compose,
flip)
from .hints import (Domain,
Map,
Range)
from .iterating import expand
from .reversal import reverse
[docs]def accumulator(function: Callable[[Domain, Range], Range],
initial: Range
) -> Map[Iterable[Domain], Iterable[Iterable[Range]]]:
"""
Returns function that yields cumulative results of given binary function
starting from given initial object in direction from right to left.
>>> def to_next_fraction(partial_denominator: int,
... reciprocal: float) -> float:
... return partial_denominator + 1 / reciprocal
>>> to_simple_continued_fractions = accumulator(to_next_fraction, 1)
>>> from itertools import repeat
>>> [round(fraction, 4)
... for fraction in to_simple_continued_fractions(list(repeat(1, 10)))]
[1, 2.0, 1.5, 1.6667, 1.6, 1.625, 1.6154, 1.619, 1.6176, 1.6182, 1.618]
"""
left_accumulator = left.accumulator(flip(function), initial)
return compose(left_accumulator, reverse)
[docs]def attacher(object_: Domain) -> Map[Iterable[Domain], Iterable[Domain]]:
"""
Returns function that appends given object to iterable.
>>> attach_hundred = attacher(100)
>>> list(attach_hundred(range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]
"""
return functools.partial(attach,
object_=object_)
[docs]@functools.singledispatch
def attach(iterable: Iterable[Domain],
object_: Domain) -> Iterable[Domain]:
"""
Appends given object to the iterable.
"""
yield from itertools.chain(iterable, expand(object_))
@attach.register(list)
def _(iterable: List[Domain],
object_: Domain) -> List[Domain]:
"""
Appends given object to the list.
"""
return iterable + [object_]
@attach.register(tuple)
def _(iterable: Tuple[Domain, ...],
object_: Domain) -> Tuple[Domain, ...]:
"""
Appends given object to the tuple.
"""
return iterable + (object_,)
[docs]def folder(function: Callable[[Domain, Range], Range],
initial: Range) -> Map[Iterable[Domain], Range]:
"""
Returns function that cumulatively applies given binary function
starting from given initial object in direction from right to left.
>>> to_sum_evaluation_order = folder('({} + {})'.format, 0)
>>> to_sum_evaluation_order(range(1, 10))
'(1 + (2 + (3 + (4 + (5 + (6 + (7 + (8 + (9 + 0)))))))))'
"""
left_folder = left.folder(flip(function), initial)
return compose(left_folder, reverse)
class Applier(ApplierBase):
def __init__(self, function: Callable[..., Range],
*args: Domain,
**kwargs: Domain) -> None:
super().__init__(function, *args, **kwargs)
def __call__(self, *args: Domain, **kwargs: Domain) -> Range:
return self.func(*args, *self.args, **self.keywords, **kwargs)
__repr__ = generate_repr(__init__,
field_seeker=seekers.complex_)
@signatures.factory.register(Applier)
def _(object_: Applier) -> signatures.Base:
return _bind_positionals(signatures.factory(object_.func)
.bind(**object_.keywords),
object_.args)
@functools.singledispatch
def _bind_positionals(signature: signatures.Base,
args: Tuple[Domain, ...]) -> signatures.Base:
raise TypeError('Unsupported signature type: {type}.'
.format(type=type(signature)))
@_bind_positionals.register(signatures.Plain)
def _(signature: signatures.Plain,
args: Tuple[Domain, ...]) -> signatures.Base:
if not args:
return signature
variadic_positionals = signature.parameters_by_kind[
signatures.Parameter.Kind.VARIADIC_POSITIONAL]
positionals = (signature.parameters_by_kind[
signatures.Parameter.Kind.POSITIONAL_ONLY]
+ signature.parameters_by_kind[
signatures.Parameter.Kind.POSITIONAL_OR_KEYWORD])
if len(args) > len(positionals) and not variadic_positionals:
value = 'argument' + 's' * (len(positionals) != 1)
raise TypeError('Takes {parameters_count} positional {value}, '
'but {arguments_count} {verb} given.'
.format(parameters_count=len(positionals),
value=value,
arguments_count=len(args),
verb='was' if len(args) == 1 else 'were'))
non_positionals = (signature.parameters_by_kind[
signatures.Parameter.Kind.KEYWORD_ONLY]
+ signature.parameters_by_kind[
signatures.Parameter.Kind.VARIADIC_KEYWORD])
signatures_parameters = []
if len(args) <= len(positionals):
signatures_parameters.append(positionals[:-len(args)]
+ non_positionals)
if variadic_positionals:
signatures_parameters.append(signature.parameters)
for limit in range(1, len(args)):
signatures_parameters.append(positionals[:-limit]
+ variadic_positionals
+ non_positionals)
positionals_or_keywords = signature.parameters_by_kind[
signatures.Parameter.Kind.POSITIONAL_OR_KEYWORD]
positionals_or_keywords_with_defaults_count = sum(
map(attrgetter('has_default'), positionals_or_keywords))
for offset in range(1, positionals_or_keywords_with_defaults_count + 1):
signatures_parameters.append(
positionals[:-(len(args) + offset)]
+ [signatures.Parameter(
name=parameter.name,
kind=signatures.Parameter.Kind.KEYWORD_ONLY,
has_default=parameter.has_default)
for parameter in positionals_or_keywords[-offset:]]
+ non_positionals)
return signatures.Overloaded(*(signatures.Plain(*parameters)
for parameters in signatures_parameters))
@_bind_positionals.register(signatures.Overloaded)
def _(signature: signatures.Overloaded,
args: Tuple[Domain, ...]) -> signatures.Base:
sub_signatures = list(filter(methodcaller(signatures.Base.expects.__name__,
*args),
signature.signatures))
if not sub_signatures:
raise TypeError('No corresponding signature found.')
return signatures.Overloaded(*map(functools.partial(_bind_positionals,
args=args),
sub_signatures))
[docs]def applier(function: Callable[..., Range],
*args: Domain,
**kwargs: Domain) -> Callable[..., Range]:
"""
Returns function that behaves like given function
with given arguments partially applied.
Given positional arguments will be added to the right end.
>>> square = applier(pow, 2)
>>> square(10)
100
"""
return Applier(function, *args, **kwargs)