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.42k stars 125 forks source link

Named arguments & optional parameters #641

Closed rsmckinney closed 6 days ago

rsmckinney commented 1 week ago

Only minor changes are needed to make tuples more compatible with structural interfaces to provide a foundation for virtual named arguments and optional parameters.

Prelim documentation. . .

Named arguments & optional parameters

Combining named arguments with optional parameters is a useful alternative for flexible and readable function calls. Although Java does not provide either feature, we can utilize tuples and structural interfaces for virtually the same functionality.

Consider a typical telescoping constructor in Java.

public class Person {
  private String name;
  private int age;
  private Gender gender;
  private String address;
  private String phone;

  // name is the only required parameter, others are optional
  public Person(String name) {
    this(name, 0, null, null, null);
  }

  public Person(String name, int age) {
    this(name, age, null, null, null);
  }

  public Person(String name, int age, Gender gender) {
    this(name, age, gender, null, null);
  }

  public Person(String name, int age, Gender gender, String address) {
    this(name, age, gender, address, null);
  }

  public Person(String name, int age, Gender gender, String address, String phone) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.address = address;
    this.phone = phone;
  }
  . . .
}

While useful, this technique has become known as an "anti-pattern" due to its verbosity, maintenance concerns, and general inadequacy. Although the Builder pattern is sometimes used to overcome some of the drawbacks, it too has its own set of problems, mostly due to the boilerplate and tedium involved with writing/generating and maintaining them.

Using tuples and structural typing to simulate named arguments and optional parameters offer a cleaner, easier to maintain alternative to these strategies.

public class Person {
  private String name;
  private int age;
  private Gender gender;
  private String address;
  private String phone;

  @Structural interface Options {
    default int getAge() {return 0;}
    default Gender getGender() {return null;}
    default String getAddress() {return null;}
    default String getPhone() {return null;}
  }
  public Person(String name, Options options) {
    this.name = name;
    this.age = options.getAge();
    this.gender = options.getGender();
    this.address = options.getAddress();
    this.phone = options.getPhone();
  }

  . . .
}

Since tuples can be assignable to structural interfaces, we can use them for a pleasant named arguments & optional parameters syntax.

Person person = new Person("Scott", (age:100, phone:"408-555-1234"));

Here, we create a Person using select options in the order of our choosing. Not only is this syntax more concise and easier to read & use, the reduction in boilerplate also relieves the maintenance burden compared with telescoping methods and builders.

Note, the name parameter may be shifted to the Options interface as a required method. As a result the name constructor parameter is also shifted into the options argument.

Person person = new Person((name:"Scott", age:100, phone:"408-555-1234"));
. . .
@Structural interface Options {                                  
  String getName();
  default int getAge() {return 0;}
  default Gender getGender() {return null;}
  default String getAddress() {return null;}
  default String getPhone() {return null;}
}
public Person(Options options) {
  this.name = name;
  this.age = options.getAge();
  this.gender = options.getGender();
  this.address = options.getAddress();
  this.phone = options.getPhone();
}

The benefit in making such a change is that required parameters can be named at the call site and appear in any order.

Note, using properties can further eliminate Java's boilerplate while increasing readability.

@Structural interface Options {                                  
  @val String name;
  @val int age = 0;
  @val Gender gender = null;
  @val String address = null;
  @val String phone = null;
}
public Person(Options options) {
  this.name = options.name;
  this.age = options.age;
  this.gender = options.gender;
  this.address = options.address;
  this.phone = options.phone;
}
rsmckinney commented 6 days ago

Feature available with release 2024.1.42