Pre-SIP: Tuple shorthand syntax for case class instantiation

Problem

There is overlap in use cases between case classes and named tuples. They are conceptually similar and syntactically.

One ergonomic advantage named tuples has is that instantiation is less verbose when supplying default arguments.

type Operation = (lhs: Int, op: Char, rhs: Int)

def f(operation: Operation = (2, '+', 2))
//                           ^ inferred
case class Operation(lhs: Int, op: Char, rhs: Int)

def f(operation: Operation = Operation(2, '+', 2))
//                           ^ redundant

Proposal

When an identifier is explicitly typed, allow invoking the constructor through parentheses, like named tuples.

case class Operation(lhs: Int, op: Char, rhs: Int)

val o: Operation = (2, '+', '2)
//  o: Operation =  Operation(2, '+', 2)

I also imagine if this proposal were accepted, it should for non-case classes too.

Precedent

in C#, when the type of an identifier is explicitly known, you can simply type new to invoke the constructor.

Vector3 v = new(0,0,0)
//        = new Vector3(0,0,0)

I would also argue that named tuples themselves are good precedent for this proposal; now that named tuples are becoming standard and have this inference ability, newcomers in Scala will see it and may also expect case classes to support it, being surprised when they find it doesn’t work.

Motivation

In scenarios where named tuples and case classes are both reasonable choices in representing my domain problem, I might decide that case classes are the most suitable construct, yet still choose named tuples simply for the more pleasant syntax…

In opinion, that is a bad sign. I think these sorts of arbitrary cosmetic and ergonomic differences between constructs – arbitrary in that there isn’t a reason why they couldn’t have both been designed to be equally concise and pleasant from the beginning, in hindsight – shouldn’t be the sorts of factors programmers have to weigh when choosing which is most appropriate tool. So, minimizing the cosmetic differences between the two constructs would makes the decision process easier.

Personally, I’ve have chosen named tuples a few times because of this cosmetic appeal of simplicitly, only to regret it after I realize that i want to have methods on the type, which would have been easy with case classes, but requires an extension on named tuples, and a companion object if i want those extensions to be imported automatically… Trade-offs that I shouldn’t have to spend time thinking about.

Second, I think that often outside callers looking at an API or function signature shouldn’t have to know the exact language construct that a type was created through; it may have been a case class, or a named tuple, or even an opaque type alias, it usually doesn’t have relevance to the semantics of the procedure. So, I think the current asymmetry in notation is a bit of an abstraction leakage.

1 Like

Note that this was also discussed before in: Pre-SIP: A Syntax for Collection Literals - #94 by odersky

See a prototype here: Allow named tuple syntax for case class constructors. by odersky · Pull Request #22400 · scala/scala3 · GitHub

1 Like