[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