Tuesday, October 27, 2015

Approximating tuples in Java

What is a tuple? It's an ordered collection of individually typed elements. Wikipedia has a complicated explanation that comes down to the same thing in the context of ordinary programming. The Python implementation of tuples is a good example.

There are many related concepts, such as:

Arrays and lists
Ordered but all elements are the same type
Ordered dictionaries (maps)
Ordered but all values are of the same type, and they are named
Aggregates (structs, unions)
Individually typed but named and unordered

The closest matches to tuples in current Java are "structs" (more below) and arrays or lists, each with drawbacks. For most use of tuples, arrays and lists are non-starters: elements are all of the same nominal type. There is a lot of thought around how to do this best in Java.

What do I mean by saying Java has "structs" ala "C"? The vast bulk of Java code hides fields away with private, but exposes them with getter methods (and setters when non-final). This is the "Java Bean" anti-pattern. An alternative is to simply expose fields directly:

public final class CartesianPoint {
    public final int x;
    public final int y;

    public CartesianPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

This is almost a tuple. However:

  • CartesianPoint extends java.lang.Object but should not be an object in the OOP sense
  • Elements are accessed by name, e.g., point.x, rather than position
  • Elements are unordered—you cannot write a general function to process the first and second elements of the tuple without knowing 1) the struct type and 2) the names of the fields

But for many use cases this can be close enough, for example as a return type to simulate multiple value return. Hopefully Java 10 brings value types which resolves the java.lang.Object issue. This may even bring true tuples!

This example can be improved with Lombok:

@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "valueOf")
@ToString(includeFieldNames = false)
public final class CartesianPoint {
    public final int x;
    public final int y;
}

Calling code would look like:

public CartesianPoint locate() {
    int x = computeX();
    int y = computeY();

    return CartesianPoint.valueOf(x, y);
}

Notice the visual similarity of the local variables x and y to the corresponding fields in CartesianPoint. Users of CartesianPoint would see:

public static void main(final String... args) {
    Boat boat = Boat.rowBoat();
    CartesianPoint point = boat.locate();

    out.printf("%s -> x is %d, y is %d%n", point, point.x, point.y);
}

$ ./float-boat 1 2
CartesianPoint(1, 2) -> x is 1, y is 2

If you want to go hog wild, the ordered and unnamed qualities of tuples can be simulated though not without significant noise:

Function<CartesianPoint, Integer> first = p -> p.x;
Function<CartesianPoint, Integer> second = p -> p.y;

out.printf("first is %d, second is %d%n",
        first.apply(point),
        second.apply(point));

Update

Streaming functionally:

Function<CartesianPoint, Integer> first = p -> p.x;
Function<CartesianPoint, Integer> second = p -> p.y;
Stream.of(first, second).
    map(f -> f.apply(point)).
    forEach(out::println);
Post a Comment