pre-PEP: Simple Thunks

Brian Sabbey sabbey at u.washington.edu
Sat Apr 16 20:30:01 EDT 2005


peufeu at free.fr wrote:
>
> 	Keep in mind that most of the problems come from the "space is 
> significant" thing, which is IMHO a very good idea, but prevents us from 
> putting code in expressions, like :
>
> 	func( a,b, def callback( x ):
> 		print x
> 	)
>
> 	or does it ? maybe this syntax could be made to work ?

Hmm.  I'd like to think that suite-based keywords do make this example 
work.  One just has to let go of the idea that all arguments to a function 
appear inside parentheses.

> ****************************************
> 	Comments on the thunks.
>
> 	First of all I view code blocks as essential to a language. They are 
> very useful for a lot of common programming tasks (like defining callbacks in 
> an elegant way) :
>
> 	button = create_button( "Save changes" ):
> 		do
> 			self.save()
>
> 	However it seems your thunks can't take parameters, which to me is a 
> big drawback. In ruby a thunk can take parameters. Simple examples :
>
> 	field = edit_field( "initial value", onsubmit={ |value| if 
> self.validate(value) then do something else alert( "the value is invalid" ) } 
> )
> 	[1,3,4].each { |x| puts x }

Thunks can take parameters:

do value in field = edit_field("initial value"):
 	if self.validate(value):
 	 	something
 	else:
 		alert("the value is invalid")

But callbacks are better defined with suite-based keywords:

field = edit_field("initial value"):
 	def onsubmit(value):
 		if self.validate(value):
 			something
 		else:
 			alert("the value is invalid")

> 	This has the advantage that the interface to the thunk (ie. its 
> parameters) are right there before your eyes instead of being buried in the 
> thunk invocation code inside the edit_field.

In the cases in which one is defining a callback, I agree that it would be 
preferable to have the thunk parameters not buried next to 'do'. 
However, I do not consider thunks a replacement for callbacks.  They are a 
replacement for code in which callbacks could be used, but normally aren't 
because using them would be awkward.  Your 'withfile' example below is a 
good one.  Most people wouldn't bother defining a 'withfile' function, 
they would just call 'open' and 'close' individually.

> 	So I think it's essential that thunks take parameters and return a 
> value (to use them effectively as callbacks).
> 	What shall distinguish them from a simple alteration to def(): which 
> returns the function as a value, and an optional name ? really I don't know, 
> but it could be the way they handle closures and share local variables with 
> the defining scope. Or it could be that there is no need for two kinds of 
> function/blocks and so we can reuse the keyword def() :

Right, defining a function with 'def' is different than defining a thunk 
because thunks share the namespace of the surrounding function, functions 
do not:

x = 1
def f():
 	x = 2   # <- creates a new x
g(f)
print x # ==> 1

do g():
 	x = 2
print x # ==> 2 ( assuming 'g' calls the thunk at least once)

>
> 	If you wish to modify def(), you could do, without creating any 
> keyword :
>
> # standard form
> f = def func( params ):
> 	code
>
> # unnamed code block taking params
> func = def (params):
> 	code
>
> 	Note that the two above are equivalent with regard to the variable 
> "func", ie. func contains the defined function. Actually I find def 
> funcname() to be bloat, as funcname = def() has the same functionality, but 
> is a lot more universal.
>
> # unnamed block taking no params
> f = def:
> 	code

I'm confused.  These examples seem to do something different than what a 
'do' statement would do.  They create a function with a new namespace and 
they do not call any "thunk-accepting" function.

> ***************************************************
> 	Comments on the suite-based keywords.
>
> 	Did you notice that this was basically a generalized HEREDOC syntax ?
>
> 	I'd say that explicit is better than implicit, hence...
>
> Your syntax is :
>
> do f(a,b):
> 	a block
>
> 	passes block as the last parameter of f.
> 	I don't like it because it hides stuff.

Yes, it hides stuff.  It doesn't seem to me any worse than what is done 
with 'self' when calling a method though.  Once I got used to 'self' 
appearing automatically as the first parameter to a method, it wasn't a 
big deal for me.

