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 -- http://mail.python.org/mailman/listinfo/python-list

Reply via email to