0

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.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
code
  • 5,294
  • 16
  • 62
  • 113
  • 2
    This looks like an **XY Problem**, why exactly do you need this? Runtime reflection is unsafe, slow, insecure and non-portable. Also, actually generics do not exists at runtime and there are no types but classes. – Luis Miguel Mejía Suárez Aug 14 '20 at 01:25
  • @LuisMiguelMejíaSuárez Do you consider all reflection questions as XY? :) – Dmytro Mitin Aug 14 '20 at 09:33
  • 1
    @DmytroMitin short answer yes :) - long answer. Given that: First, I consider _(runtime)_ **reflection** a last resort tool. Second, I really do not have the knowledge to answer the question. And third, that I know that someone else _(usually you)_ will provide a full answer to the specific question _(which I always upvote)_; then, I prefer to just make a comment telling OP if he / she is really really sure that reflection is really what his / her problem needs or if maybe there is another approach. – Luis Miguel Mejía Suárez Aug 14 '20 at 15:30

1 Answers1

3

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
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66