Proposed keyword to transfer control to another function

Chris Angelico rosuav at gmail.com
Thu Jul 16 19:46:45 EDT 2015


Out of the lengthy thread on tail call optimization has come one broad
theory that might be of interest, so I'm spinning it off into its own
thread.

The concept is like the Unix exec[vlpe] family of functions: replace
the current stack frame with a new one. This can be used for explicit
tail recursion without repeated stack frames, or for a pre-check that
then disappears out of the stack. Like any other feature, it can be
misused to make debugging difficult, but among consenting adults,
hopefully it can be used sensibly.

Examples:

# derived from Paul Rubin's example
def quicksort(array, start, end):
     midp = partition(array, start, end)
     if midp <= (start+end)//2:
        quicksort(array, start, midp)
        transfer quicksort(array, midp+1, end)
     else:
        quicksort(array, midp+1, end)
        transfer quicksort(array, start, midp)

def count_usage(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.usage += 1
        transfer func(*args, **kwargs)
    wrapper.usage = 0
    return wrapper

Semantics:
* Evaluate the function and all its arguments, exactly as per a
current function call.
* Leaving them on the stack, remove the current call frame and dispose
of all its objects.
* Finally, construct a new stack frame for the target function and
transfer control to it.

In effect, "transfer f()" is equivalent to "return f()", except that
the current function finishes before the target is entered.

Note that this is incompatible with 'try' and 'with' blocks, and is
disallowed inside them. It may be safe to use 'transfer' from a
'finally' block, and possibly from an 'except' block that isn't
followed by a 'finally', but otherwise, there's code to be executed
after the target returns, ergo it can't be done with a direct
transfer.

Open for bikeshedding: What should the keyword be? We can't use
"exec", which would match Unix and shell usage, because it's already
used in a rather different sense in Python. Current candidates:
"transfer", "goto", "recurse", and anything else you suggest.

Possible alternate syntax:

transfer func[, (arg1, arg2, arg3)[, {'kw1': val1, 'kw2': val2}]]

This makes it very clear that this is NOT accepting an arbitrary
expression, but MUST be used with a single function and its arguments.
Downside: It doesn't look like a function call any more. Upside: It's
easy to parse. Also, if the argument sequence is mandatory instead of
optional, this doesn't conflict with the simpler syntax (which
wouldn't be allowed to have a comma after it), so one can be added now
and another added later, if necessary.

Is there anything that I've missed out in speccing this up? I've a
suspicion that this might turn out to be as complicated as 'yield
from' in all its handling of edge cases.

ChrisA



More information about the Python-list mailing list