Dmitry Reutov
Dmitry Reutov

Reputation: 3032

How to extract types from tuple type?

I have a code

case class MyTypeTag[T] () 

def getTypeTags[TT <: Product : TypeTag] = {
  val subtypesTags: List[MyTypeTag[Option[_]] = ???  
  sybtypesTags
}

val res = getTypeTags[(Int, String, Boolean)] 
// res = MyTypeTag[Option[Int]] :: MyTypeTag[Option[String]] :: MyTypeTag[Option[Boolean]] :: Nil

so i want to call function getTypeTags passing any tuple type as type parameter and get list of MyTypeTag instances with each type inside tuple wrapped in Option

if in intelliJ i evaluate expression typeof[TT] i see property args with list of my types, but i do not know how to access from code. Or may be some other ways can be apllied.

Thanks in advance

Upvotes: 0

Views: 317

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51648

Type parameter T in case class MyTypeTag[T]() must be known at compile time. But it seems you try to define it using runtime reflection. This can't work (unless you define the class at runtime: toolbox.define(q"case class MyTypeTag[T]()") but this would be tricky).

You can try to use compile-time reflection

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def getTypeTags[TT <: Product]: List[MyTypeTag[_ <: Option[_]]] = 
  macro getTypeTagsImpl[TT]

def getTypeTagsImpl[TT: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
  import c.universe._
  weakTypeOf[TT].typeArgs.map(t => q"MyTypeTag[Option[$t]]()")
    .foldRight[Tree](q"Nil")((t, ts) => q"$t :: $ts")
}

Usage:

getTypeTags[(Int, String, Boolean)] //List(MyTypeTag(), MyTypeTag(), MyTypeTag())

In order to verify that the code works properly let's temporarily modify MyTypeTag

import scala.reflect.runtime.universe.TypeTag

case class MyTypeTag[T]()(implicit val typeTag: TypeTag[T])

val res = getTypeTags[(Int, String, Boolean)]
res(0).typeTag // TypeTag[Option[Int]]
res(1).typeTag // TypeTag[Option[String]]
res(2).typeTag // TypeTag[Option[Boolean]]

You can also use Shapeless instead of macros

import shapeless.ops.hlist.{FillWith, Mapped, ToList}
import shapeless.{Generic, HList, Poly0}

case class MyTypeTag[T]()

def getTypeTags[TT <: Product] = new {
  def apply[L <: HList, L1 <: HList]()(implicit
    generic: Generic.Aux[TT, L],
    mapped: Mapped.Aux[L, λ[X => MyTypeTag[Option[X]]], L1],
    fillWith: FillWith[myTypeTagPoly.type, L1],
    toList: ToList[L1, MyTypeTag[_ <: Option[_]]]
  ): List[MyTypeTag[_ <: Option[_]]] = 
    fillWith().toList
}

object myTypeTagPoly extends Poly0 {
  implicit def cse[A]: Case0[MyTypeTag[Option[A]]] = at(MyTypeTag[Option[A]]())
}

getTypeTags[(Int, String, Boolean)]() // List(MyTypeTag(), MyTypeTag(), MyTypeTag())

If you make MyTypeTag covariant (MyTypeTag[+T]) then List[MyTypeTag[_ <: Option[_]]] can be replaced with List[MyTypeTag[Option[_]]].

Upvotes: 1

Martijn
Martijn

Reputation: 12091

You can't distinguish between an instance of MyTypeTag[Int] and an instance of MyTypeTag[String] at runtime (to check this for yourself, try

val l = List(MyTypeTag[Option[Int]](), MyTypeTag[Option[String]](), MyTypeTag[Option[Boolean]]())

and see what that gives you, and what you can and can't do with it), so the answer to the question as you ask it is

def getTypeTags[TT <: Product](implicit tt: TypeTag[TT]): List[MyTypeTag[_]] = {
  tt.tpe.typeParams.map(_ => MyTypeTag[Option[_]]())
}

You can get the type parameters with tt.tpe.typeParams, but since that's a runtime value, you can't recover that as a compile-time type T for MyTypeTag[T] since it doesn't exist at compile-time yet.

Maybe you can leverage shapeless to do whatever it is you want to do, it has ways to abstract over tuples. See https://underscore.io/books/shapeless-guide/

Upvotes: 0

Related Questions