pre-PEP: Simple Thunks

Brian Sabbey sabbey at u.washington.edu
Sat Apr 16 20:25:00 EDT 2005


On Sat, 16 Apr 2005, Ron_Adam wrote:
>> Thunks are, as far as this PEP is concerned, anonymous functions that
>> blend into their environment. They can be used in ways similar to code
>> blocks in Ruby or Smalltalk. One specific use of thunks is as a way to
>> abstract acquire/release code. Another use is as a complement to
>> generators.
>
> I'm not familiar with Ruby or Smalltalk.  Could you explain this
> without referring to them?

Hopefully my example below is more understandable.  I realize now that I 
should have provided more motivational examples.

>> def f(thunk):
>>    before()
>>    thunk()
>>    after()
>>
>> do f():
>>    stuff()
>>
>> The above code has the same effect as:
>>
>> before()
>> stuff()
>> after()
>
> 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.

>> Other arguments to 'f' get placed after the thunk:
>>
>> def f(thunk, a, b):
>>     # a == 27, b == 28
>>     before()
>>     thunk()
>>     after()
>>
>> do f(27, 28):
>>     stuff()
>
> Can you explain what 'do' does better?
>
> Why is the 'do' form better than just the straight function call?
>
> 	f(stuff, 27, 28)
>
> The main difference I see is the call to stuff is implied in the
> thunk, something I dislike in decorators.  In decorators, it works
> that way do to the way the functions get evaluated.  Why is it needed
> here?

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:

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')

>
> 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.  It can 
be used in a flow control-like way, or as an item-in-a-list way.  For 
example, you could replace 'if' with your own version (not that you would 
want to):

def my_if(thunk, val):
 	if val:
 		thunk()

do my_if(a):     # same as "if a:"
 	assert a

(replacing "else" wouldn't be possible without allowing multiple thunks.)

Or you could create your own 'for' (again, NTYWWT):

def my_for(thunk, vals):
 	for i in vals:
 		thunk(i)

do i in my_for(range(10)):
 	print i


-Brian



More information about the Python-list mailing list