Pre-SIP: Named-Tuple, Case Class Destructuring, and Application Shorthand

See original discussion.

Currently, when destructuring a case class or named tuple using named parameters, Scala requires developers to specify both the parameter name and the name of the variable they wish to extract the value into, for example:

case class User(firstName: String, lastName: String)
val User(firstName = firstName) = User("joe", "shmo") 

When the intended variable name is the same as the field name, this leads to repetitive code (firstName = firstName). Other languages offer ergonomic shortcuts for this common pattern. For example, JavaScript allows const { firstName } = user, and Dart allows final User(:firstName) = user to extract the field name firstName from the target object and create a local variable with the same name and value.

This repetition extends to inverse operations as well: instantiating case classes or applying methods. It is common to have local variables that match the target parameter names, resulting in verbose application code like User(name = name, age = age) or f(x = x, y = y).

This Pre-SIP proposes adding syntax to optionally eliminate this boilerplate across destructuring, instantiation, and method applications.

Feature 1: Destructuring Shorthand
Allow developers to extract fields from case classes and named tuples into local variables sharing the same name as the target parameter, without having to type the name twice.

Several syntaxes have been proposed for this feature:

Dot Syntax: val User(.firstName) = user

Rationale: Aesthetically looks like field access, representing what is practically happening.

Braces Syntax: val User { firstName } = user

Rationale: Functions similarly to selective import statements.

Leading Equals: val User(=firstName) = user

Rationale: Treats =firstName as a wildcard placeholder pattern on the right-hand side.

Trailing Equals: val User(firstName =) = user

Rationale: symmetric with existing named-argument application syntax.

Feature 2: Instantiation / Creation Shorthand
Provide the reverse operation to create instances concisely when local variables in scope match the case class parameter names.

Syntax: val user = User(=name, =age) (or User(name =, age =), etc, depending on the decided convention)

Desugars to: val user = User(name = name, age = age)

Feature 3: Method Application Shorthand
Extend the instantiation shorthand to general method applications. When calling a method, it is common to pass local variables that share a name with the method’s parameters. This is especially prevalent in recursive calls or functions where parameter types are identical (e.g., multiple Strings) and named arguments are used to prevent accidental swapping.

Syntax: f(myFirstArg =, anotherArg =) (again, exact syntax depending on decided convention)

Desugars to: f(myFirstArg = myFirstArg, anotherArg = anotherArg)

Existing Alternatives/Workarounds:
The original thread identified current features that can achieve similar end goals, at least for destructuring assignment:

Positional matching with wildcards: val User(firstName, _) = user
I think it is already well-accepted that this becomes tedious for cases with enough unrelated fields and is brittle to field modification.

Import scoping: import user.{firstName, lastName} brings the fields directly into the local scope without pattern matching, but I don’t believe is useful for example in pattern matching cases or assignments in for-comprehensions.

I don’t believe either alternative addresses the method/constructor applications as well.

2 Likes