[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