Code block literals

Alex Martelli aleax at aleax.it
Thu Oct 9 10:46:42 EDT 2003


Dave Benjamin wrote (answering Mike Rovner):
   ...
>> "Explicit is better than implicit"
> 
> In that case, why do we eschew code blocks, yet have no problem with the
> implicit invocation of an iterator, as in:
> 
> for line in file('input.txt'):
>      do_something_with(line)

I don't see that there's anything "implicit" in the concept that a
special operation works as indicated by its syntax.  I.e., I do not
find this construct any more "implicit" in the first line than in
its second one, which is the juxtaposition of a name and a pair of
parentheses to indicate calling-with-arguments -- and alternatives
such as:

    do_something_with.call_with_arguments(line)

aren't "more explicit", just more verbose.

Similarly, the fact that
    file('input.txt')
(a call to a type object) creates and returns an object is not any
more "implicit" than it would be to have to call factory classmethods
a la:
    file.open_for_reading_an_existing_textfile_named('input.txt')
would not be "more explicit", just more verbose.

Simply juxtaposing parentheses right after a callable CALLS it,
because that syntax is defined to be THE syntax for such calls in
Python.  Similarly, simply prepending "for xx in" before an iterable
ITERATES ON it, because that syntax is defined to be THE syntax for
such iteration in Python.  Neither is "less explicit" than verbose
alternatives requiring (e.g.) access to attributes on the callable
or iterable object.  Such access to attributes could not (by first
class objects rule -- "everything's an object") produce anything
BUT objects -- so where does one stop...? x.call.call.call.call...???

This has nothing to do with "eschewing code blocks", btw; code blocks
are not "eschewed" -- they are simply syntactically allowed, as
"suites", only im specific positions.  If Python's syntax defined
other forms of suites, e.g. hypothetically:

with <object>:
    <suite>

meaning to call the object (or some given method[s] in it, whatever)
with the suite as its argument, it would be just as explicit as, e.g.:

for <name> in <object>:
    <suite>

or

<object>(<object>)

are today.  Whether it would be wise, useful, etc, etc, is a different
set of issues, but I disagree that there is relevance here of the
implicit vs explicit principle (I know the poster you were replying to
did first claim that principle mattered here, but I just disagree with
him as well:-).  Of course, if we did adopt that 'with' or similar
syntax we'd also have to decide on the TYPE of a 'suite' thus literally
expressed, an issue which current syntax constructs using suites do
not have -- perhaps a code object, perhaps a callable (but in the
latter case you'r probably also want 'arguments' -- so the syntax might
have to be slightly more extensive, e.g. "with <object>, <name1>, <name2>:"
instead).  But that seems a secondary issue to me.


> This is not to say that I dislike that behavior; in fact, I find it
> *beneficial* that the manner of looping is *implicit* because you can
> substitute a generator for a sequence without changing the usage. But

You could do so even if you HAD to say iter(<object>) instead of
just <object> after every "for <name> in" -- it wouldn't be any
more "explicit", just more verbose (redundant, boiler-platey).  So
I do not agree with your motivation for liking "for x in y:" either;-).

> there's little readability difference, IMHO, between that and:
> 
> file('input.txt').each_line({ |line|
>      do_something_with(line)
> })

Not huge, but the abundance of ({ | &c here hurts a little bit.


> Plus, the first example is only obvious because I called my iteration
> variable "line", and because this behavior is already widely known. What
> if I wrote:
> 
> for byte in file('input.dat'):
>      do_something_with(byte)
> 
> That would be a bit misleading, no? But the mistake isn't obvious. OTOH,
> in the more explicit (in this case) Ruby language, it would look silly:
> 
> open('input.txt').each_line { |byte|
>      # huh? why a byte? we said each_line!
> }

Here, you're arguing for redundance, not for explicitness: you are claiming
that IF you had to say the same thing more than once, redundantly, then
mistakes might be more easily caught.  I.e., the analogy is with:

file('foo.txt').write('wot?')

where the error is not at all obvious (until runtime when you get an
exception): file(name) returns an object *open for reading only* -- so
if you could not call file directly but rather than do say, e.g.:

file.open_for_reading_only('foo.txt').write('wot?')

the contrast induced by the mandated redundance might (one hopes)
make the error "look silly".  Many languages do rather enthusiastically
embrace this, making you write more redundant boilerplate than useful
code connected with your program's task, or so it seems at times --
neither Ruby nor Python, however, go in for such systematic use of
redundance in general.  In any case, redundance and explicitness are
separate concepts: if you have to express something more than once,
that is redundance -- if you have to express it (once or more) rather
than having the language guess on your behalf, that is explicitness.

Having sensible defaults does not necessarily violate explicitness,
btw.  E.g., the reason we have to say "class x(object):" today is NOT
"just to be explicit" -- it's an unfortunate consequence of the need
to continue having old-style classes (and the prudent choice to keep
them as the default to ensure slow, smooth migration); an issue of
legacy, backwards compatibility, and concern for the existing body of
code, in other words, rather than of "implicit vs explicit".  Once we
proceed on the slow process of burying classic classes, we can make
object the default base _without_ damaging anything.  Of course, one
COULD puristically disagree -- but, practicality beats purity...;-).


> I think this is important to point out, because the implicit/explicit
> rule comes up all the time, yet Python is implicit about lots of things!
> To name a few:
> 
>    - for loops and iterators

Already addressed above: nothing implicit there.

>    - types of variables

There are none, so how could such a nonexisting thing be EITHER implicit
OR explicit?  Variables don't HAVE types -- OBJECTS do.

Etc, etc -- can't spend another 1000 lines to explain why your "lots of
things" do not indicate violations of "explicit is better than implicit".


> If all you're saying is that naming something is better than not naming
> something because explicit is better than implicit, I'd have to ask why:

Sometimes it is (to avoid perilous nesting), sometimes it isn't (to
avoid wanton naming).  I generally don't mind naming things, but it IS
surely possible to overdo it -- without going to the extreme below,
just imagine a language where ONLY named argument passing, and no use
of positional arguments, was allowed (instead of naming arguments being
optional, as it is today in Python).


