evhub / coconut

Simple, elegant, Pythonic functional programming.
http://coconut-lang.org
Apache License 2.0
4.04k stars 120 forks source link

Add built-in to combine unary functions into a function that applies each function to the corresponding argument #807

Closed evhub closed 7 months ago

evhub commented 9 months ago

Should be equivalent to a pickleable version of

def apply(*funcs, map_using=map) = (*args) => map_using(call, funcs, args, strict=True)
evhub commented 9 months ago

We should also add

apply.using_processes = apply$(map_using=process_map)
apply.using_threads = apply$(map_using=thread_map)
apply.using_async = apply$(map_using=async_map)
evhub commented 9 months ago

Initial implementation for header:

class apply(_coconut_base_callable):
    """Given unary functions, create a new function that applies each function to its corresponding argument.

    Thus, apply((.+1), (.*2)) is equivalent to (x, y) -> (x + 1, y * 2).

    Effectively equivalent to:
        def apply(*funcs, map_using=map) =
            (*args) => map_using(call, funcs, args, strict=True)
    """
    __slots__ = ("funcs", "map_using")
    def __init__(self, *funcs, **kwargs):
        self.funcs = funcs
        self.map_using = kwargs.pop("map_using", None)
        if kwargs:
            raise _coconut.TypeError("apply() got unexpected keyword arguments " + _coconut.repr(kwargs))
    def __reduce__(self):
        return (self.__class__, self.funcs, {lbrace}"map_using": self.map_using{rbrace})
    def __repr__(self):
        return "apply(" + ", ".join(_coconut.repr(f) for f in self.funcs) + (", map_using=" + _coconut.repr(self.map_using) if self.map_using is not None else "") + ")"
    def __call__(self, *args):
        return ({_coconut_}map if self.map_using is None else self.map_using)({_coconut_}call, self.funcs, self.args, strict=True)
evhub commented 9 months ago

Alternatively, we could make something closer to lift like

def comp(func) = (*func_args, **func_kwargs) => (*args, **kwargs) => (
    func(
        *map(call, func_args, args, strict=True),
        **{k: func_kwargs[k](kwargs[k]) for k in func_kwargs.keys() | kwargs.keys()},
    )
)
evhub commented 9 months ago

To encourage piping with branching, we should also add usage of

branch = lift(,)
branched = comp(,)

to the documentation.

evhub commented 9 months ago

For symmetry with lift, we could name it:

Note that the usage is as the D2 combinator.

evhub commented 8 months ago

Added as lift_apart.

evhub commented 8 months ago

~Maybe lift_args is a better name than lift_apart, actually.~