Based on Travis Brown answer to the question "Iteration over a sealed trait in Scala" (https://stackoverflow.com/a/13672520/1637916) and my (poor) knowledge about scala macros, I wrote a simple implementation of select and random methods.
"select" method (from Sealed object) returns List of instances of case classes (or objects) which have constructor with specified parameters. "select" can not return one, random, implementation because it works at compile time and would return it always - for every invocation.
"random" method (from RanomElement object) picks one object from List returned by "select" method.
Sealed.scala file should be compiled first. If you try to compile two files (Sealed.scala and RandomGene.scala) at the same time, you will see the following error:
Error:(34, 27) macro implementation not found: select
(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)
select[Gene]().random
- Sealed.scala contains select
import scala.language.experimental.macros
import scala.reflect.internal.Symbols
import scala.reflect.macros.whitebox
/**
* Based on Travis Brown answer to the following question:
* https://stackoverflow.com/questions/13671734/iteration-over-a-sealed-trait-in-scala (https://stackoverflow.com/a/13672520/1637916)
*/
object Sealed {
def select[A](param: Any*): List[A] = macro select_impl[A]
def select_impl[A: c.WeakTypeTag](c: whitebox.Context)(param: c.Expr[Any]*): c.Expr[List[A]] = {
import c.universe._
val symbol = weakTypeOf[A].typeSymbol
if (!symbol.isClass) c.abort(
c.enclosingPosition,
"Can only enumerate values of a sealed trait or class."
)
else if (!symbol.asClass.isSealed) c.abort(
c.enclosingPosition,
"Can only enumerate values of a sealed trait or class."
)
else {
val children = symbol.asClass.knownDirectSubclasses.toList
val matchedChildren = children.filter { child =>
child.asType.toType.members.filter(s => s.isConstructor && s.owner == child).exists {
_.asMethod.paramLists match {
case head :: Nil =>
val constructorTypes = head.map(_.typeSignature)
val paramTypes = param.map(_.actualType)
constructorTypes.size == paramTypes.size && constructorTypes.zip(paramTypes).count { case (t1, t2) => t1 == t2 } == paramTypes.size
case _ => false
}
}
}
c.Expr[List[A]] {
def sourceModuleRef(sym: Symbol) = {
if(param.nonEmpty) {
Apply(
Select(
Ident(sym.companion),
TermName("apply")
),
param.map(_.tree).toList
)
} else {
Ident(sym.asInstanceOf[Symbols#Symbol].sourceModule.asInstanceOf[Symbol])
}
}
Apply(
Select(
reify(List).tree,
TermName("apply")
),
matchedChildren.map(sourceModuleRef(_))
)
}
}
}
}
- random
implicit class RandomSealed[A](ls: List[A]) {
def random = ls(Random.nextInt(ls.size))
}
- example
import scala.language.experimental.macros
import scala.util.Random
object RandomGene {
sealed abstract class Gene(val code: Int)
object Gene {
case object None extends Gene(0)
case object Identity extends Gene(1)
case object Squared extends Gene(2)
case object Cubed extends Gene(3)
case class Exp(a: Int, b: String) extends Gene(a)
case class Exp1(b: String, a: Int) extends Gene(a)
case class Exp2(a: Int) extends Gene(a)
case class Exp3(a: Int) extends Gene(a)
case class Exp4(a: Int) extends Gene(a)
}
implicit class RandomElement[A](ls: List[A]) {
def random = ls(Random.nextInt(ls.size))
}
def main(args: Array[String]) {
import Sealed._
(1 to 5).foreach { n =>
println(select[Gene]().random)
}
(1 to 5).foreach { n =>
println(select[Gene](n).random)
}
}
}