Pre-SIP: Extending consistency of Product fields

Even though Scala 3 has dropped the limit of arity 22, there is still a large gap between arities 1 to 22 and arities greater than 22. Some of these limits in Products (and by extension, tuples), is the lack of accessor fields. If I have a product of arity 23, I can’t access the 23rd field with _23, or even any of the first 22 fields in a type safe way. This is mostly a non issue for tuples, as they already have a type safe way of accessing fields without needing to use these accessors, however they also will have an issue expressing dependent types: Tuples with arity greater than 22 have to expression dependent function types as (t: (T, … T)) => Tuple.Elem[t, 22] which could cause issues with any future implementations of TupledFunction.

I propose that for generated products with arity > 22, the compiler will synthesize a ProductN type for that arity, and will insert inline def fields in the type according to how it currently does for arity 1 to 22.
For example, a case class with 23 fields will end up generating this:

trait Product23[+T1, +T2, +T3, +T4, ..., +T23]:
    inline def _1: T1
    inline def _2: T2
    inline def _3: T3
    inline def _4: T4
    // ...
    inline def _23: T23
case class Foo(a1: R1, a2: R2, a3: R3, a4: R4, ..., a23: R23) 
    extends Product23[R1, R2, R3, R4, ..., R23]:
    inline def _1: R1 = a1
    inline def _2: R2 = a2
    inline def _3: R3 = a3
    inline def _4: R4 = a4
    // ...
    inline def _23: R23 = a23

At runtime, these inserted methods are all erased, and the Product23 trait is erased at runtime too. The case class will still end up extending Product though.

Tuples will generate in a similar manner. For each tuple above arity 22, a synthetic TupleN class will extend the ProductN trait of the same arity.

This trait can’t be instanced by an end user, and isn’t present at runtime, as it’s erased to TupleXXL. It simply contains inline methods to forward to the existing Tuple methods.

It will look something like this:

final abstract class Tuple23[+T1, +T2, +T3, ... +T23]() extends Tuple, Product23[T1, T2, T3, ..., T23]:
    inline def _1: T1 = apply(0)
    inline def _2: T2 = apply(1)
    inline def _3: T3 = apply(2)
    // ...
    inline def _23: T23 = apply(22)
  

Questions I have:

I don’t know exactly how the internals work, so I don’t know if final abstract class is the correct choice. I took final abstract class from Int.

I do not know if I’ve correctly implemented how case class would work.

I think I do have the general idea correct, but I don’t know exactly how everything is translated so any feedback would be appreciated

1 Like

I would like to ask if you actually tried to define a case class with 23 fields, because _23 will be there, and is publicly accessible, and is necessary for pattern matching which uses the underscore accessor methods to extract the fields,

It is true that for Tuple >22 accessors are not there so is inconsistent

The problem with Tuple22+ is that if we synthesize it, it needs some generated bytecode. Where does that live? This might seem a small detail, but is really a can of worms. That’s why we opted for the TupleXXL solution instead.

That’s why I thought maybe it could be an erased class, like how primitive types are currently erased. It’s only available at compile time, and the fields are inline so it will be inlined by the compiler.

No, I haven’t done case classes. Yeah, it does seem that they do indeed have accessors for all their fields. I’ll probably remove the section about case classes and make this just about tuples, as there doesn’t seem to be a point in defining a ProductN if TupleN will be the only thing utilizing it.

what is the problem with using apply(n)? you didnt qualify with a real use case other than surprise that underscore accessors are not there, and there is always productElement(n: Int): Any

How do you write this dependent function type for tuples with lower arities? And what issue exactly arises if you write them like this?