Skip to content

How to support Mirrors for Generic Tuples arity +22 #15398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
bishabosha opened this issue Jun 8, 2022 · 9 comments
Open

How to support Mirrors for Generic Tuples arity +22 #15398

bishabosha opened this issue Jun 8, 2022 · 9 comments

Comments

@bishabosha
Copy link
Member

bishabosha commented Jun 8, 2022

Since #15250 we can now synthesize mirrors for generic tuple types if they have arity <= 22, we can do this because we pretend the mirror was for one of the equivalent scala.Tuple<N> classes.

We can't do that for TupleXXL however as it has no accessor methods, and supporting them by creating fake accessor names would break the assumption that MirroredElemLabels corresponds to field accessors. Look at the example below:

Compiler version

3.2.0-RC1-bin-20220606-cec9aa3-NIGHTLY

Minimized example

case class Row(
  a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int, a9: Int, a10: Int,
  a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int,
  a21: Int, a22: Int, a23: Int
)

val mRow = summon[scala.deriving.Mirror.Of[Row]] // ok
val mElems = summon[scala.deriving.Mirror.Of[mRow.MirroredElemTypes]] // error

Output

-- Error: ----------------------------------------------------------------------
1 |summon[scala.deriving.Mirror.Of[mRow.MirroredElemTypes]] // error
  |                                                        ^
  |No given instance of type deriving.Mirror.Of[mRow.MirroredElemTypes] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[mRow.MirroredElemTypes]: 
  |	* class *: is not a generic product because it reduces to a tuple with arity 23, expected arity <= 22
  |	* class *: is not a generic sum because it does not have subclasses
1 error found

Expectation

we should be able to support generic tuples when arity is above 22. But the path to getting there would need some planning.

I propose that we could reinterpret MirroredElemLabels as a tuple of literal integer types for larger generic tuples, as there is currently no static constraint preventing this. e.g.

val res1: 
  scala.deriving.Mirror.Product{
    MirroredMonoType = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23);
    MirroredType = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23); 
    MirroredLabel = "Tuple23";
    MirroredElemTypes = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23);
    MirroredElemLabels = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22)
  } = anon$1@3f9e4811

Another alternative would be to add a new subtype of mirror: Mirror.Tuple, (probably also necessary to make explicit that the MirroredLabel no longer corresponds to a class name) and optionally a new Mirror.TupleOf[x *: y *: z *: EmptyTuple] summoner?

Either way the elem labels only act as unique identifiers, rather than having any runtime impact (and no you should not use the labels to access fields with java reflection, just use productIterator.)

@bishabosha bishabosha added stat:needs triage Every issue needs to have an "area" and "itype" label itype:enhancement area:typeclass-derivation and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jun 8, 2022
@nicolasstucki
Copy link
Contributor

The MirroredLabel should probably be "Tuple".

@sjrd
Copy link
Member

sjrd commented Jun 8, 2022

I believe the integers should start at 0, to match the values we can pass to the apply(i) method.

@bishabosha
Copy link
Member Author

bishabosha commented Jun 8, 2022

I believe the integers should start at 0, to match the values we can pass to the apply(i) method.

good catch! I did too much copy pasting :) (I fixed it now)

@nicolasstucki
Copy link
Contributor

nicolasstucki commented Jun 8, 2022

Do we really need labels for tuples? We could just have type MirroredElemLabels = Tuple and rely on the MirroredElemTypes for the rest.

@nicolasstucki
Copy link
Contributor

We should also make sure that #15399 works for large tuples.

@bishabosha
Copy link
Member Author

should summoning mirror for large tuples be experimental?

@nicolasstucki
Copy link
Contributor

Probably a good idea to have them experimental to start with.

@eejbyfeldt
Copy link

With NamedTuples in Scala 3.7.0 one summon Mirrors for NamedTuples of arbitrary size

scala> type BigTuple = (f1: Int, f2: Int, f3: Int, f4: Int, f5: Int, f6: Int, f7: Int,
     |                    f8: Int, f9: Int, f10: Int, f11: Int, f12: Int, f13: Int,
     |                    f14: Int, f15: Int, f16: Int, f17: Int, f18: Int, f19: Int,
     |                    f20: Int, f21: Int, f22: Int, f23: Int, f24: Int, f25: Int)
     | 
     | summon[scala.deriving.Mirror.ProductOf[BigTuple]]
// defined alias type BigTuple
   = (f1 : Int, f2 : Int, f3 : Int, f4 : Int, f5 : Int, f6 : Int, f7 : Int, f8 : Int,
    f9 : Int, f10 : Int, f11 : Int, f12 : Int, f13 : Int, f14 : Int, f15 : Int, f16 : Int,
    f17 : Int, f18 : Int, f19 : Int, f20 : Int, f21 : Int, f22 : Int, f23 : Int, f24 : Int,
    f25 : Int)
val res2:
  scala.deriving.Mirror.Product{
    type MirroredMonoType = BigTuple; type MirroredType = BigTuple;
      type MirroredLabel = "NamedTuple";
      type MirroredElemTypes = (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
        Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int);
      type MirroredElemLabels = ("f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10",
        "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22",
        "f23", "f24", "f25")
  } = scala.runtime.TupleMirror@52294ab7

So

probably also necessary to make explicit that the MirroredLabel no longer corresponds to a class name

and

Either way the elem labels only act as unique identifiers, rather than having any runtime impact (and no you should not use the labels to access fields with java reflection, just use productIterator.)

Are now part of the behavior Mirror for NamedTuples.

As a user this makes the current limit of 22 element mirror for Tuples feel very arbitrary. But maybe it will be easier to relax now?

@bishabosha
Copy link
Member Author

its really a case of removing the explicit special case in Synthesizer.scala, so PR welcome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants