Python's simplicity philosophy

Alex Martelli aleax at aleax.it
Mon Nov 17 08:47:21 EST 2003


Douglas Alan wrote:

> Alex Martelli <aleax at aleax.it> writes:
> 
>> But to be consistent with your other arguments, no doubt you'd argue
>> for a .sort() followed by [-1] as "more general" than max...
> 
> That would have a larger big O time growth value -- most likely
> O(n * log n) vs. O(n), for reasonable implemenations.  And while I

If the sequence is carefully randomized, yes.  If the sequence has
any semblance of pre-existing order, the timsort is amazingly good
at exploiting it, so that in many real-world cases it DOES run as
O(N).

> wouldn't sweat a factor of 2 for a feature of a RAD or scripting
> language, I would be more concerned about moving to a larger big O
> value.

Me too!  That's why I'd like to make SURE that some benighted soul
cannot code:

    onebigstring = reduce(str.__add__, lotsofstrings)

and get O(N squared) performance, versus the O(N) performance of
the correct Python idiom, ''.join(lotsofstrings) .  At least sum
does give an error message, AND a reminder that ''.join should be
used, when you try sum(lotsofstrings, '') -- reduce just slows
your program down by a LOT silently and underhandedly...


>> for all items in the sequence of numbers".  But, reduce as it exists
>> in Python today cannot be clearly defined EXCEPT by such an
>> iteration with some generic "callable which accepts two arguments"
>> specified in lieu of the operator.add.  Given this total generality,
>> even though hardly ever does it make sense to USE it, reduce becomes
>> quite complex.
> 
> Your proposed extension to max() and min() has all the same problems.

Not at all.  Maybe you have totally misunderstood "my proposed
extension"?  There is NO "callable which accepts two arguments"
involved -- and therefore no complications at all.  Indeed, what
I propose is simply to ensure that under all circumstances

x = max(iterable, key=k)

binds x to exactly the same value which would be be bound by

x = list.sorted(iterable, key=k)[-1]

in Python 2.4 (max can of course guarantee O(N) behavior, while
list.sorted cannot -- this optimization, as well as the slight
simplification, would be my motivations for having key= in max).

> But reasonable programmers don't abuse this generality, and so there

So, you're claiming that ALL people who were defending 'reduce' by
posting use cases which DID "abuse this generality" are unreasonable?

> this urge to be stiffled.  Don't take my word for it -- ask Paul
> Graham.  I believe he was even invited to give the Keynote Address at
> a recent PyCon.

However, if you agree with Paul Graham's theories on language design,
you should be consistent, and use Lisp.  If you consider Python to be
preferable, then there must be some point on which you disagree with
him.  In my case, I would put "simplicity vs generality" issues as
the crux of my own disagreements with Dr. Graham.

So, Python is not designed as PG would design it (else it would be
Lisp).  It's designed with far greater care for simplicity, and for
practicality, and with a jaundiced eye against excess generality.
Indeed, 'sum' itself lost substantial generality between my original
conception of it and the way Guido eventually accepted it into the
built-ins -- and Guido's on record as regretting that he did not
remove even _more_ generality from it.


>> You've even argued in this thread, incorrectly, that reduce could be
>> eliminated if only all binary operators were able to accept arbitrary
>> numbers of arguments.  This, plus your insistence that 'reduce' is
>> "just like" APL's / (which does NOT accept arbitrary functions on its
>> left -- just specific operators such as +), indicate a _misconception_
>> of reduce on your part.  I'm sure you don't hold it consciously, but
>> these miscommunications indicate that even you, reduce's paladin, do
>> NOT properly grasp reduce "intuitively".
> 
> Just what is it that I don't grasp again?  I think my position is
> clear: I have no intention to abuse reduce(), so I don't worry myself
> with ways in which I might be tempted to.

Yet you want reduce to keep accepting ANY callable that takes two
arguments as its first argument, differently from APL's / (which does
NOT accept arbitrary functions on its left); and you claimed that reduce
could be removed if add, mul, etc, would accept arbitrary numbers of
arguments.  This set of stances is not self-consistent.


>> Having (e.g.) add accept a sequence argument (like max does), or, for
>> explicitness and potentially performance, grow an add.reduce attribute
>> just like in Numeric, would give no particular problems.  I'd still
>> want (just like Numeric does) to have sum as a name for add.reduce,
>> because it's by far the most common case
> 
> So, now you *do* want multiple obviously right ways to do the same
> thing?

