[Python-Dev] anonymous blocks
Brian Sabbey
sabbey at u.washington.edu
Fri Apr 22 00:21:18 CEST 2005
Greg Ewing wrote:
> I also have a thought concerning whether the block
> argument to the function should come first or last or
> whatever. My solution is that the function should take
> exactly *one* argument, which is the block. Any other
> arguments are dealt with by currying. In other words,
> with_file above would be defined as
>
> def with_file(filename):
> def func(block):
> f = open(filename)
> try:
> block(f)
> finally:
> f.close()
> return func
>
> This would also make implementation much easier. The
> parser isn't going to know that it's dealing with anything
> other than a normal expression statement until it gets to
> the 'as' or ':', by which time going back and radically
> re-interpreting a previous function call could be awkward.
I made an example implementation, and this wasn't an issue. It took some
code to stick the thunk into the argument list, but it was pretty
straightforward. The syntax that is actually used by the parser can be
the same regardless of whether or not argument list augmentation is done,
so the parser will not find one more awkward than the other.
> This way, the syntax is just
>
> expr ['as' assignment_target] ':' suite
>
> and the expr is evaluated quite normally.
Requiring arguments other than the block to be dealt with by currying can
lead to problems. I won't claim these problems are serious, but they will
be annoying. Say, for example, you create a block-accepting function that
takes no arguments. Naturally, you would define it like this:
def f(block):
do_something_with_block
Now, say you want to add to this function an optional argument, so you
wrap another function around it like in your 'with_file' example above.
Unfortunately, now you need to go find every call of this function and add
empty parentheses. This is annoying. Remember the first time you added
optional arguments to a function and what a relief it was not to have to
go find every call to that function and stick in the extra argument?
Those days are over! (well, in this case anyway.)
Some people, aware of this problem of adding optional arguments, will
define *all* of their block-accepting functions so that they are wrapped
in another function, even if that function takes no arguments (and wars,
annoying ones, will be fought over whether this is the "right" way to do
it or not!):
def f():
def real_func(block):
pass
return real_func
Now the documentation gets confusing. Just saying that the function
doesn't take any non-block arguments isn't enough. You would need very
specific language, which many library authors will not provide.
And there will always be that extra step in thought: do I need the stupid
parentheses or not? There will inevitably be people (including me) who
get the parentheses wrong because of absentmindedness or carelessness.
This will be an extra little speed bump.
Now, you may say that all these arguments apply to function decorators, so
why have none of these problems appeared? The difference is that defining
a function takes a long time, so a little speed bump when decorating it
isn't a big deal. But blocks can be defined almost instantly. Much of
their purpose has to do with making things quicker. Speed bumps are
therefore a bigger deal.
This will also be an issue for beginners who use python. A beginner won't
necessarily have a good understanding of a function that returns a
function. But such an understanding would be required simply to *use*
block-accepting functions. Otherwise it would be completely mysterious
why sometimes one sees this
f(a,b,c) as i:
pass
and sometimes this
g as i:
pass
even though both of these cases just seem to call the function that
appears next to 'as' (imagine you don't have the source of 'f' and 'g').
Even worse, imagine finally learning the rule that parentheses are not
allowed if there are zero arguments, and then seeing:
h() as i:
pass
Now it would just seem arbitrary whether or not parentheses are required
or disallowed. Such an issue may seem trivial to an experienced
programmer, but can be very off-putting for a beginner.
>> Another set of question arose for me when Barry started musing over the
>> combination of blocks and decorators. What are blocks? Well, obviously
>> they are callable. What do they return? The local namespace they
>> created/modified?
>
> I think the return value of a block should be None.
> In constructs like with_file, the block is being used for
> its side effect, not to compute a value for consumption
> by the block function. I don't see a great need for blocks
> to be able to return values.
If you google "filetype:rb yield", you can see many the uses of yield in
ruby. By looking for the uses in which yield's return value is used, you
can find blocks that return values. For example, "t = yield()" or "unless
yield()" indicate that a block is returning a value. It is true that most
of the time blocks do not return values, but I estimate that maybe 20% of
the hits returned by google contain at least one block that does. Of
course, this information is alone is not very informative, one would like
to understand each case individually. But, as a first guess, it seems
that people do find good uses for being able to return a value from a
block.
Probably 'continue <block_retun_val>', which I had proposed earlier, is
awful syntax for returning a value from a block. But 'produce
<block_return_val>' or some other verb may not be so bad. In cases that
the block returns no value, 'continue' could still be used to indicate
that control should return to the function that called the block.
-Brian
More information about the Python-Dev
mailing list