manifold-systems / manifold

Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.
http://manifold.systems/
Apache License 2.0
2.43k stars 125 forks source link

Support type-safe tuple expressions #372

Closed rsmckinney closed 2 years ago

rsmckinney commented 2 years ago

Proposal

Provide type-safe tuple expressions, mostly for internal code use as opposed to API usage.

Rationale: Tuple expressions provide a concise, more type-safe alternative to existing idioms such as less type-safe Pair classes and the like. Tuple expressions can also be a less verbose alternative to Java's record types.

Tuple expression syntax:

tuple-expression: '('<tuple-value> {, <tuple-value>}')'
tuple-value:      [<label> ':'] <expression>

Examples

In most cases tuple expressions involve the use of the auto feature. This is because tuple types are always inferred. More on this later.

// labeled tuple expression
auto record = (name: "Scott", age: 100);
String name = record.name;
int age = record.age;

// non-labeled tuple (labels are inferred from values)
auto select = (person.name, person.age);
String name = select.name;
int age = select.age;

// multiple return values
auto aggr = aggregate();
useMinMax(aggr.min, aggr.max) ;

auto aggregate() {
  int avg = findAvg();
  int min = findMin();
  int max = findMax();
...
  return avg, min, max;
}

// a query selection 
var namesAndAges = ofAge(persons, 25);
for(var t: namesAndAges) { 
  out.println(t.name + ": " + t.age); 
}

public auto ofAge(List<Person> persons, int age) {
  return list.stream()
    .filter(p -> p.age >= age)
    .map(p -> (p.name, p.age)) // makes a list of tuples (name, age)
    .collect(Collectors.toList());
}

Inferred type

While considering tuple support as a general feature I found that tuple expressions are what was missing, not so much tuple types. The main idea is that tuple expressions are a quick, concise way to produce type-safe name/value data, and not so much as a way to consume it. For instance, it's easy for a method to anonymously return a tuple with auto, but to consume a tuple as a parameter it must be typed explicitly. Tuple type syntax is inherently verbose and difficult to read, particularly in the context of a parameter type.

(id: int, comment: String, timestamp: LocalTime)  applyChanges((id: int, comment: String, timestamp: LocalTime) record, Filter filter) {
  ...
}

This simple example illustrates the readability impact caused by tuple type verbosity. Since they are purely structural, tuple types cannot be centrally defined like a nominal type, as a consequence there are no nominal tuple type references. Or rather, a tuple type reference is indistinguishable from a tuple type definition.

Type aliases, if Java had them, could fix this. But then what's the purpose of using tuples? If you're going to nominally define it, why not just define a class or record? Ah, it's the expressions that are the real benefit with tuples. In fact, tuple expressions also work directly with Java records in the case of nominal typing.

Note, some languages define nominal types for tuples without item names, but this sacrifices type-safety. There is no way to document what each of the tuple's items are, only the types are present.

Record types

Tuple expressions can be used interchangeably with record types. A tuple used in the context of a Java 16 record type automatically resolves as an instance of the record type.

// tuples are an alternative syntax for using Java records
MyRecord record = (person.name, person.age);

In Java 17+ tuples are backed by Java records.

Structural interfaces

Tuples naturally satisfy structural interfaces. Interfaces are typically a more user-friendly, API-neutral means of exposing type-safe name/value data.

Limitations

No tuple types

Tuple types are inferred from tuple expressions, there is no way to define a tuple type explicitly. This is a designed limitation, see the Inferred type discussion above.

Tuples can't reference private classes

A tuple expression may not contain a value having a private inner class type. This is a first draft limitation that will likely be supported in a future revision.

rsmckinney commented 2 years ago

See https://github.com/manifold-systems/manifold/tree/master/manifold-deps-parent/manifold-tuple

Feature available with release 2022.1.16