> I'd write :
>
> f(a,b,@>,@>):
> 	"""a very
> large multi-line
> string"""
> 	def (x):
> 		print x
>
> 	Here the @> is a symbol (use whatever you like) to indicate 
> "placeholder for something which is on the next line".
> 	Indentation indicates that the following lines are indeed argument 
> for the function. The : at the end of the function call is there for 
> coherence with the rest of the syntax.
> 	Notice that, this way, there is no need for a "do" keyword, as the 
> code block created by an anonymous def() is no different that the other 
> parameter, in this case a multiline string.
>
> 	This has many advantages.
>
> 	It will make big statements more readable :
>
> 	instead of :
> 	f( a,b, [some very big expression made up of nested class 
> constructors like a form defintion ], c, d )
>
> 	write :
> 	f( a, b, @>, c, d ):
> 		[the very big expression goes here]
>
> 	So, independently of code blocks, this already improves the 
> readability of big statements.

It might be useful in some situations to be able to define suite-based 
arguments by their order rather than their keyword.  But, to me, one of 
Python's strengths is that it avoids special syntactic characters.  So I 
can't say I like "@>" very much.  Perhaps there is another way of allowing 
this.

> 	You could also use named parameters (various proposals):
>
> 	f( a,b, c=@>, d=@> ):
> 		value of c
> 		value of d
>
> 	or :
>
> 	f( a,b, @*> ):
> 		value of c
> 		value of d
>
> 	f( a,b, @**> ):
> 		c: value of c
> 		d: value of d
>
> 	Notice how this mimics f( a,b, * ) and f(a,b, ** ) for multiple 
> arguments, and multiple named arguments. Do you like it ? I do. Especially 
> the named version where you cant' get lost in the param block because you see 
> their names !

I like those ideas, but maybe just leave off the "@>".

f(a,b,c=,d=):
 	c = 1
 	d = 2

f(a,b,*)
 	1
 	2

f(a,b,**)
 	c = 1
 	d = 2

Another problem is that one may create bindings in the suite that should 
not be keywords.  Explicitly defining the keywords would be useful in this 
case too.  For example,

f(a,b,c=,d=):
 	c = [i**2 for i in [1,2]]   # 'i' is temporary
 	d = 2

Here 'i' would not be passed as a keyword argument, because it 'c' and 'd' 
are explicitly defined as the only keyword arguments.

>
> 	Now if you say that def returns the defined function as a value, you 
> don't need a do keyword.
>
> So, for instance :
>
> def withfile( fname, thunk, mode = "r" ):
> 	f = open( fname, mode )
> 	thunk(f)
> 	f.close()
>
> then :
>
> withfile( "afile.txt", @>, "w" ):
> 	def (f):
> 		f.write( something )
>
> Now, you may say that the def on an extra line is ugly, then just write :
>
> withfile( "afile.txt", @>, "w" ):	def (f):
> 	f.write( something )
>
> If you really like do you can make it a synonym for def and then :
>
> withfile( "afile.txt", @>, "w" ):	do (f):
> 	f.write( something )
>
> 	The two ":" seem a bit weird but I think they're OK.
>

Again, there is the problem of a new namespace being created when using 
'def'.  Also, it's annoying to have to use 'def' (even though one could 
just put it on the same line).

>> 'break' and 'return' should probably not be allowed in thunks.  One
>
> 	I do think return should be allowed in a thunk. After all it's a 
> function block, so why ditch useful functionality ?
> 	yield should also be allowed, after all why can a thunk not be a 
> generator ? This would be powerful.


> def withfile( fname, thunk, mode = "r" ):
> 	f = open( fname, mode )
> 	r = thunk(f)
> 	f.close()
> 	return r
>
> val = withfile( "afile.txt", @>, "w" ):
> 	def (f):
> 		f.write( something )
> 		return something
>
> 	Well, it seems I have no more ideas for now.
> 	What do you think about all this ?
>

Well here you're using a type of suite-based keywords, so 'return' is ok. 
Inside thunks I still think 'return' would be confusing.  I don't think 
one can replace thunks with suite-based keywords.

>> The thunk evaluates in the same frame as the function in which it was 
>> defined. This frame is accessible:
>
> 	Hm ?
> 	You mean like a function closure, or that local variables defined 
> inside the thunk will be visible to the caller ?
> 	This might change a lot of things and it becomes like a continuation, 
> are you going to recode stackless ?

I meant that a thunk is not like a function closure.  Variables defined in 
the thunk won't be visible to the caller of the thunk (except through 
tk_frame), but they will be visible to the function surrounding the thunk. 
I made an initial implementation, and it didn't require anything like 
stackless.

-Brian



More information about the Python-list mailing list