Generator Functions
Description:
In this kata, you'll become a functional programmer! You'll be implementing JavaScript-like generator functions in Java.
As a Java programmer, you might now be asking, "what's a generator function?". In essence, a generator function is simply a function whose execution can be paused at certain times. This pause is caused by the yield
statement, which is similar to return
in that it gives a value back to the caller. The yield statement, however, instead of exiting the function, causes it to suspend until the next time a value is requested. For example, this is what a generator function that returns the counting numbers might look like in JavaScript:
function* countingNumbers() {
let i = 0;
while (true) {
yield i++;
}
}
Fairly counterintuitive, right? Why would a programmer ever purposefully enter an infinite loop with no means of breaking? In reality, this is a perfectly sane algorithm; think of it like an iterator of sorts that begets an arbitrarily large subset of the counting numbers. Let's look at that in action, once again in JavaScript:
let generator = countingNumbers();
console.log(generator.next()); // 0
console.log(generator.next()); // 1
console.log(generator.next()); // 2
What's really going on here? When we call countingNumbers()
in that first line, we create a generator object by calling the generator function. Execution hasn't begun yet! Then, once we call generator.next()
, the function runs until it reaches the first yield
, at which point it suspends and returns the yielded value to the caller of next()
. The next time we call next()
, the function will unsuspend and once again run until the next yield
. In this way, we have a sort of lazy supplier of the counting numbers.
JavaScript, for some reason, also has a yield*
, or yield-from statement. When this is called with another generator function as a parameter, the currently running generator function begins delegating all calls to its next()
function to the generator generated from the yield*
statement. Let's see this in action:
function* threeFourFive() {
yield 3;
yield 4;
yield 5;
}
function* oneThroughSeven() {
yield 1;
yield 2;
yield* threeFourFive();
yield 6;
yield 7;
}
let generator = oneThroughSeven();
for (let i = 0; i < 7; i++) {
console.log(generator.next()); // prints the numbers 1..7
}
Wow! Magical!
You can also pass parameters to next()
, and this will cause the yield
statement to take on that value. To demonstrate, here's a generator function that sums up the numbers passed to it:
function* summation(initialValue) {
let total = initialValue;
while (true) {
total += yield total;
}
}
When we call this generator's next()
function for the first time, the parameter we pass to next()
is passed to the function in the form of the argument initialValue
. We can pass it more numbers in subsequent next()
calls, which will then be added to total
:
let gen = summation();
console.log(gen.next(10)); // 10
console.log(gen.next(42)); // 52
console.log(gen.next(17)); // 69
console.log(gen.next(638)); // 707
...and so on.
Now here's your task: you'll have to implement a GeneratorFunction<I, O>
class that performs the same function as a JavaScript generator function using some Java sorcery! For example, here's what the above summation function will look like when you're done:
GeneratorFunction<Integer, Integer> summation = new GeneratorFunction<>(initialValue -> {
int total = initialValue;
while (true) {
total += Flow.<Integer, Integer>yield(total);
}
});
Generator<Integer, Integer> gen = summation.call();
System.out.println(gen.next(10)); // 10
System.out.println(gen.next(42)); // 52
System.out.println(gen.next(17)); // 69
System.out.println(gen.next(638)); // 707
...and so on. You'll also have to implement yield-from, which will look like this:
GeneratorFunction<Void, Integer> threeFourFive = new GeneratorFunction<>(() -> {
Flow.yield(3);
Flow.yield(4);
Flow.yield(5);
});
GeneratorFunction<Void, Integer> oneThroughSeven = new GeneratorFunction<>(() -> {
Flow.yield(1);
Flow.yield(2);
Flow.yieldFrom(threeFourFive);
Flow.yield(6);
Flow.yield(7);
});
Generator<Void, Integer> gen = oneThroughSeven.call();
for (int i = 0; i < 7; i++) {
System.out.println(gen.next()); // prints the numbers 1..7
}
If Generator#next()
is called when no data is left to be yielded (i.e. the function has finished executing and no data is buffered for yielding), null
should be returned. Additionally, a Generator#done() -> boolean
function should be written such that it returns whether the function has finished executing yet or not. Note that generators might also yield null
as a value! In that case, you'll have to use done()
to distinguish between generators that have yielded null
and generators that are done.
JavaDoc comments exist in the initial solution to help you a bit. Good luck!
(Note: generator functions as presented in this problem slightly diverge from generator functions in actual JS, but it's a little simpler this way.)
Similar Kata:
Stats:
Created | Oct 18, 2017 |
Published | Oct 19, 2017 |
Warriors Trained | 430 |
Total Skips | 102 |
Total Code Submissions | 167 |
Total Times Completed | 24 |
Java Completions | 24 |
Total Stars | 24 |
% of votes with a positive feedback rating | 96% of 12 |
Total "Very Satisfied" Votes | 11 |
Total "Somewhat Satisfied" Votes | 1 |
Total "Not Satisfied" Votes | 0 |
Total Rank Assessments | 8 |
Average Assessed Rank | 2 kyu |
Highest Assessed Rank | 1 kyu |
Lowest Assessed Rank | 3 kyu |