I'm used to believing that functions in Java are always applicatively evaluated, that is, all the function arguments are evaluated before being applied to the function; until today when I was playing with prime numbers and wrote this function to generate an infinite sequence of prime numbers:
public static IntStream primes() { final IntPredicate isPrime = (n) -> { final int isqrt = (int)Math.sqrt(n); return primes().takeWhile(i -> i <= isqrt).allMatch(i -> n % i != 0); }; return IntStream.concat( IntStream.of(2), IntStream.iterate(3, i -> i + 2).filter(isPrime)); } I expected the program to throw a StackOverflowError when primes() is called, with the following understanding:
- Evaluating return statement of
primes()- Evaluating
IntStream.concat(...)- Argument
IntStream.iterate(3, i -> i + 2).filter(isPrime)must be evaluated before being applied toIntStream.concat - Testing
isPrimeon 3isqrtevaluated as1- Evaluating return statement of
isPrimeCalling
primes()Evaluating return statement of
primes()...
- Argument
- Evaluating
And eventually lead to StackOverflowError.
Whereas the program actually runs and does produce an infinite sequence of prime numbers. What's wrong with my thinking, or is IntStream.concat actually lazily evaluated?