nSource is a small, stream-like, fully featured and functional library to chain computation stages on top of regular Java Collections. This library is intended as a case of study for lazy design and to demonstrate how laziness is an important part when building performant software.
<dependency>
<groupId>com.github.anicolaspp</groupId>
<artifactId>nSource</artifactId>
<version>1.0.1</version>
</dependency>
nSource has a few interesting characteristics.
- it is lazy, until the last materialization stage.
- its API is functional, so we can use declarative flow control.
- it has a familiar API, by using stream-like naming.
- It is optimized, so it operates on the minimum and only necessary elements of the underlying data source.
nSource allows us to do the following.
RunnableStage<List<String>> s = nsource
.from(getNumbers())
.filter(x-> x % 2 == 0)
.map(x-> x.toString())
.toList();
List<String> f = s.run();
Notice that nothing happens until we call .run()
of the corresponding RunnableStage<>
.
As we can see, this library can be seen in a similar way to what Java Streams
offer and even though the intention is not to replace Streams, the library shows how the inception of lazy desing is a building blog when building smart components that need to present good performance.
The main component of the nSource is ComposableStage<>
and the followings are some of the present combinators.
public <B> ComposableStage<B> map(Function<A, B> fn)
public ComposableStage<A> filter(Predicate<A> predicate)
public ComposableStage<A> take(int n)
public ComposableStage<A> takeWhile(Predicate<A> predicate)
public RunnableStage<List<A>> toList()
public <B> RunnableStage<B> foldLeft(B zero, BiFunction<A, B, B> biFunction)
public RunnableStage<Done> forEach(Consumer<A> consumer)
public RunnableStage<Optional<A>> first()
public RunnableStage<A> firstOrDefault(Supplier<A> defaultValue)
This is NOT the entire list
As we can appreciate, it shares a lot with Java Streams
but it holds on laziness as much as it can.
nSource ComposableStage<>
are meant to be used for chaining operations. However, once we get a RunnableStage<>
we should not reuse the corresponding ComposableStage<>
.
String name = ....
RunnableStage<Integer> sum = nSource
.from(getValues(...))
.filter(v -> v.name.equals(name))
.map(v -> v.getAge())
.foldLeft(0, (a, b) -> a + b);
assert sum.run() == sum.run()
In here, the computations happens only once (the first time we call .run()
and the second time the value is reused internally.
On the other hand, the following might cause a source reuse, and that is not allowed.
ComposableStage<List<Integer>> stage = nSource
.from(getValues(...))
.filter(v -> v.name.equals(name))
.map(v -> v.getAge());
RunnableStage<Integer> sum = stage.foldLeft(0, (a, b) -> a + b);
RunnableStage<Done> printThem = stage.forEach(System.out::println);
This is perfectly valid since nothing has been executed just yet, but then we should only be able to materialize exactly one of them (the first one to call .run()
.
printThem.run();
...
int sumValue = sum.run(); // MaterializationException thrown here.
The first one to be materialized wins.
This library is not intended to be used in production, but to present implementation details on lazy and smart design/implementation.