The power of Tuples

Sergiy Yevtushenko - Oct 23 '19 - - Dev Community

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));

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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));

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

The method above will wait for resolution of 3 Promise's and then return all values in single Tuple with 3 elements.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .