Search code examples
javascalagenericsreflectionscala-reflect

How to pass into generic type using a Symbol or Type object into a generic typed function?


In Scala, is it possible to pass a type derived from a Symbol or Type object into a generic typed function? For example:

case class Address(street: String, city: String, state: String, zipCode: String)
case class Person(name: String, age: Int, address: Address)

def a[T: TypeTag](): Unit = {
    val fields: Seq[Symbol] = typeOf[T].members.filter(_.isMethod == false).toSeq
    fields.foreach(x => {
       b[x.getMyType]() // How to pass field's "Type" into generic typed function?
    })
}

def b[T](): Unit = ???

a[Person]()

From the above example, I am interested in calling a[Person]() and within a(), use reflection to obtain the fields from Person to make calls into b[?]() using each field's type.


Solution

  • is it possible to pass a type derived from a Symbol or Type object into a generic typed function?

    Type parameter T of method b must be known at compile time but x.typeSignature becomes known only at runtime.

    You can try to use compile-time reflection rather than runtime one. Then x.typeSignature becomes known at runtime of the macro, which is compile time of the main code.

    // macros subproject
    
    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    def a[T](): Unit = macro aImpl[T]
    
    def aImpl[T: c.WeakTypeTag](c: blackbox.Context)(): c.Tree = {
      import c.universe._
      val fields: Seq[Symbol] = weakTypeOf[T].members.filter(_.isMethod == false).toSeq
      val bCalls = fields.map(x => 
        q"b[${x.typeSignature}]()"
      )
      q"..$bCalls"
    }
    
    // main subproject
    
    case class Address(street: String, city: String, state: String, zipCode: String)
    case class Person(name: String, age: Int, address: Address)
    
    def b[T](): Unit = ???
    
    a[Person]()
    
    // scalac: {
    //  b[App.Address]();
    //  b[Int]();
    //  b[String]()
    //}
    

    Similar thing can be done with Shapeless.

    import shapeless.ops.hlist.{FillWith, Mapper}
    import shapeless.{Generic, HList, Poly0, Poly1}
    
    def b[T](): Unit = println("b")
    
    object bPoly extends Poly1 {
      implicit def cse[X]: Case.Aux[X, Unit] = at(_ => b[X]())
    }
    
    object nullPoly extends Poly0 {
      implicit def cse[X]: Case0[X] = at(null.asInstanceOf[X])
    }
    
    def a[T] = new PartiallyAppliedA[T]
    
    class PartiallyAppliedA[T] {
      def apply[L <: HList]()(implicit
        generic: Generic.Aux[T, L],
        mapper: Mapper[bPoly.type, L],
        fillWith: FillWith[nullPoly.type, L]
      ): Unit = mapper(fillWith())
    }
    
    case class Address(street: String, city: String, state: String, zipCode: String)
    case class Person(name: String, age: Int, address: Address)
    
    a[Person]()
    
    //b
    //b
    //b
    

    Alternatively, if you really want to use runtime reflection, you have to postpone compilation of b[...]() till runtime. You can do this with toolbox.

    import scala.reflect.runtime.currentMirror
    import scala.reflect.runtime.universe._
    import scala.tools.reflect.ToolBox
    
    val toolbox = currentMirror.mkToolBox()
    
    def a[T: TypeTag](): Unit = {
      val fields: Seq[Symbol] = typeOf[T].members.filter(_.isMethod == false).toSeq
      val bCalls = fields.map(x => 
        q"b[${x.typeSignature}]()"
      )
      toolbox.eval(q"""
        import Obj._
        ..$bCalls
      """)
    }
    
    object Obj {
      def b[T](): Unit = println("b")
    }
    
    case class Address(street: String, city: String, state: String, zipCode: String)
    case class Person(name: String, age: Int, address: Address)
    
    a[Person]()
    
    //b
    //b
    //b