I have implemented Python-style generators within Java. I might be reinventing the wheel here, but I feel that the ability to define a Generator with an anonymous class is the most flexible approach. Here's the relevant code:
generator/Generator.java
public abstract class Generator<T> implements Iterable<T>, Iterator<T>
{
private Lock lock = null;
private Lock lock2 = null;
private Semaphore semaphore = null;
private T curr;
private Thread execution;
private Runnable onTermination = null;
private Consumer<? super T> forEachAction = null;
public Generator(Object... params)
{
execution = new Thread(() ->
{
try
{
T t = get(params);
onTermination = ThrowingRunnable.of(execution::join);
yield(t);
getPermit();
}
catch(Exception unexpected)
{
onTermination = ThrowingRunnable.of(() ->
{
Exception e = new NoSuchElementException("Failed to retrieve element!");
e.initCause(unexpected);
throw e;
});
semaphore.release();
}
});
execution.setDaemon(true);
}
@Override
public final Iterator<T> iterator()
{
return this;
}
@Override
public final boolean hasNext()
{
return onTermination == null;
}
@Override
public final T next()
{
if(!hasNext())
throw new NoSuchElementException(
"There are no more elements to be generated by this Generator!");
if(semaphore == null && lock == null)
{
lock = new ReentrantLock();
lock2 = new ReentrantLock();
lock.lock();
semaphore = new Semaphore(0);
execution.start();
getPermit();
return curr;
}
lock2.lock();
lock.unlock();
getPermit();
lock.lock();
lock2.unlock();
getPermit();
if(onTermination != null)
{
lock.unlock();
onTermination.run();
}
return curr;
}
protected final void yield(T t)
{
if(forEachAction != null)
{
forEachAction.accept(t);
return;
}
curr = t;
semaphore.release();
lock.lock();
lock.unlock();
semaphore.release();
lock2.lock();
lock2.unlock();
}
private final void getPermit()
{
try
{
if(semaphore != null)
semaphore.acquire();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
/**
* Consumes all remaining elements possible. Obviously, don't use on
* infinite Generators.
*/
@Override
public void forEach(Consumer<? super T> action)
{
Objects.requireNonNull(action);
if(!hasNext())
throw new IllegalStateException("Exhausted elements before calling forEach!");
forEachAction = action;
if(execution.isAlive())
{
lock.unlock();
}
else
{
try
{
execution.start();
}
catch(IllegalThreadStateException itse)
{
itse.initCause(// double checking
new IllegalStateException("Can't exhaust elements and then call forEach!"));
throw itse;
}
}
ThrowingRunnable.of(execution::join).run();
onTermination.run();
}
protected abstract T get(Object... objs);
}
This is the code that I use for ignoring compile-time exceptions from lambdas (which should be thrown at runtime, with the default handler).
throwing/ThrowingRunnable.java
@FunctionalInterface
public interface ThrowingRunnable extends Runnable, ExceptionFlowController
{
public abstract void run_() throws Exception;
@Override
default void run()
{
try
{
run_();
}
catch (Exception e)
{
handle(e);
}
}
static Runnable of(ThrowingRunnable tr, Consumer<Exception> h)
{
return new ThrowingRunnable()
{
public void run_() throws Exception
{
tr.run_();
}
public void handle(Exception e)
{
h.accept(e);
}
};
}
static Runnable of(ThrowingRunnable tr)
{
return tr;
}
}
throwing/ExceptionFlowController.java
/**
* Controls exception flow by piping it into a handler.
*/
public interface ExceptionFlowController
{
public default void handle(Exception e)
{
ThrowingUtil.raise(e);
}
}
throwing/ThrowingUtil.java
public class ThrowingUtil
{
@SuppressWarnings("unchecked")
static <E extends Exception> void raise(Exception e) throws E
{
throw (E) e;// sneakyThrow if you google it, restricted to exceptions only
}
}
Here's an example of using a Generator to print the first 92 Fibonacci numbers (until 64 bits is no longer enough):
Main.java
public class Main
{
public static void main(String[] args)
{
Generator<Number> g = new Generator<>()
{
public Number get(Object[] o)
{
return get(0, 1);
}
private Number get(long fib0, long fib1)
{
yield(fib0);
fib0 += fib1;
yield(fib1);
fib1 += fib0;
if(fib0 < 0 || fib1 < 0)
return null;
return get(fib0, fib1);
}
};
StreamSupport.stream(g.spliterator(),false)
.takeWhile(Objects::nonNull)
.forEach(System.out::println);
}
}
Output:
0
1
1
2
...
4660046610375530309
I was a bit disappointed with the amount of concurrent primitive vomit that I had to use in order to ensure Generators were synchronized properly. Keeping this in mind, here's what I'd like to know:
- Any generic code quality suggestions/opinions/revisions.
- How can I cut down on the number of Locks and Semaphores/usages of Locks and Semaphores (maybe using well-named condition variables)?
Edit:
Here's an example where using a Generator is massively convenient compared to creating a stateful Supplier/Iterator to do the same thing:
public static void main(String[] args)
{
// 0
// 1 2
// _ 6 3 4
//_ _ 8 9 5 _ 7 _
Node n0 = new Node(), n1 = new Node(), n2 = new Node(),
n3 = new Node(), n4 = new Node(), n5 = new Node(),
n6 = new Node(), n7 = new Node(), n8 = new Node(),
n9 = new Node();
n0.left = n1;
n0.right = n2;
n2.left = n3;
n2.right = n4;
n3.left = n5;
n1.right = n6;
n4.left = n7;
n6.left = n8;
n6.right = n9;
Generator<Node> g = new Generator<>()
{
public Node get(Object[] o)
{
return get(n0);
}
private Node get(Node n)
{
if(n.left != null)
get(n.left);
yield(n);
if(n.right != null)
get(n.right);
return null;
}
};
Generator<Node> rightMost7Nodes = new Generator<Node>()
{
int count = 0;
int target = 7;
public Node get(Object[] o)
{
return get(n0);
}
private Node get(Node n)
{
if(n.right != null)
get(n.right);
if(count++ >= target)
return null;
yield(n);
if(n.left != null)
get(n.left);
return null;
}
};
System.out.println("Nodes in-order:");
StreamSupport.stream(g.spliterator(),false)
.takeWhile(o -> g.hasNext()) //ignore last element
.forEach(System.out::println);
System.out.println("Right-most 7 nodes in reverse order:");
StreamSupport.stream(rightMost7Nodes.spliterator(),false)
.takeWhile(o -> g.hasNext()) //ignore last element
.forEach(System.out::println);
}
Output:
Nodes in-order:
Node(1)
Node(8)
Node(6)
...
Node(2)
Node(7)
Node(4)
Right-most 7 nodes in reverse order:
Node(4)
Node(7)
Node(2)
Node(3)
Node(5)
Node(0)
Node(9)
The flexibility provided by being able to yield
mid-logic makes it extremely simple for the programmer to modify the ordering and stored state during stream creation. If the same were to be done with a Supplier/Iterator, the need to correctly modify stored state during the traversal could be unnecessarily complex (using Morris Traversal or a stack-based approach is fine for full iteration, but can get complicated when you stop midway). Furthermore, the code would be inflexible to modify for other types of traversals (pre-order/post-order). For this reason, I plan to use my Generator implementation relatively frequently - which is why I'd like for it to be reviewed as per questions 1 and 2 :)
Stream.generate()
which seems a good deal simpler than your implementation: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/… \$\endgroup\$yield
statements will pause a function, save all state (context), and continue from the lastyield
on successive calls tonext()
.Stream::generate
is insufficiently powerful for my desired use cases, as it provides only an infinite, unordered stream of elements, whereas my Generator intends to provide (in/)finite, ordered, stateful stream of elements. According to Stream's Javadoc: behavioral parameters "in most cases must be stateless (their result should not depend on any state that might change ...)". \$\endgroup\$next()
to switch to the Generator'sget
logic until such time as the Generatoryield
s orreturns
, at which point control flow should resume in the thread that callednext()
with the correct return value. This sounds extremely similar to a context switch to me, which is why I used threads. \$\endgroup\$