All Java engineers know about classes, in particular about POJO.
We writing POJO's every time we need some set of related named variables.
Tuple
is a data structure which is very similar to Java POJO - it contains few variables with respective types. The difference is that Tuple
contains unnamed values unlike POJO. Tuple
also similar to arrays, except that elements may have different types and properly implemented Tuple
must preserve those types.
Tuple
is often implemented as a Monad, i.e. there is no accessors and therefore there is no way to get single element. In fact there is no big sense to have accessors for Tuple
. Since elements have no names, accessors would operate with ordinals of elements, which is error-prone and inconvenient.
Fortunately Monad
-based implementation of Tuple
allows to provide convenient way to access all elements at once (and even give those elements arbitrary names) - map()
method which accepts lambda with number of parameters equal to Tuple
size.
Provided below small example shows how this looks like:
final var tuple = Tuple.with(10, "some string", UUID.randomUUID());
tuple.map((integer, string, uuid) -> System.out.printf("Received: %d, %s, %s", integer, string, uuid));
The lambda invoked by map()
has access to all components at once and can produce arbitrary result. Note that if there is an POJO, which has all-args constructor with matching parameter order, then Tuple
can be converted to POJO in one step:
public class SimplePojo {
private final Integer intValue;
private final String stringValue;
private final UUID uuidValue;
public SimplePojo(final Integer intValue, final String stringValue, final UUID uuidValue) {
...
}
...
}
...
final var tuple = Tuple.with(10, "some string", UUID.randomUUID());
final var pojo = tuple.map(SimplePojo::new);
Of course, factory method will work too.
The Tuple
is convenient in a number of areas, especially those where structure of the complex piece of data can't be defined upfront. In particular this is true for general purpose API's.
Another area is returning several values from the method call. Here Tuple
is especially convenient - map()
enables destructuring very similar to languages which have this feature built in:
...
Tuple3<Integer, String, UUID> parseParameters(final Request request);
...
Result<ResponsePojo> serveRequest(final Integer count, final String message, final UUID userId);
...
return parseRequest(request)
.map((count, message, uuid) -> serveRequest(count, message, uuid));
The Reactive Toolbox Core uses Tuple
in cases when several results should be grouped together, for example when there is a need to return result of waiting of several Promise
's to be resolved:
<T1, T2, T3> Promise<Tuple3<T1, T2, T3>> all(final Promise<T1> promise1,
final Promise<T2> promise2,
final Promise<T3> promise3);
The method above will wait for resolution of 3 Promise
's and then return all values in single Tuple
with 3 elements.