How well can Java Lambdas serve as nested functions?
#Nested Functions This article will explore how closely Java lambdas can replicate the functionality of Javascript nested functions. Not all programing languages support nested functions at all, and so as a programmer you could go a long time without seeing them, but for some problems they can be helpful. If you have a function which logically consists of two different sub-operations, which themselves do not make any sense outside of the context of the overall function then writing them as nested sub-functions can improve readability as opposed to, say, writing them as class/object level private functions. An example of this would be the split and merge operations of a Merge sort, or the partition and swap operations of Quicksort. For this article I’ll use a different example because its one which I enjoyed writing so much.
#The Javascript Code This is a function I wrote some time ago for converting numbers (conventionally represented in Arabic notation) to Roman numerals. This Javascript code isn’t the only or even necessarily the best way to do it but I liked it because it took advantage of several Javascript language features:
- Nested functions for clarity
- Closure (access to a function’s local variable outside of its immediate lexical scope)
- Multiple return values from a function
Here is the Javascript function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var toRoman = function(input) {
// these numbers can be converted into single Roman "digits
var toArabic = [ 100, 50, 10, 5, 1 ],
// ...and these are the corresponding Roman characters for them
fromRoman = [ 'C', 'L', 'X', 'V', 'I' ],
i = 0, j, nextVal, output = '';
while (input > 0) {
nextVal = nextRomanFor(input);
output += nextVal['char'];
input -= nextVal['num'];
}
return output;
function nextRomanFor(num) {
var nextNumber;
for (; i < toArabic.length; i++) {
nextNumber = toArabic[i];
// return tuple of Roman-plus-Arabic-equivalent
if (nextNumber <= num) {
return {
'char' : fromRoman[i],
'num' : nextNumber
};
}
// now look for a subtractable combination w/this number
for (j = i + 1; j < toArabic.length; j++) {
if (isSubtractable(toArabic[i], toArabic[j])) {
nextNumber = toArabic[i] - toArabic[j];
if (nextNumber <= num) {
return {
'char' : fromRoman[j] + fromRoman[i],
'num' : nextNumber
};
}
// if 1st subtractable isn't smaller, can continue
break;
}
}
}
}
function isSubtractable(num1, num2) {
var d;
// num2 is subtractable from num1 if is smaller & a power of 10
if (num1 > num2) {
// obtain log10(num2)
d = Math.log(num2) / Math.log(10);
// if is a whole number, is true
return Math.floor(d) === d;
}
// got here, is false
return false;
}
}
Let’s see how close Java can come to achieving the same things with the addition of Lambda syntax!
#Java Lambdas
Unlike Javascript, of course Java doesn’t have top-level functions; all functions must be associated with a class (static methods) or the instances of a class (object methods). As such, there’s no direct analog for this type of construct; traditionally the closest we could come would be to use a class wrapper around a function, with a lot of unwanted verbosity. However, as of JDK 1.8, we have a Lambda construct which can work in a similar fashion to a nested function (although as we will see later, the pre-existing baggage of a wrapper class is still there although hidden at development time). A lambda is declared using the following syntax:
This would be analogous to creating a Javascript function using a variable-assigned function:
A consequence of this is that the variable can’t be used until it is assigned, and so the helper functions need to appear at the top of the method, before the code which uses them. This isn’t a severe shortcoming; Javascript is almost alone in allowing this type of construction (Haskell is the only other one I know of. Scala technically allows this construction but in practice you can’t really use it).
#First Try
The closest and most straightforward implementation of this algorithm in Java would look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public static String toRoman(int input) {
// running count of how much we have left
int remainder = input, i = 0;
StringBuffer output = new StringBuffer();
// num2 is subtractable from num1 if is smaller & a power of 10
BiPredicate<Integer, Integer> isSubtractable = (num1, num2) -> {
if (num1 > num2) {
double d = Math.log(num2) / Math.log(10);
return (Math.floor(d)) == d;
}
return false;
};
// In Java, to return multiple values must use array or collection
Function<Integer, Object[]> nextRomanFor = (num) -> {
int nextNumber;
for (; i < intToRoman.length; i++) {
nextNumber = intToRoman[i];
if (nextNumber <= remainder) {
return new Object[] { romanFromInt[i], nextNumber };
} else {
// try to find a subtractable combination to return
for (int j = i + 1; j < intToRoman.length; j++) {
if (isSubtractable.test(intToRoman[i], intToRoman[j])) {
nextNumber = intToRoman[i] - intToRoman[j];
if (nextNumber <= num) {
return new Object[] {
romanFromInt[j] + romanFromInt[i],
nextNumber };
}
// if the largest subtractable number doesn't work,
// we can exit loop
break;
}
}
}
}
// should never happen!
return null;
};
while (remainder > 0) {
// 1st return val is Roman numeral, 2nd is corresponding int value
Object[] returnVal = nextRomanFor.apply(remainder);
output.append((String) returnVal[0]);
remainder -= (Integer) returnVal[1];
}
return output.toString();
}
Not so bad; the line count of the function is actually smaller than the Javascript version! Of course with Java we’ll have the additional overhead of the method signature, but still it will be close. Unfortunately this code won’t compile:
The reason for this is that lambdas in Java are implemented by, at compile time, generating anonymous classes implementing predefined Functional Interfaces. As a consequence, in order for the local variables (in this case remainder and output) to be accessible inside of the lambda expressions they must be marked final:
1
2
final int remainder = input, i = 0;
final StringBuffer output = new StringBuffer();
But now our method won’t compile for another reason:
This is because the final modifier not only makes our variables accessible to the lambda function it also becomes, well, final and so it can’t be assigned. Furthermore in Java an int is a primitive type and can’t have its value modified (and its corresponding type Integer is immutable and final). So what to do?
#Array as a primitive-by-reference
One solution is to use a trick that Java programmers have long used when they’ve needed pass-by-reference behavior for an argument to a method with a primitive value, namely to use a single-element array reference, and change the rest of the code as appropriate e.g.:
1
final int[] remainder = { input }, i = {0};
1
for (; i[0] < intToRoman.length; i[0]++){
1
remainder[0] -= (Integer) returnVal[1];
This makes the code a bit ugly but works just fine.
#Mutable primitve wrapper
Another alternative would be to use a mutable reference with an int value like AtomicInteger from java.util.concurrent. Again this is doable but it is rather verbose, for example:
1
final AtomicInteger remainder = new AtomicInteger(input), i = new AtomicInteger(0);
1
for (; i.get() < intToRoman.length; i.incrementAndGet()) {
1
remainder.set( remainder.get() - (Integer) returnVal[1]);
#Java Function vs Consumer
Another difference between the Java and Javascript code is in the return value from the nextRomanFor() function: in Javascript the function returns an associative array in the form of an object created on-the fly. Because of Java’s strong typing and lack of tuple syntax there’s no analogous approach; what we can do is either return an array of Object[], as we did in example1, or create/appropriate another type for the multiple return values (many developers use java.util.Map.Entry as a two-item pseudo-tuple). In this case there is another solution, which is to simply remove the return value for the function and do the assignment directly in nextRomanFor():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Consumer<Integer> nextRomanFor = (num) -> {
int nextNumber;
for (; i[0] < intToRoman.length; i[0]++) {
nextNumber = intToRoman[i[0]];
if (nextNumber <= remainder[0]) {
output.append(romanFromInt[i[0]]);
remainder[0] -= nextNumber;
} else {
// try to find a subtractable combination to return
for (int j = i[0] + 1; j < intToRoman.length; j++) {
if (isSubtractable.test(intToRoman[i[0]], intToRoman[j])) {
// compound roman number, large value on right minus
// small value on left
nextNumber = intToRoman[i[0]] - intToRoman[j];
if (nextNumber <= num) {
output.append(romanFromInt[j] + romanFromInt[i[0]]);
remainder[0] -= nextNumber;
}
// if the largest subtractable number doesn't work,
// we can exit loop
break;
}
}
}
}
};
while (remainder[0] > 0) {
// 1st return val is Roman numeral, 2nd is corresponding int value
nextRomanFor.accept(remainder[0]);
}
Note that to do this we have to:
- Change the type of the lambda expression from Function to Consumer (a Consumer is a lambda with no return value)
- Change the code which calls the lambda expression to accept() instead of apply()
#Conclusion
Overall, Java Lambda expressions can approximate the functionality of what true nested functions offer in a language like Javascript, however in many scenarios the shoe-horned nature of the solution becomes apparent. This example (at least to my eye and experience) results in awkward looking and unintuitive code.
These patterns will make sense to an experienced Java developer who understands what the underlying class representation of the code will be and won’t present a problem for them. However in terms of attracting and gaining mind share among junior developers they represent yet another hurdle to overcome. Java is no longer a small or simple language, and developers want to spend their time developing application code and not on learning patterns forced upon them by the idiosyncrasies of a platform.
For the future viability of Java these considerations are minor compared to the question of how the new syntax affects application development - particularly web and network applications based on the increasingly popular asynchronous I/O model. I’ll explore this in a later article!