>> Even your example clearly shows that try block is much more readable and
>> understandable.
>> That's why it's being considered evil by majority of python developers.

I don't agree with Mike that try/finally is particularly readable.  Yes,
it IS understandable, but its lack of support for [a] an explicit
initialization phase and [b] distinction, when needed, between normal
and unnormal exists, does lead to frequent problems -- e.g.:

    try:
        f = open('goo.gah')
        process_file(f)
    finally:
        f.close()

this is a FREQUENT bug -- if open fails, and thus f remains unbound,
the finally clause will STILL try to call close on it.  Psychologically
it comes natural to write the initialization INSIDE the try/finally,
but technically it should be inside.  Also, when the actual actions
require more than one object, try/finally leads to deep nesting, and
flat is better than nested, e.g.:

    fs = open(xx, 'r')
    try:
        f1 = open(x1, 'w')
        try:
            f2 = open(x2, 'w')
            try:
                skip_prefix(fs)
                split_from_to(pred, fs, f1, f2)
                add_postfix(f1)
                add_postfix(f2)
            finally:
                f2.close()
        finally:
            f1.close()
    finally:
        fs.close()

In a word, "yeurgh".

Not that the introduction of "block syntax" would be a panacea here,
necessarily.  But claiming there is no problem with try/finally is,
IMHO, kidding ourselves.


> Readability is a moving target. I think that the code block syntax
> strikes a nice balance between readability and expressiveness. As far as

Maybe.  I'm still not sold, though I think I do understand well why
one would WANT a literal form for code blocks.  But some of the
use cases you give for 'not naming' -- e.g, a return statement --
just don't sit right with the kind of syntax that I think might help
with many of them (e.g. the 'with' keyword or something similar, to
pass blocks as arguments to callables, ONLY -- that's all Ruby allows,
too, so your 'return' use case above-mentioned wouldn't work all that
well there, either, though its lack of expression/statement split may
perhaps help a little).

If a Pythonic syntax can't be found to solve ALL use cases you've
raised, then the "balance" may be considered not nice enough to
compensate for the obvious problem -- a serious case of MTOWTDI.

> what the majority of Python developers consider evil, I don't think
> we've got the stats back on that one.

I don't think anybody has "stats", but following python-dev
regularly does give you a pretty good sense of what the consensus
is on what issues (it matters up to a point, since in the end Guido
decides, but, it does matter somewhat).  MTOWTDI, for example, is
a dead cert for a chorus of boos -- even when the existing WTDI is
anything but "obvious", e.g. reduce(operator.add, somelist) in 2.2
and before, proposing an obvious alternative. e.g. sum(somelist) in
2.3, is SURE to draw some disagreement (good thing, in this case,
that Guido overruled the disagremeent and adopted the 'sum' builtin).

So, the emergence of a way to write, e.g.:

"""
def loop_on_each_byte(filename):
    def looping_callable(block):
        ...block(byte)...
    return looping_callable

with loop_on_each_byte(filename), byte:
    process_byte(byte)
"""

as an OWTDI from

"""
def each_byte(filename):
    ...yield byte...

for byte in each_byte(filename):
    process_byte(byte)
"""

would SURELY draw a well-justified roar.  The benefits would have
to be very overwhelming indeed to overcome this issue.  But if we
were to support "return this literal code block", for example,
then the code block literal syntax would have to be an expression
rather than a suite -- and I just can't find a good way to do
THAT.  And if we don't support use cases which advocates of such
a new construct, like you, quote fondly -- and, I repeat, the
"why should I have to name something I am just going to return"
WAS one of the few use cases for literal code blocks you brought,
even though it's not directly supported in Ruby (or Smalltalk, as
fas as I know).  So, I suspect there may be no good solution.


>>>But the anonymous version still looks more concise to me.
>> 
>> Python prioritize things diferently than other languages.
>> It's not an APL. "Readability counts"
> 
> This is nothing like APL... if anything, it's like Smalltalk, a language
> designed to be readable by children! 

Cite pls?  I knew that Logo and ABC had been specifically designed
with children in mind, but didn't know that of Smalltalk.

> I realize that APL sacrificed
> readability for expressiveness to an uncomfortable extreme, but I really
> think you're comparing apples and oranges here. List comprehensions are
> closer to APL than code blocks.

As an ex-user of APL (and APL2) from way back when, I think you're
both talking through your respective hats: neither list comprehensions
(particularly in the Python variation on a Haskell theme, with
keywords rather than punctuation) nor code blocks resemble APL in the least.


Alex





More information about the Python-list mailing list