sum(sequence) is the obviously right way to sum the numbers that are
the items of sequence.  If that maps to add.reduce(sequence), no problem;
nobody in their right mind would claim the latter as "the one obvious
way", exactly because it IS quite un-obvious.


>> and avoids getting into the issue of what the (expletive delete)
>> does "reducing" have to do with anything that "add.reduce" does
>> (it's really a curve ball compared with the many meanings of
>> "reduce" in English, after all).
> 
> The English world "reduce" certainly has multiple meanings, but so
> does "sum".  I can be a noun or a verb, for instance.  It can mean

Any noun can be verbed, but that's a problem, or feature, of English
as a natural language, and unrelated to programming languages.

The point is that the primary meaning of "reduce" is "diminish",
and when you're summing (positive:-) numbers you are not diminishing
anything whatsoever ... unless you think in terms of multidimensional
arrays and diminishing dimensionality, but while that's quite OK in
APL or Numeric, which DO have multidimensional arrays, it's silly in
Python proper, which doesn't.  The primary meaning of "sum" is "sum",
so I have never met anybody having the slightest problem understanding
or using it (including both people with English as their mother
tongue, and others, all the way to people with near-zero command of
English: it helps, here, that "summare" is a _Latin_ word -- so is
"reducere", but with the same primary meaning as in English:-).

> "summary" or "gist" in addition to addition.  It also can be confusing
> by appearing to be just a synonym for "add".  Now people might have
> trouble remember what the difference between sum() and add() is.

Got any relevant experience teaching Python?  I have plenty and I have
never met ANY case of the "trouble" you mention.  


> In Computer Science, however, "reduce" typically only has one meaning
> when provided as a function in a language, and programmers might as
> well learn that sooner than later.

I think you're wrong.  "reduce dimensionality of a multi-dimensional
array by 1 by operating along one axis" is one such meaning, but there
are many others.  For example, the second Google hit for "reduce
function" gives me:

http://www.gits.nl/dg/node65.html

where 'reduce' applies to rewriting for multi-dot grammars, and
the 5th hit is

http://www.dcs.ed.ac.uk/home/stg/NOTES/node31.html

which uses a much more complicated generalization:

reduce(g, e, m, n, f)=g(e,g(f(m),g(f(m+1),...g(f(n-1),g(f(n)))...)))

not to mention Python's own __reduce__, etc.  And the 'reduce'
given at
http://w3future.com/html/stories/hop.xml
is what we might code as sum(map(templateFunction, sequence))
while
http://csdl.computer.org/comp/trans/tp/1993/04/i0364abs.htm
deals with "the derivation of general methods for the L/sub 2/
 approximation of signals by polynomial splines" and defines
REDUCE as "prefilter and down-sampler" (which is exactly as I
might expect it to be defined in any language dealing mostly
with signal processing, of course).

So, it's quite sensible for people to be confused about the
meaning of 'reduce' within a programming language.

 
>> But, most importantly, such a design would avoid the veritable traps
>> to which the current, too-general 'reduce' subjects the poor learner
>> who's quite reasonably going to believe that all that generality
>> MUST be good for something if it's been designed into a built-in.
>> We've seen quite a few such inappropriate uses on this thread, after
>> all.
> 
> That's very easy to fix:
> 
>    FAQ
>    ---
>    Q. Should I ever pass a function with side effects into reduce() or
>       map()?
> 
>    A. No.
> 
>    (Unless the side-effect is just printing out debugging information,
>     or saving away statistics, or something like that.)

Designing an over-general approach, and "fixing it in the docs" by
telling people not to use 90% of the generality they so obviously
get, is not a fully satisfactory solution.  Add in the caveats about
not using reduce(str.__add__, manystrings), etc, and any reasonable
observer would agree that reduce had better be redesigned.

Again, I commend APL's approach, also seen with more generality in Numeric
(in APL you're stuck with the existing operator on the left of + -- in
Numeric you can, in theory, write your own ufuncs), as saner.  While not
quite as advisable, allowing callables such as operator.add to take
multiple arguments would afford a similarly _correctly-limited generality_
effect.  reduce + a zillion warnings about not using most of its potential
is just an unsatisfactory combination.


Alex





More information about the Python-list mailing list