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.
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 Random
s but for Tuple
s it’s not really possible to express an abstract constraint on the type of individual elements