7

Paul Graham, in his great article Revenge of the Nerds, claimed that languages vary in power. He mentioned a nice exercise - writing an accumulator generator:

We want to write a function that generates accumulators-- a function that takes a number n, and returns a function that takes another number i and returns n incremented by i.

Solution in Java is

public class Accumulator {

    public interface Inttoint {
        public int call(int i);
    }

    public static Inttoint foo(final int n) {
        return new Inttoint() {
            int s = n;
            public int call(int i) {
                s = s + i;
                return s;
            }};
    }

    public static void main(String... args) {
        Inttoint accumulator = foo(1);

        System.out.println(accumulator.call(2) == 3);
        System.out.println(accumulator.call(3) == 6);
    }

}

I am curious, whether in Java 8 (thanks to lambda) is already some elegant way how to write it similarly to Groovy, see below. I tried Function<Integer, Integer>

But I stuck with this compiler error.

local variables referenced from a lambda expression must be final or effectively final

So do you have some Java 8 solution?

Compare the old Java solution with the Groovy one

def foo(n) {
    return {n += it}
}

def accumulator = foo(1)
assert accumulator(2) == 3
assert accumulator(3) == 6
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
banterCZ
  • 1,551
  • 1
  • 22
  • 37

1 Answers1

8

First of all, you can still use all the new Java 8 interfaces using anonymous class syntax instead of lambda syntax. For example:

import java.util.function.IntUnaryOperator;

public class Accumulator {
    public static IntUnaryOperator foo(int n) {
        return new IntUnaryOperator() {
            private int value = n;
            @Override
            public int applyAsInt(int i) {
                return value += i;
            }
        };
    }

    public static void main(String... args) {
        IntUnaryOperator accumulator = foo(1);
        System.out.println(accumulator.applyAsInt(2)); // output: 3
        System.out.println(accumulator.applyAsInt(3)); // output: 6
    }
}

(Rather than Function, I used IntUnaryOperator here as it allows the use of primitive ints instead of boxed Integers. It's logically equivalent to Function<int,int>, if that were legal.)

Now, how can we shorten this bulky thing with lambda syntax? Local variables passed into lambdas are required to be (effectively) final. The limitation means you cannot trivially write a variable whose value accumulates between calls. The following does not work:

public static IntUnaryOperator foo(int n) {
    return i -> n += i; // nope, sorry!
}

We can work around the limitation by using some mutable object as a holder for the current accumulator value. A one-element array can be used for this. The array variable is not changing – only the contents of the array object it points at are changing, so the array variable is effectively final and this is allowed:

public static IntUnaryOperator foo(int n) {
    int[] value = new int[] { n };
    return i -> value[0] += i;
}

Any object with a mutable field can potentially be used as a holder. As suggested below by @andersschuller, an AtomicInteger fits well here, and makes the returned functions thread-safe:

public static IntUnaryOperator foo(int n) {
    AtomicInteger value = new AtomicInteger(n);
    return i -> value.addAndGet(i);
}

And @srborlongan points out this can be re-written using a method reference, which is even shorter (though not more readable):

public static IntUnaryOperator foo(int n) {
    return new AtomicInteger(n)::addAndGet;
}
Boann
  • 48,794
  • 16
  • 117
  • 146
  • 1
    +1 Just curious, what is the purpose of using an array to (as far as I can see) make a copy of n? – Stefano Sanfilippo Jun 06 '14 at 13:49
  • 7
    I would probably suggest replacing the array with an [`AtomicInteger`](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html), or maybe even the [`LongAdder`](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html) added in Java 8. – andersschuller Jun 06 '14 at 13:50
  • 1
    @Boann It works, but I am disappointed that there is not more elegant way. – banterCZ Jun 06 '14 at 13:52
  • 2
    @StefanoSanfilippo Because you cannot change n in the lambda. The variable must be (effectively) final. But you can make a final variable point at some object, such as an array, which has non-final elements or fields. – Boann Jun 06 '14 at 13:53
  • @Boann Maybe add an example of using your `Accumulator` in a stream, for example `IntStream.rangeClosed (1,3).forEach (accumulator::apply)`? Up to you. – sparc_spread Jun 06 '14 at 14:41
  • 3
    `foo(int)` may also be simply defined as `return new AtomicInteger(n)::addAndGet;`, though I am sure that such code may not be as readable as the original (which does the same thing: create an AtomicInteger instance from the given integer, then encapsulate the instance's addAndGet method (possibly as a Function or UnaryOperator or IntUnaryOperator)). – srborlongan Jun 07 '14 at 01:49
  • @srborlongan Woah amazing! – Boann Jun 07 '14 at 03:10