karlf
karlf

Reputation: 65

Gather type information from type parameters and type members

I have the following nested structure involving type parameters and type members:

trait B

trait B1 extends B

trait U {
  type _B <: B
}

type U1 = U {
  type _B = B1
}

class Q[_U <: U] {
  override def toString() : String = {
    // print out type information on B here...
  }
}

def test() {
  val q = new Q[U1]()
  println(q.toString())
}

It seems impossible to me gathering the type information on B at runtime because of the way U1 is specified.

Am I wrong? If not, is there a solution with minor changes in the setup?

Thanks to the answer from Kipton Barros I came up with the following setup:

trait B

trait B1 extends B
trait B2 extends B

trait U {
  type _B <: B
  implicit val mfB : Manifest[_B]
}

class U1 extends U {
  type _B = B1
  val mfB : Manifest[_B] = implicitly
}

class U2 extends U {
  type _B = B2
  val mfB : Manifest[_B] = implicitly
}

class Q[_U <: U](u : _U) {
  override def toString() : String = {
    "B: " + u.mfB.erasure.getName()
  }
}

def test() {
  println(new Q(new U1) toString)
  println(new Q(new U2) toString)
}

The only downside of this approach is the need for instantiation of U.

Upvotes: 2

Views: 335

Answers (2)

Kipton Barros
Kipton Barros

Reputation: 21112

I would have thought to use a combination of a type refinement and a Manifest. The former allows to surface the abstract type _B as a type parameter B, and the latter instructs the Scala compiler to reify the type of B (edit: the static type from the call context) as a run-time object. Here's my attempt,

trait B
trait B1 extends B
trait B2 extends B
trait U { type _B <: B }
class U1 extends U { type _B = B1 }

class Q[B: Manifest, _U <: U { type _B = B}](u: U) {
  override def toString() : String = {
    implicitly[Manifest[B]].toString // Manifest[B] was an implicit parameter to Q
  }
}

// Four tests:
println(new Q[B1, U1](new U1) toString)        // (1) prints "$line1.$read$$iw$$iw$B1"
// println(new Q[B2, U1](new U1) toString)     // (2) correctly fails to compile
// println(new Q[Nothing, U1](new U1) toString)// (3) correctly fails to compile
println(new Q(new U1) toString)                // (4) prints "Nothing" (why not B1?)

It works in the first case, where explicit type parameters are given. The second case correctly fails to compile, since U1 contains a B1 type, not a B2 type. Similarly for the third case. For some reason, though, the Scala compiler is generating an incorrect manifest in the fourth case, even though the compiler seems to infer type B1. I don't know enough to say whether this is a bug, but it's certainly surprising to me. Can anyone explain why case (4) doesn't print B1's Manifest?

Upvotes: 0

retronym
retronym

Reputation: 55028

Just to clear up a misconception: a Manifest does not carry the run-time type of a type parameter. It carries the static type from the context of the call site from which the method or constructor that requires a manifest is called.

scala> def foo[A: Manifest](a: A) = (manifest[A].erasure, a.asInstanceOf[AnyRef].getClass)
foo: [A](a: A)(implicit evidence$1: Manifest[A])(java.lang.Class[_], java.lang.Class[_])

scala> foo("")
res1: (java.lang.Class[_], java.lang.Class[_]) = (class java.lang.String,class java.lang.String)

scala> foo[AnyRef]("")
res2: (java.lang.Class[_], java.lang.Class[_]) = (class java.lang.Object,class java.lang.String)

scala> val a: Any = ""
a: Any = ""

scala> foo(a)
res3: (java.lang.Class[_], java.lang.Class[_]) = (class java.lang.Object,class java.lang.String)

Upvotes: 2

Related Questions