Trampolining: a practical guide for awesome Java Developers

Trampolines : Recursion without Stacks

A Trampoline is really a relatively straightforward concept. It is an interface with two concrete implementations, one of which simply represents the result and another that represents the next stage of the computation.

Example Recursive algorithm : Fibonacci generation

Some algorithms can be very neatly defined recursively and the almost prototypical example of an algorithm that can be implemented elegantly recursively is the Fibonacci sequence. Making use of the cyclops-react Trampoline (javadoc link) control structure it can be defined quite succintly as follows

What is happening?

When get is called on the returned Trampoline, internally it will iterate calling ‘bounce’ on the returned Trampoline as long as the concrete instance returned is More, stopping once the returned instance is Done. Essential we convert looping via recursion into iteration, the key enabling mechanism is the fact that Trampoline.more is a lazy operation. Trampoline in cyclops-react extends java.util.Supplier. Calling Trampoline.more we are basically creating a Supplier that defers the actual recursive call, and having defered the call we can move it outside of the recursive loop. This means we can define algorithms recursively in Java but execute them iteratively. To delve deeper, while there are many ways to implement Trampolines in Java, for two particularly elegant examples I recommend looking at are Mario Fusco’s Presentation on Laziness and Trampolines (http://www.slideshare.net/mariofusco/lazine) and Daniel Bodart’s Totally Lazy’s Trampoline implementation (https://github.com/bodar/totallylazy/blob/master/src/com/googlecode/totallylazy/Trampoline.java)

Iterative equivalent

The iterative equivalent is more verbose than it’s recursive cousin.

Trampolines : concurrency without threads

Another benefit is the ability to interleave the execution of two or more functions on the same thread. A technique we use internally in cyclops-react, to have data consumers switch to become data producers when no data is available inside our advanced parallel stream type LazyFutureStream.

Consumer to producer co-routine

Let’s show how Trampolines can be used to create co-routines, interleaved functions running on the same thread. In the example below we have a bog standard JDK Queue, populated with a single value. We want to read three values however, and we don’t want to fail on any read. If no data is available we would like our reader to handle that case and generate some.

LazyFutureStream

cyclops introduces the concept of a FutureStream, this is essential a Stream of data with each data point embeded inside a Future. Clients do not manipulate the Futures directly themselves but rather define (advanced) Stream operations as normal and the library manages the pipeline of Futures and their parallel execution for you.

A FutureStream is a Stream that manages a flow of Future Tasks

Building Trampolines into the APIs

Eval

The Eval type in cyclops-react is a monadic container for Lazy values. It is a Supplier on steroids, and it’s implementation was heavily influenced by Eval from the Scala library Cats (see http://eed3si9n.com/herding-cats/Eval.html).

  1. Now : An eagerly evaluated value
  2. Later : A value that will be evaluated exactly once in the future if triggered
  3. Always : A value that will be evaluated every time it is accessed in the future.
Eval Later and Eval always can be used to defer evaluation and optionally cache the result

Tail recursion for map / flatMap operators

Like Cat’s Eval, cyclops-react version has tail call optimization for map and flatMap operators, as it implements it’s own internal Trampoline for executing these operators. Although less elegant, we can rewrite the fibonacci algorithm using Eval :-

Maybe

The Maybe type in cyclops-react is a totally lazy Haskell stye ‘Option’ type. That is unlike Java’s Optional or Scala’s Option type all operations are lazy. In fact Maybe in cyclops-react is built using Eval. This makes Maybe an excellent return type for expensive operations that may or may not return a value.

Maybe is a lazy Option(al) type

Tail call optimization for map / flatMap operators

Because Maybe is built using Eval, we also inherit it’s tail-call optimization during map & flatMap operations.

Pattern Matching

Match on structures

cyclops also has a pattern matching API. Structural pattern matching in cyclops is implemented via product (Tuples) and sum (Either / Xor) types. Like an Object a Tuple consists of multiple typed fields. Each field must be of the defined type. But unlike a heterogeneous Java Objects, which are defined by their clasess, Tuples have a predefined structure that we can take advantage of at compile time. Sum types (such as Eithers or Xor’s) also have a predefined structure this time determining a number of possible types, of which a field must be one.

  1. We are matching on structures (MTuple1 tells us we know there is one value of the defined type Integer)
  2. Pattern matching is lazy (the return type is Eval)
  3. Pattern matching is tail-recursive (the return type is Eval)

Tail call optimization for map / flatMap operators

Because cyclops pattern matching makes heavy use of Eval, like Eval itself and Maybe map (and other functor operators) as well as flatMap (and other monadic operators) are also tail-call optimized.

Trampolining in a Stream (and other functors!)

Some algorithms can be expressed more elegantly and less verbosely in recursive form, but because Java lacks native Tail Call Optimization often we need to add a little more verbosity to our code in order to write stackless recursive implementations. Throughout the api in cyclops-react we try to make using Trampolines as painless as possible. The functor interface, implemented by a swathe of cyclops control structures including ReactiveSeq (a powerful sequential extended Stream type), LazyFutureStream (a powerful parallel extended Stream type), extended collections, Xor, Maybe, Try and more, includes a trampolining transformation — an excellent choice if you would like to implement your transformation function recursively or interleave multiple co-routines within.

Summary

In my view, while the code style overhead of using Trampolines to implement recursion in Java 8 is pretty minimal, I wouldn’t recommend that engineers start using them everywhere. There are many cases where a standard iterative implementation will be much clearer and easier to understand. Occasionally though, most of us will stumble across algorithms that are just so much neater when written recursively. Trampolines allow us to implement these algorithms in Java in a stack-safe way. For me though the biggest benefit of adopting this technique, is for those very hard to solve problems where code needs to be interleaved and we do not want to spawn extra threads. So long as we have solid reasons for doing so, using trampolines to implement co-routines in Java can make very tough problems a *lot* simpler.

--

--

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
John McClean

John McClean

2.3K Followers

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie