In a couple of weeks, I’m giving two talks at talk at the 2016 JavaOne conference in San Francisco. One of them is called “Groovy and Java 8: Making Java Better“. I’m building examples in preparation for the conference, and as the Groovy community is good about correcting my errors in a friendly way, I thought I’d show some of them here ahead of time.
[Note: the session is labeled CON3277 and will take place Monday, Sept 19, from 12:30 – 1:30pm in the Hilton Plaza Room A, according to the session catalog.]
[One more aside: my co-host on the Groovy Podcast, Baruch Sadogursky, is participating in “The Ultimate Build Tools Face-off“, representing Gradle against Maven and Bazel. The winner of the face-off is determined by audience applause, so if you are within 500 miles of the event, be sure to make your voice heard. If I wasn’t giving my talk at the exact same time, I’d definitely be there.
Let me put that another way. As T’Pau said in the Star Trek original series episode Amok Time:
T’Pau: If both survive the lirpa, combat will continue with the ahn woon.
Kirk: Ah, what do you mean “if both survive?”
T’Pau: This combat is to the death. (emphasis unnecessarily added)
So Baruch, if you’re reading this: DON’T SCREW IT UP.
But no pressure.]
Anyway, my (first) talk is a demonstration of how Groovy goes beyond the functional capabilities added in Java 8, but can be used from Java to make life easier. Of course I’m going to talk about Java 8 lambdas vs Groovy closures, and how the method references syntax is different, but in this post I thought I’d highlight a couple of the cool AST transformations that are somewhat less common.
First, there’s memoize. Memoization is the process of building a cache of method calls, so if the same call occurs again, the system can return the cached value right away rather than re-computing it.
One way Groovy accomplishes this is by adding a memoize
method to the Closure
class. To build the cache, simply invoke the method. For example, consider a variable called add
assigned to a closure that takes two arguments and sleeps for one second before returning their sum.
[sourcecode language=”groovy”]
def add = { x, y -> sleep 1000; x + y }.memoize()
println add(3, 4) // takes 1 sec
println add(3, 4) // immediate
println add(‘a’,’b’) // takes 1 sec
println add(‘a’,’b’) // immediate
[/sourcecode]
Invoking the memoize
method replaces the closure with a new one that has keeps a cache of method calls. Therefore, the first call to add
with any arguments executes normally, but the second and all subsequent calls with the same arguments pulls the value out of the cache. Pretty sweet.
Recursive calls are naturals for this, and the classic example is computing Fibonacci numbers. Here are two separate ways to memoize a fib
function. In the first, a variable is assigned to a closure, so internally the closure uses the call
method for recursion.
[sourcecode language=”groovy”]
def fib = { n ->
if (n < 2) 1
else call(n – 1) + call(n – 2)
}.memoize()
[/sourcecode]
Alternatively, the @Memoize
Abstract Syntax Tree (AST) transformation can be applied to a method to accomplish the same thing.
[sourcecode language=”groovy”]
@Memoized
long fib(long n) {
if (n < 2) 1
else fib(n – 1) + fib(n – 2)
}
[/sourcecode]
Either way, the result of each call with a particular value of n
is saved, so the recursive calls return almost immediately.
To demonstrate that I can use that from Java, I put the method in a Groovy class.
[sourcecode language=”groovy”]
import groovy.transform.Memoized
class AnnotatedFunctions {
@Memoized
BigInteger fib(BigInteger n) {
if (n < 2) 1
else fib(n – 1) + fib(n – 2)
}
@Memoized
BigInteger fact(BigInteger n) {
if (n < 2) 1
else n * fact(n – 1)
}
}
[/sourcecode]
In addition to the Fibonacci method fib
, I also have a recursive factorial computation, fact
. In Java, it’s easy enough to instantiate the Groovy class and invoke its methods directly. Here’s a snippet from the main
method of my Java class.
[sourcecode language=”java”]
AnnotatedFunctions mf = new AnnotatedFunctions();
IntStream.range(1, 100)
.forEach(i -> {
long before = System.nanoTime();
BigInteger val = mf.fib(new BigInteger(i + ""));
long after = System.nanoTime();
System.out.printf("%3d: %8s, fib(%2d) = %d%n", i,
(after – before) / 1e9, i, val);
});
IntStream.range(1, 100)
.forEach(i -> {
long before = System.nanoTime();
BigInteger val = mf.fact(new BigInteger(i + ""));
long after = System.nanoTime();
System.out.printf("%3d: %8s, fact(%2d) = %d%n", i,
(after – before) / 1e9, i, val);
});
[/sourcecode]
The output resembles:
[sourcecode language=”bash”]
1: 0.10599, fib( 1) = 1
2: 0.02197, fib( 2) = 2
3: 2.1E-4, fib( 3) = 3
4: 1.56E-4, fib( 4) = 5
5: 1.71E-4, fib( 5) = 8
6: 1.7E-4, fib( 6) = 13
// …
98: 1.19E-4, fib(98) = 218922995834555169026
99: 1.16E-4, fib(99) = 354224848179261915075
1: 2.74E-4, fact( 1) = 1
2: 0.002715, fact( 2) = 2
3: 1.34E-4, fact( 3) = 6
4: 1.31E-4, fact( 4) = 24
5: 8.1E-5, fact( 5) = 120
6: 1.41E-4, fact( 6) = 720
// …
98: 5.5E-5, fact(98) = 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000
99: 5.3E-5, fact(99) = 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000
[/sourcecode]
Pretty impressive, and trivial to do in Groovy even if you just want to call it from Java.
Another AST transform that comes up if the algorithm is right is @TailRecursive
. If you can express your algorithm in a way that the recursive call comes last, you can use that transform, which will also check that you did it properly.
I added the following method to my AnnotatedFunctions
class.
[sourcecode language=”groovy”]
import groovy.transform.TailRecursive
class AnnotatedFunctions {
// … other methods …
@TailRecursive
BigInteger factorial(BigInteger n, BigInteger acc = 1G) {
n < 2 ? acc : factorial(n – 1G, n * acc)
}
}
[/sourcecode]
I don’t normally write BigInteger
, since Groovy automatically uses it if the system requires that many digits, but when integrating with Java it helps to be explicit. I’m also taking advantage of Groovy’s ability to optionally initialize a method variable by assigning the acc
accumulator the value of 1.
Now I can call this from Java, too.
[sourcecode language=”java”]
AnnotatedFunctions mf = new AnnotatedFunctions();
// … other calls from before …
System.out.println("70000! has " +
mf.factorial(new BigInteger(70_000 + "")).toString().length() +
" digits");
[/sourcecode]
[This also takes advantage of the fact that starting in Java 7, you can embed underscores in numeric literals, like 70_000, for readability.]
The output is: 70000! has 308760 digits
.
Another cool AST transform is @Immutable
. Functional programming favors immutability, but it’s enormously difficult to make a Java class produce immutable objects. You have to remove all the setters, provide private final backing fields for properties, wrap collections in their unmodifiable equivalents, make the class final, and so on.
Or, you can just use the @Immutable
annotation, which does all that and more for you. Here’s an immutable point class.
[sourcecode language=”groovy”]
import groovy.transform.Immutable
@Immutable
class Point {
double x
double y
}
[/sourcecode]
Here is a Spock test (aside — my other JavaOne talk is on Spock testing) that demonstrates its capabilities.
[sourcecode language=”groovy”]
import spock.lang.Specification
class PointSpec extends Specification {
def ‘tuple constructor works'() {
expect: new Point(3, 4)
}
def "can’t change x"() {
given:
Point p = new Point(1, 2)
when:
p.x = 5
then:
thrown(ReadOnlyPropertyException)
}
def "can’t change y"() {
given:
Point p = new Point(1, 2)
when:
p.y = 5
then:
thrown(ReadOnlyPropertyException)
}
}
[/sourcecode]
I couldn’t really leave that alone, so I added a few more methods.
[sourcecode language=”groovy”]
import groovy.transform.Immutable
@Immutable
class Point {
double x
double y
static Point createPoint(double x, double y) {
new Point(x, y)
}
Point translate(double dx = 0, double dy = 0) {
new Point(x + dx, y + dy)
}
Point rotate(double radians) {
double r = Math.sqrt(x * x + y + y)
new Point(r * Math.cos(radians), r * Math.sin(radians))
}
Point plus(Point p) {
new Point(x + p.x, y + p.y)
}
Point minus(Point p) {
new Point(x – p.x, y – p.y)
}
}
[/sourcecode]
The translate
and rotate
methods produce new points that are the result of moving or rotating the original point. I also added a plus
and a minus
method to take advantage of operator overloading. The corresponding tests are:
[sourcecode language=”groovy”]
import spock.lang.Specification
class PointSpec extends Specification {
// … other tests …
def "can translate"() {
given:
Point start = new Point(1, 0)
Point end = new Point(3, 3)
when:
Point p = start.translate(2, 3)
then:
assert (p.x – end.x).abs() < 1e-10
assert (p.y – end.y).abs() < 1e-10
}
def "can rotate 90 deg"() {
given:
Point p = new Point(1, 0)
when:
p = p.rotate(Math.PI / 2)
then:
p.x.abs() < 1e-10
p.y == 1
}
def "can rotate 180 deg"() {
given:
Point p = new Point(1, 0)
when:
p = p.rotate(Math.PI)
then:
p.x == -1
p.y.abs() < 1e-10
}
def "overloaded plus"() {
given:
Point p1 = new Point(1, 2)
Point p2 = new Point(3, 4)
when:
Point p = p1 + p2
then:
p.x == 4
p.y == 6
}
def "overloaded minus"() {
given:
Point p1 = new Point(1, 2)
Point p2 = new Point(3, 4)
when:
Point p = p1 – p2
then:
p.x == -2
p.y == -2
}
}
[/sourcecode]
The only nuisance is that because I’m planning to integrate with Java, I’m using doubles rather than BigDecimal
s, and that means the precision of zero isn’t quite what I need. Everything works, though, including the tuple constructor.
That’s significant, because I ran into a problem when trying to call this from Java.
[sourcecode language=”java”]
public class UsePoint {
public static void main(String[] args) {
// Point p = new Point(1, 0) // doesn’t work (aww)
Point p = Point.createPoint(1, 0);
System.out.println(p);
System.out.printf("(%s,%s)%n", p.getX(), p.getY());
Point p1 = p.translate(2, 3);
System.out.println(p1); // should be (3,3)
Point p2 = p.rotate(Math.PI / 2);
System.out.println(p2); // should be (0,1)
}
}
[/sourcecode]
Even though the AST transform generates a tuple constructor for Point
, the Java code apparently is compiled too soon to see it. I was forced to add a createPoint
method to Point
in order to instantiate the class.
The rest works, though. I can invoke translate
or rotate
without a problem. The plus
and minus
methods don’t help Java much, since they’re there just for the Groovy operator overloading. Of course, there are no setters (no setX
or setY
methods) available, so I don’t have an issue with Java trying to call them.
I’m going to talk about streams, lambdas, and method references, too, but this post has enough in it for now. I’ll show that stuff in my next post. Besides, that’ll give me another chance to “encourage” Baruch in the Thunderdome.
Leave a Reply