pre-PEP: Simple Thunks

Ron_Adam radam2_ at _tampabay.rr.com
Sun Apr 17 02:31:16 EDT 2005


On Sat, 16 Apr 2005 17:25:00 -0700, Brian Sabbey
<sabbey at u.washington.edu> wrote:

>> You can already do this, this way.
>>
>>>>> def f(thunk):
>> ...     before()
>> ...     thunk()
>> ...     after()
>> ...
>>>>> def before():
>> ...     print 'before'
>> ...
>>>>> def after():
>> ...     print 'after'
>> ...
>>>>> def stuff():
>> ...     print 'stuff'
>> ...
>>>>> def morestuff():
>> ...     print 'morestuff'
>> ...
>>>>> f(stuff)
>> before
>> stuff
>> after
>>>>> f(morestuff)
>> before
>> morestuff
>> after
>>>>>
>>
>> This works with arguments also.
>
>Yes, much of what thunks do can also be done by passing a function 
>argument.  But thunks are different because they share the surrounding 
>function's namespace (which inner functions do not), and because they can 
>be defined in a more readable way.

Generally my reason for using a function is to group and separate code
from the current name space.  I don't see that as a drawback.

Are thunks a way to group and reuse expressions in the current scope?
If so, why even use arguments?  Wouldn't it be easier to just declare
what I need right before calling the group?  Maybe just informally
declare the calling procedure in a comment.

def thunkit:	  # no argument list defines a local group.
	# set thunk,x and y before calling.      
	before()
	result = thunk(x,y)
	after()

def foo(x,y):
	x, y = y, x
	return x,y

thunk = foo
x,y = 1,2
do thunkit
print result

-> (2,1)

Since everything is in local name space, you  really don't need to
pass arguments or return values.  

The 'do' keyword says to evaluate the group.  Sort of like eval() or
exec would, but in a much more controlled way.  And the group as a
whole can be passed around by not using the 'do'.  But then it starts
to look and act like a class with limits on it.  But maybe a good
replacement for lambas?

I sort of wonder if this is one of those things that looks like it
could be useful at first, but it turns out that using functions and
class's in the proper way, is also the best way. (?)

>You're right that, in this case, it would be better to just write 
>"f(stuff, 27, 28)".  That example was just an attempt at describing the 
>syntax and semantics rather than to provide any sort of motivation.  If 
>the thunk contained anything more than a call to 'stuff', though, it would 
>not be as easy as passing 'stuff' to 'f'.  For example,
>
>do f(27, 28):
> 	print stuff()
>
>would require one to define and pass a callback function to 'f'.  To me, 
>'do' should be used in any situation in which a callback *could* be used, 
>but rarely is because doing so would be awkward.  Probably the simplest 
>real-world example is opening and closing a file.  Rarely will you see 
>code like this:
>
>def with_file(callback, filename):
> 	f = open(filename)
> 	callback(f)
> 	f.close()
>
>def print_file(file):
> 	print file.read()
>
>with_file(print_file, 'file.txt')
>
>For obvious reasons, it usually appears like this:
>
>f = open('file.txt')
>print f.read()
>f.close()
>
>Normally, though, one wants to do a lot more than just print the file. 
>There may be many lines between 'open' and 'close'.  In this case, it is 
>easy to introduce a bug, such as returning before calling 'close', or 
>re-binding 'f' to a different file (the former bug is avoidable by using 
>'try'/'finally', but the latter is not).  It would be nice to be able to 
>avoid these types of bugs by abstracting open/close.  Thunks allow you to 
>make this abstraction in a way that is more concise and more readable than 
>the callback example given above:

How would abstracting open/close help reduce bugs?  

I'm really used to using function calls, so anything that does things
differently tend to be less readable to me. But this is my own
preference.  What is most readable to people tends to be what they use
most. IMHO

>do f in with_file('file.txt'):
> 	print f.read()
>
>Thunks are also more useful than callbacks in many cases since they allow 
>variables to be rebound:
>
>t = "no file read yet"
>do f in with_file('file.txt'):
> 	t = f.read()
>
>Using a callback to do the above example is, in my opinion, more 
>difficult:
>
>def with_file(callback, filename):
> 	f = open(filename)
> 	t = callback(f)
> 	f.close()
> 	return t
>
>def my_read(f):
> 	return f.read()
>
>t = with_file(my_read, 'file.txt')

Wouldn't your with_file thunk def look pretty much the same as the
callback?

I wouldn't use either of these examples.  To me the open/read/close
example you gave as the normal case would work fine, with some basic
error checking of course. Since Python's return statement can handle
multiple values, it's no problem to put everything in a single
function and return both the status with an error code if any, and the
result.  I would keep the open, read/write, and close statements in
the same function and not split them up.

>> When I see 'do', it reminds me of 'do loops'. That is 'Do' involves
>> some sort of flow control.  I gather you mean it as do items in a
>> list, but with the capability to substitute the named function.  Is
>> this correct?
>
>I used 'do' because that's what ruby uses for something similar.  
>

I could see using do as an inverse 'for' operator.  Where 'do' would
give values to items in a list, verses taking them from a list. I'm
not exactly sure how that would work. Maybe...  

def fa(a,b):
	return a+b
def fb(c,d):
	return c*b
def fc(e,f):
	return e**f

fgroup:
	fa(a,b)
	fb(c,d)
	fc(e,f)

results = do 2,3 in flist

print results
->  (5, 6, 8)

But this is something else, and not what your thunk is trying to do.



So it looks to me you have two basic concepts here.  

(1.) Grouping code in local name space.

I can see where this could be useful. It would be cool if the group
inherited the name space it was called with.  

(2.) A way to pass values to items in the group. 

Since you are using local space, that would be assignment statements
in place of arguments.

Would this work ok for what you want to do?

def with_file:				# no argument list, local group.
	f = open(filename)
	t = callback(f)
	f.close

def my_read(f):
 	return f.read()

callback = my_read
filename = 'filename'
do with_file


Something I've noticed is that there seems to be a trend to want to
put things behind other things as with decorators.

do with_file:
	filename = 'filename'
	callback = my_read


This in my opinion is yet a third item.  Could be that 'do' could also
relate to that as in do 'a' after 'b'.

do:							# do this after next group
	do with_file			# do group named with_file
after:						# don't like 'after', but what else?
	filename = 'filename'
	callback = my_read


Any way, this if just food for thought,  I'm really undecided on most
of this.

Cheers,
Ron














  





 


	



More information about the Python-list mailing list