I’ve recently started coding in Scala (recently being over a year ago). While I dislike Scala companion religion ‘everything functional’, I do like some of the language features that Scala provides – pattern matching, lambdas, traits and some. I had a discussion with a friend regarding implementing something as close as possible to Scala Lambdas in Java – which resulted in The Lambda Experiment – here is the story of the attempt, what it does and why I abandoned it.
It is important to note that this experiment, while not production ready, does show a number of interesting technics in Java (which can be implemented just the same with any other JVM language, Scala included). We demonstrate the nature of Java, being statically typed dynamic language. We implement the dynamic nature of Java in this example using the Javassist library to generate Java classes at runtime. It is important to note that there are other mechanisms for dynamic coding in Java, such as JDK Proxy and other bytecode level libraries, but that is out of the scope of this post.
We were inspired by a number of sources – the proposals for Java 8 Lambdas, The Scala programming language and some projects trying to do things like Lambdas for Java (e.g. LambdaJ). The functional code (functions body) is written as a String (the Java compiler sees it as a string) that is compiled using the javassist compiler, loaded at runtime and used to implement SAM (Single Abstract Method) interfaces.
Example of The Lambda Experiment
The lambda experiment enables writing a list.map as
which is equivalent to the Scala code
In addition, The Lambda Experiment supports SAM (Single Abstract Method) Interfaces, which enables reusing existing Java interfaces that take SAM parameters as inputs. A Good example is the Collections.sort operation which accepts a Comparator SAM.
How does it work?
The main idea of the lambda experiment is coding at timetime. We take a string which is the code of a method body and compile it at runtime to generate a new Java class, load that class and use it to create an instance of a SAM interface. In affect, we have implemented a two step process
A SAM is, as stated above, a single method interface, such as Comparator
The methods for creation of FunctionN Lambdas
The LambdaSignature class is a factory class that, given a code and a set of variable bindings, will use the LambdaClassGenerator to actually generate a class implementing the F interface.
The actual compilation and loading of classes at runtime is done using the LambdaClassGenerator class (LambdaClassGenerator.java)
Checkout the generateClass() method – it does the following:
Generates the code of our class, each method and field separately.
Creates Javassist ClassPool and configures it with the relevant classpath
Creates Javassist CtClass object
Adds parent interface to the CtClass and all the fields and methods
Calls ctClass.toClass() to get the actual Java Class object of our newly created class
Then we use reflection to get the class constructor and create an instance of that class.
How does the code generation works? Given the following Lambda
Limitations of the Lambda Experiment Project
The Lambda Experiment was built as a prof of concept and as such, we did not invest in proving all the features required by a production library. We did not implement the following:
Multiple line expressions. The Lambda Experiment assumes that the string expressions passed to the build method are single line expressions, that are always wrapped with return {expression};.
Functions with over 3 parameters. It is trivial to add those – not interesting for experiment scope.
Curry operations – transform a Function3 into Function2 by providing a variable value. This seems trivial and as such, was not implemented.
Functional Collections – the project does not implement another set of functional collection libraries for Java, such as LambdaJ or Guava. The Lambda Experiment can be used with such existing libraries or used to create additional such libraries.
Why did we abandon the project?
The Lambda Experiment exposed some limitations of the Javassist project as well as some limitations imposed by it’s approach.
The first and most annoying limitation is that Javassist syntax is pre-java 5. It does not wrap and unwrap primitives as the Java 5+ compiler does.
Another limitation is that because the code is compiled as a new class and not as an inner class, it cannot reuse the imports list of the declaring class nor can we use variables declared in scope.
The impact of the two above limitations is that when in Scala or Java 8 lambdas we can write
What can we learn from this experiment?
It shows how we can create new java classes at runtime and use them just like any other class.
It shows the nature of Java – it is statically typed, requiring interface definitions at compile time.
However, Java is dynamic – we can implement those interfaces at runtime using the JDK Proxy and the creation of classes at runtime.
This post was written by Yoav Abrahami
You can follow him on Twitter