[Python-Dev] functools.compose to chain functions together

Jason R. Coombs jaraco at jaraco.com
Sun Aug 16 18:22:55 CEST 2009


Steven D'Aprano wrote:
> Sent: Sunday, 16 August, 2009 08:15
>
> On Sat, 15 Aug 2009 04:39:03 am Jason R. Coombs wrote:
>
> >
> > def meta_decorator(data):
> >     return compose(dec_register_function_for_x, dec_alter_docstring,
> >     dec_inject_some_data(data))
>
> Surely that's better written as:
>
> meta_decorator = compose(dec_register_function_for_x,
>     dec_alter_docstring, dec_inject_some_data)

I agree. The former looks unnecessarily complicated.

I purposely chose a non-trivial use case, one which involves a decorator that requires a parameter and thus must be called first before the actual decorator is returned.  I think for this reason, the former syntax must be used so that the meta_decorator also takes the data parameter and constructs the proper "inject" decorator.  Put another way, both dec_inject_some_data and meta_decorator are more like decorator factories.

I suspect a simpler, and more common use-case would be like the one you described, where either data is global or the "inject" decorator is not used:

meta_decorator = compose(dec_register_function_for_x, dec_alter_docstring)

>
> Mine wasn't -- I've never even used Scheme, or Lisp, or any other
> functional language. But I've come to appreciate Python's functional
> tools, and would like to give a +0.5 to compose(). +1 if anyone can
> come up with additional use-cases.

Thanks for the interest.  I decided to search through some of my active code for lambdas and see if there are areas where I would prefer to be using a compose function instead of an explicit lambda/reduce combination.

I only found one such application; I attribute this limited finding to the fact that I probably elected for a procedural implementation when the functional implementation might have proven difficult to read, esp. with lambda.

1) Multiple string substitutions.  You have a list of functions that operate on a string, but you want to collect them into a single operator that can be applied to a list of strings.

sub_year = lambda s: s.replace("%Y", "2009")

fix_strings_with_substituted_year = compose(str.strip, textwrap.dedent, sub_year)
map(fix_strings_with_substituted_year, target_strings)

Moreover, it would be great to be able to accept any number of substitutions.

substitutions = [sub_year, sub_month, ...]
fix_strings_with_substitutions = compose(str.strip, textwrap.dedent, *substitutions)



I did conceive of another possibly interesting use case: vector translation.

Consider an application that performs mathematical translations on n-dimensional vectors.  While it would be optimal to use optimized matrix operations to perform these translations, for the sake of this example, all we have are basic Python programming constructs.

At run-time, the user can compose an experiment to be conducted on his series of vectors. To do this, he selects from a list of provided translations and can provide his own.  These translations can be tagged as named translations and thereafter used as translations themselves.  The code might look something like:

translations = selected_translations + custom_translations
meta_translation = compose(*translations)
save_translation(meta_translation, "My New Translation")

def run_experiment(translation, vectors):
        result = map(translation, vectors)
        # do something with result

Then, run_experiment can take a single translation or a meta-translation such as the one created above. This use-case highlights that a composed functions must take and return exactly one value, but that the value need not be a primitive scalar.



I'm certain there are other, more obscure examples, but I feel these two use cases demonstrate some fairly common potential use cases for something like a composition function.

Jason


More information about the Python-Dev mailing list