[Python-ideas] How do you think about these language extensions?

Steven D'Aprano steve at pearwood.info
Sat Aug 19 06:42:03 EDT 2017


On Fri, Aug 18, 2017 at 10:33:40PM -0700, David Mertz wrote:

> This is pretty easy to write without any syntax changes, just using a
> higher-order function `compose()` (possible implementation at foot).
> Again, I'll assume auto-currying like the map/filter versions of those
> functions in toolz, as Steven does:
[...]

> result = compose(map(str.strip),
>                  filter(lambda s: not startswith('#'),
>                  sorted,
>                  collapse,
>                  extract_dates,
>                  map(date_to_seconds),
>                  min
>                  )(myfile.readlines())

A ~~slight~~ major nit: given the implementation of compose you quote 
below, this applies the functions in the wrong order. min() is called 
first, and map(str.strip) last.

But apart from being completely wrong *wink* that's not too bad :-)

Now we start bike-shedding the aethetics of what looks better 
and reads more nicely. Your version is pretty good, except:

1) The order of function composition is backwards to that normally 
expected (more on this below);

2) there's that unfortunate call to "compose" which isn't actually part 
of the algorithm, its just scaffolding to make it work;

3) the data being operated on still at the far end of the chain, instead 
of the start;

4) and I believe that teaching a chain of function calls is easier than 
teaching higher order function composition. Much easier.


The standard mathematical definition of function composition operates 
left to right:

(f∘g∘h)(x) = f(g(h(x))

http://mathworld.wolfram.com/Composition.html

And that's precisely what your implementation does. Given your 
implementation quoted below:

py> def add_one(x): return x + 1
...
py> def double(x): return 2*x
...
py> def take_one(x): return x - 1
...
py>
py> compose(add_one,
...         double,
...         take_one)(10)
19
py>
py> add_one(double(take_one(10)))
19

which is the mathematically expected behaviour. But for chaining, we 
want the operations in the opposite order:

10 -> add_one -> double -> take_one

which is equivalent to:

take_one(double(add_one(10))


So to use composition for chaining, we need:


- a non-standard implementation of chaining, which operates in the 
  reverse to what mathematicians and functional programmers expect;

- AND remember to use this rcompose() instead of compose()

- stick to the standard compose(), but put the functions in the
  reverse order to what we want;

- or use the standard compose, but use even more scaffolding to
  make it work:

result = compose(*reversed(
                   ( map(str.strip),
                     filter(lambda s: not startswith('#')),
                     sorted,
                     collapse,
                     extract_dates,
                     map(date_to_seconds),
                     min
                   )))(myfile.readlines())


> def compose(*funcs):
>     """Return a new function s.t.
>        compose(f,g,...)(x) == f(g(...(x)))
>     """
>     def inner(data, funcs=funcs):
>         result = data
>         for f in reversed(funcs):
>             result = f(result)
>         return result
>     return inner



-- 
Steve


More information about the Python-ideas mailing list