0

I have a singleton objet with 100 different case classes. For example:

object Foo {

case class Bar1 {
...
}

... 

case class Bar100 {
...
}
}

I would like to be able to iterate over each of the case class. Something like getting all the case classes in a Seq and then being able to map over it. (map with a polymorphic function for example)

Is it possible using reflection? If yes how? And what are the drawbacks of using reflection here over hard coding a sequence with all the case classes.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    > "And what are the drawbacks of using reflection here over hard coding a sequence with all the case classes." You miss type-safety, compile warnings and got relatively slow code. If you have similar functions among case classes, then probably it is better to create traits and to use a pattern matching on them. – Mikhail Ionkin May 07 '22 at 10:10
  • Another drawback to reflection: if you need to do bytecode obfuscation for whatever reason, you need to configure a rule to not rename those classes, or else the reflection-based logic will be broken. – Dylan May 09 '22 at 03:59
  • @MikhailIonkin *"You miss type-safety..."* Maybe by reflection OP meant runtime reflection, but besides runtime reflection in Scala there is compile-time reflection, which is not missing type safety. – Dmytro Mitin Feb 13 '23 at 07:01

2 Answers2

1

Foo.getClass.getDeclaredClasses gives you all the classes declared inside Foo. Because they are case classes, each also defines a companion object (which is also a class), so you'll need to filter them out:

Foo.getClass.getDeclaredClasses.filterNot(_.endsWith("$"))

Reflection is slow, but if you are only going to do it once (no reason to do it more than once, because you'll alway be getting the same result), it's not really an issue.

A bigger problem is that I can't really imagine a "polymorphic function" that would let you do anything useful with this information without some extreme hacking.

Dima
  • 39,570
  • 6
  • 44
  • 70
0

Fully parametric polymorphic functions exist in Scala 3. In Scala 2 there are no parametric polymorphic functions (and polymorphic values at all, only polymorphic methods) but there are ad-hoc polymorphic functions implemented normally with Shapeless.

I guess in Shapeless there is a type class (Generic/LabelledGeneric) for iterating case classes extending some sealed trait

case class Bar1() extends MyTrait
//...
case class Bar100() extends MyTrait

Scala how to derivate a type class on a trait

Use the lowest subtype in a typeclass?

Type class instance for case objects defined in sealed trait

Iteration over a sealed trait in Scala?

Getting subclasses of a sealed trait

Can I get a compile-time list of all of the case objects which derive from a sealed parent in Scala?

but not for iterating case classes nested into an object. So probably we'd need a macro (compile-time reflection) anyway, even using Shapeless

import scala.language.experimental.macros
// libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.reflect.macros.whitebox
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.HList

trait GetInnerCaseClasses[A] {
  type Out <: HList
}

object GetInnerCaseClasses {
  type Aux[A, Out0] = GetInnerCaseClasses[A] { type Out = Out0 }

  implicit def mkGetInnerCaseClasses[A, Out <: HList]: Aux[A, Out] =
    macro mkGetInnerCaseClassesImpl[A]

  def mkGetInnerCaseClassesImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._
    val A = weakTypeOf[A]
    val caseClasses = A.decls.filter(s => s.isClass && s.asClass.isCaseClass)
    val hList = caseClasses.foldRight[Tree](tq"_root_.shapeless.HNil")(
      (s, hl) => tq"_root_.shapeless.::[$s, $hl]"
    )
    q"""
      new GetInnerCaseClasses[$A] {
        override type Out = $hList
      }
    """
  }
}
// in a different subproject

import shapeless.ops.hlist.{FillWith, Mapper}
import shapeless.{::, HList, HNil, Poly0, Poly1, Typeable, the}

object Foo {
  case class Bar1()
  // ...
  case class Bar100()
}

implicitly[GetInnerCaseClasses.Aux[Foo.type, Bar1 :: Bar2 :: Bar100 :: HNil]] // compiles

val gicc = the[GetInnerCaseClasses[Foo.type]] // "the" is an advanced version of "implicitly" not damaging type refinements 
implicitly[gicc.Out =:= (Bar1 :: Bar2 :: Bar100 :: HNil)] // compiles

// I'm just printing names of case classes, you should replace this with your actual iterating logic
object myPoly extends Poly1 {
  implicit def cse[A <: Product : Typeable]: Case.Aux[A, Unit] =
    at(_ => println(Typeable[A].describe))
}

object nullPoly extends Poly0 {
  implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}

def myIterate[A <: Singleton] = new PartiallyAppliedMyIterate[A]

class PartiallyAppliedMyIterate[A <: Singleton] {
  def apply[L <: HList]()(implicit
    getInnerCaseClasses: GetInnerCaseClasses.Aux[A, L],
    mapper: Mapper[myPoly.type, L],
    fillWith: FillWith[nullPoly.type, L] // because type class GetInnerCaseClasses works only on type level
  ): Unit = mapper(fillWith())
}

myIterate[Foo.type]()
// Bar1
// ...
// Bar100
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66