Skip to main content

Trampolining for recursion...

In essence, CPS (continuation passing style) is a method of calling functions where instead of returning a value, you pass the results to another function (the continuation) provided as an argument. I wont go too deep into the reasoning behind this, as it's usually not used directly but rather as a middle representation of programming languages by some compiler/interpreter (read more on the wikipedia link).

The big downside is that you're bombarding the stack with function frames for every call, going deeper and deeper, but never returning (you just keep passing what would be the return value to another function).

One nifty method of avoiding this is to use a trampoline. A trampoline resembles a stream/lazy-list to anyone who's read SICP. Essentially, instead of calling functions from within another function, you return a bounce "call" to the trampoline which makes the call for you, doing the same with the return value of that call until it gets a land (a non-bounce return). This avoids the use of the stack for function calls, but allows you to use function closure as a means of saving state.

That being said, here is a simple trampoline implementation in Python showing a recursive fibonacci function implemented in CPS:
class Call:
def __init__(self, fn, *args, **kwargs):
self.call = lambda: fn(*args, **kwargs)

def trampoline(obj):
while isinstance(obj, Call):
obj = obj.call()
return obj

def fibonacci(k, n):
if n < 2:
return Call(k, n)
def with_a(a):
def with_b(b):
return Call(k, a + b)
return Call(fibonacci, with_b, n - 1)
return Call(fibonacci, with_a, n - 2)

call = fibonacci(lambda x: x, 20)

print trampoline(call)
Here is a normal fibonacci function, memoized, that fails due to exceeding Pythons recursion limit. The problem being, of course, that you have to recurse down deep enough to use the leaf calls to accumulate to the trunk:
def memoized(func):
cache = {}
def decorator(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return decorator

@memoized
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
print fibonacci(1000)
Here, however, is essentially the same function (memoized) implemented using a trampoline. The trampoline allows it to "recurse" all the way to the leaf calls because the calls are actually being made external to the function and state is being preserved in the with_a/with_b closures:
def kmemoized(func):
cache = {}
def decorator(k, *args):
if args in cache:
return Call(k, cache[args])
def with_value(value):
cache[args] = value
return Call(k, value)
return Call(func, with_value, *args)
return decorator

@kmemoized
def fibonacci(k, n):
if n < 2:
return Call(k, n)
def with_a(a):
def with_b(b):
return Call(k, a + b)
return Call(fibonacci, with_b, n - 1)
return Call(fibonacci, with_a, n - 2)
Practical for typical application development? Probably not...

A neat concept to play around with (or an interesting strategy at generating Python code)? Definitely!

(Somewhat) related wikipedia articles: continuation, functional-programming, tail-call-optimization, thunk, closure

Popular posts from this blog

DIY Solar Powered LoRa Repeater (with Arduino)

In today's video I be built a solar powered LoRa signal repeater to extend the range of my LoRa network. This can easily be used as the basis for a LoRa mesh network with a bit of extra code and additional repeaters. Even if you're not into LoRa networks all of the solar power hardware in this video can be used for any off-the-grid electronics projects or IoT nodes!  

A Lesson in LoRa Module P2P Standards (or the Lack Thereof)

I got a handful of LoRa modules from Reyax a while back, the RYLR896 model based on Semtech SX1276 chips. Instead of using an SPI interface they operate over UART using a small set of AT commands. This made them easier to work with since I didn't have to dig too deeply into a bunch of SPI registers and Semtech specs and they communicate between one another really well. My Espruino JS module for them is available here , which I've used in a few of my YouTube videos. And more recently I've written a MicroPython module for them here .   (A pair of Reyax RYLR896  modules) But, always being on the lookout for different boards and platforms I eventually ended up with a few Maduino LoRa boards. These are cool because they have an Arduino-compatible ATmega328 and the same Semtech LoRa chip (via an RFM95) both integrated on one board. They weren't compatible with Espruino or MicroPython though, and they used the SPI interface instead of AT commands so I knew I would need to lo...

Always Secure Your localhost Servers

Recently I was surprised to learn that web browsers allow any site you visit to make requests to resources on localhost (and that they will happily allow unreported mixed-content ). If you'd like to test this out, run an HTTP server on port 8080 (for instance with python -m http.server 8080 ) and then visit this page. You should see "Found: HTTP (8080)" listed and that's because the Javascript on that page made an HTTP GET request to your local server to determine that it was running. Chances are it detected other services as well (for instance if you run Tor or Keybase locally). There are two implications from this that follow: Website owners could potentially use this to collect information about what popular services are running on your local network. Malicious actors could use this to exploit vulnerabilities in those services. Requests made this way are limited in certain ways since they're considered opaque , meaning that the web page isn't able...