A flow control structure that never makes mistakes (sorta)

I’ve been experimenting with Lisp-style ad-hoc flow control structures. Nothing terribly useful, but nonetheless amusing. E.g., here’s a dobest() function that always does the best thing (and only the best thing) among the alternatives given to it — think of the mutant in Philip K. Dick’s The Golden Man, or Nicolas Cage in the awful “adaptation” Next.

Here’s how you use it:

if __name__ == '__main__':

    def measure_x():
        "Metric function: the value of x"
        global x
        return x

    def increment_x():
        "A good strategy: increment x"
        global x
        x += 1

    def decrement_x():
        "A bad strategy: decrement x"
        global x
        x -= 1

    def fail():
        "An even worse strategy"
        global x
        x = x / 0

    x = 1
    # assert(x == 1)
    dobest(measure_x, increment_x, decrement_x, fail)
    # assert(x == 2)

You give it a metric, a function that returns how good you think the current world is, and one or more functions that operate on the environment. Perhaps disappointingly dobest() doesn’t actually see the future; rather, it executes each function on a copy of the current environment, and only transfers to the “real” one the environment with the highest value of metric().

Here’s the ugly detail (do point out errors, but please don’t mock too much; I haven’t played much with Python scopes):

def dobest(metric, *branches):
    "Apply every function in *branches to a copy of the caller's environnment, only do 'for real' the best one according to the result of running metric()."

    world = copy.copy(dict(inspect.getargvalues(sys._getframe(1)).locals))
    alts = []

    for branchfunction in branches:
        try:
            # Run branchfunction in a copy of the world
            ns = copy.copy(world)
            exec branchfunction.__code__ in ns, {}
            alts.append(ns)
        except: # We ignore worlds where things explode
            pass
    
    # Sort worlds according to metric()
    alts.sort(key=lambda ns: eval(metric.__code__, ns, {}), reverse=True)
    for key in alts[0]:
        sys._getframe(1).f_globals[key] = alts[0][key]

One usability point is that the functions you give to dobest() have to explicitly access variables in the environment as global; I’m sure there are cleaner ways to do it.

Note that this also can work a bit like a try-except with undo, a la

dobest(bool, function_that_does_something, function_that_reports_an_error)

This would work like try-except, because dobest ignores functions that raise exceptions, but with the added benefit that dobest would clean up everything done by function_that_does_something.

Of course, and here’s the catch, “everything” is kind of limited — I haven’t precisely gone out of my way to track and catch all side effects, not that it’d even be possible without some VM or even OS support. Point is, the more I get my ass saved by git, the more I miss it in my code, or even when doing interactive data analysis with R. As the Doctor would say, working on just one timeline can be so… linear.