pytoolz / toolz

A functional standard library for Python.
http://toolz.readthedocs.org/
Other
4.57k stars 258 forks source link

Idea: Compose class should be iterable #553

Open ZeroBomb opened 1 year ago

ZeroBomb commented 1 year ago

It would be really nice to be able to iterate all the funcs in compose, without having to combine the first and funcs properties. It would let you immediately use Compose objects as iterables in the itertoolz functions.

For an example, consider the simple logging strategy outlined in my gist here: https://gist.github.com/ZeroBomb/8ac470b1d4b02c11f2873c5d4e0512a1

As written, I need to define this somewhat extraneous function

def get_funcs(composition):
    return (composition.first,)+composition.funcs

in order to map over those functions and re-compose:

@curry
def interleave_map(func, items):
    # [1,2,3] -> [func(1), 1, func(2), 2, func(3), 3]
    return interleave([map(func, items), items])

# define a debug function that interleaves logging funcs inbetween each func in an existing composition
debug = compose_left(get_funcs, interleave_map(passthru_log), star(compose_left))

if the Compose class were iterable, I could completely eliminate the get_funcs function, and comfortably feed the compose object directly into interleave:

def debug(composition):
    return compose_left(*interleave_map(passthru_log, composition))
mentalisttraceur commented 1 year ago

My compose exposes all composed callables as the .functions attribute, in case that helps.

ZeroBomb commented 1 year ago

Some other observations on iterable compositions:

Idempotence

There is a sense in which compose is idempotent:

compose(*compose(f1, f2, f3)) == compose(f1, f2, f3)

Is that good? I don't know, but it seems profound.

Trivial mapping over compositions

It is now basically trivial to update the functions inside compositions you've already made

def log(func):
    def inner(*args, **kwargs):
        print(f"callin' {func.__name__}")
        return func(*args, **kwargs)
    return inner

compose(*map(log, preexisting_composition))

Suggests future direction for combining compositions

It seems pretty straightforward to propose some kind of helper function to make this a little more straightforward

compose(*composition2, *composition1)

but it's not that bad as-is.