Mulling this over after discussing with @sjrd and some others at lunch today, I now think this can really be super simple. Here is a MVP (meaning minimum viable proposal).
Unpack modifier
Introduce a new unpack
modifier that can be added to the last parameter of a method. Example:
case class Config(size: Int, label: String = "")
def request(body: Body, unpack config: Config) = ...
The type of an unpack
parameter must be a statically accessible class. Inside the method, unpack
has no significance; we simply have a parameter of the declared type.
When such a method is called, we first match arguments as usual. When it comes to matching the last formal parameter, we simply wrap the remaining actual arguments with the apply method of the class. Example:
request(body, 20, label = "abc")
would expand to
request(body, Config(20, label = "abc"))
The expansion is done before typing. So there could be several apply methods and overloading resolution will pick the correct one.
Reuse Spread Operator
If we have a call like
request(body, config*)
we pass the last argument “as is” without wrapping. This works in the same way for repeated parameters (where the argument must be a sequence or an array) and for unpack parameters, where the argument must simply match the formal parameter type.
Notes
-
The unpack parameter class will often be a case class (since then it is easy to pick it apart) but this is no absolute requirement. In Scala 3, regular classes are provided with synthetic apply methods as well, and we can use them to wrap the arguments.
-
As always, the
unpack
class can itself have default arguments which then translate to default arguments of the unpacked version. So with the definitions aboverequest(body, size = 20)
would be legal and expand to
request(body, Config(size = 20))
which in turn expands to
request(body, Config(size = 20, label = ""))
-
The
Config
class can also have defaults for all parameters in which case we can leave out the argument completely. So givencase class Config(size: Int = 20, label: String = "abc")
we can write
request(body)
and this would expand to
request(body, Config())
-
I believe the question whether we want to accommodate multiple unpack parameters per method and/or multiple spread operators per class is orthogonal. This would probably raise a lot of tricky questions of how to expand and disambiguate. This proposal intentionally leaves that out. I would claim we thus get most of the benefit with very little additional language complication. But it could well be added later.
-
The proposal also intentionally does not allow you to use a spread operator to expand into multiple normal parameters of the called method. I believe such a feature would lead to too clever-by-half code which could become very confusing.
-
Interestingly the question of special named arguments turned out to be a red herring. The proposal works with named and positional arguments in the same way.