Summon and Using, are they different?

import scala.util.Random as ScalaRandom

trait Random[A]:
  def generate(): A

given randLong: Random[Long] with
  def generate(): Long = ScalaRandom.nextLong(1000000)

given randBool: Random[Boolean] with
  def generate(): Boolean = ScalaRandom.nextBoolean()

// use using
inline def tmp()(using m: Mirror.ProductOf[(String, Long)]): Unit =
  val success: List[Random[?]] = summonAll[Tuple.Map[(String, Long), Random]].toList
  val failed: List[Random[?]] = summonAll[Tuple.Map[m.MirroredElemTypes, Random]].toList // compiler errer !!!

// use summon
inline def tmp2(): Unit =
  val m = summon[Mirror.ProductOf[(String, Long)]]
  val success: List[Random[?]] = summonAll[Tuple.Map[(String, Long), Random]].toList
  val success2: List[Random[?]] = summonAll[Tuple.Map[m.MirroredElemTypes, Random]].toList  // ok !!!
  println(success2)

By the way, how can I print m.MirroredElemTypes

You can have a look at the type of m in REPL:

scala> val m = summon[deriving.Mirror.ProductOf[(String, Long)]]
val m:
  scala.deriving.Mirror.Product{
    type MirroredMonoType = (String, Long); type MirroredType = (String, Long);
      type MirroredLabel = "Tuple2"; type MirroredElemTypes = (String, Long);
      type MirroredElemLabels = ("_1", "_2")
  } = scala.runtime.TupleMirror@11e71181

In case of tmp2 the exact type of m is inferred by the compiler to what is returned by summon, which is more specific than Mirror.ProductOf[(String, Long)]. The inferred type has a refinement, which tells the compiler that m.MirroredElemLabels is ("_1", "_2") rather than just Tuple.
In case of tmp the type of the parameter is given specifically as Mirror.ProductOf[(String, Long)] so it lacks the refinement.
Note that tmp2 wouldn’t work either if you added an imprecise type ascription like

inline def tmp2(): Unit =
  val m: Mirror.ProductOf[(String, Long)] = summon[Mirror.ProductOf[(String, Long)]]
  val success: List[Random[?]] = summonAll[Tuple.Map[(String, Long), Random]].toList
  val success2: List[Random[?]] = summonAll[Tuple.Map[m.MirroredElemTypes, Random]].toList  // ok !!!
  println(success2)

Thanks for your reply! The REPL is a great tool to see what’s going on.
Then how can I get instances: List[Random[?]] without forcing asInstanceOf[List[Random[?]]]?
Is this possible?

object Random:
  inline given derived[A: ClassTag](using m: Mirror.Of[A]): Random[A] =
    val value = summonAll[Tuple.Map[m.MirroredElemTypes, Random]]
    lazy val instances: List[Random[?]] = value.toList.asInstanceOf[List[Random[?]]] // without asInstanceOf, not compile
    inline m match
      case s: Mirror.SumOf[A] => deriveSum(instances)
      case p: Mirror.ProductOf[A] => deriveProduct(p, instances)

  private def deriveSum[A](instances: List[Random[?]]): Random[A] =
    () => instances(scala.util.Random.nextInt(instances.length)).asInstanceOf[Random[A]].generate()

  private def deriveProduct[A: ClassTag](p: Mirror.ProductOf[A], instances: List[Random[?]]): Random[A] =
    () => p.fromProduct(Tuple.fromArray(instances.map(_.generate()).toArray))

Most probably you would have to implement summonAll by yourself instead taking the variant from the standard library so that it returns List[Random[?]]. It’s good to point out here that the implementation from the stdlib might cause problems if you’re going to deal with big case classes because it might quickly exhaust the inlining limit because it’s defined as a recursive inline method. The workaround is to implement it as a macro.

1 Like
def tmp(): Unit =
  val value: Tuple.Map[(Long, String, Boolean), Random] = summonAll[Tuple.Map[(Long, String, Boolean), Random]]
  val instances: List[Random[?]] = value.toList // don't need asInstanceOf

This code compiles. Is this meaning that the compiler is not able to figure out the m.MirroredElemTypes of RegisteredUser in

object Random:
  inline given derived[A: ClassTag](using m: Mirror.Of[A]): Random[A] =
    val value = summonAll[Tuple.Map[m.MirroredElemTypes, Random]]
    lazy val instances: List[Random[?]] = value.toList.asInstanceOf[List[Random[?]]] // without asInstanceOf, not compile
    inline m match
      case s: Mirror.SumOf[A] => deriveSum(instances)
      case p: Mirror.ProductOf[A] => deriveProduct(p, instances)

enum SiteMember:
  case RegisteredUser(id: Long, email: String, isAdmin: Boolean)
  case AnonymousUser(session: String)

The point is that inside the definition of inline given derived the exact type of m is not statically known so the compiler cannot fully compute the Tuple.Map[m.MirroredElemTypes, Random] type. It might seem reasonable to assume that the compiler should know that no matter what A was, Tuple.Map[m.MirroredElemTypes, Random] should be a tuple of Randoms but for Tuples it’s not really possible to express an abstract constraint on the type of individual elements

1 Like

@prolativ Thank you again! I do encounter the the inlining limitation tody.