1

The following example:

class Test[A] {
  def print[B >: A](x : B): Unit = {
    println(x.isInstanceOf[A])
    println(x.isInstanceOf[B])
  }
}

val test = new Test[Int]
test.print("string")

returns true true (Which means that A, B are type of String)

When [B <: A] changed to upper bound - as expected the code doesn't compile

Can someone shed light on what is going on here? Thanks!

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Amit Paz
  • 63
  • 1
  • 6

2 Answers2

3

The reason that the constraint B >: A compiles in your example is that the compiler tries to look for the most specific type that satisfies the constraint. As Int is your lower bound, and String and Int both share a super type Any, the compiler allows this example to compile.

One thing you have to remember is that Scala runs on the JVM (well, at least the most common distribution). This means that we have type erasure for generic types.

If we compile your example and look at the post erasure phase we see:

class Test extends Object {
  def print(x: Object): Unit = {
    scala.this.Predef.println(scala.Boolean.box(x.$isInstanceOf[Object]()));
    scala.this.Predef.println(scala.Boolean.box(x.$isInstanceOf[Object]()))
  };
}

As you see, due to type erase, we're actually comparing x twice to Object, which is why you see true printed twice.

If you want to check for type equality, you'll need to provide implicit evidence of a TypeTag. A TypeTag provides type information at runtime. Provided, we can use typeOf with a =:= which checks equality of the specific type. We require a TypeTag either via context bounds, or explicitly via an implicit parameter (for this example, I'll use the former):

scala> :pa
// Entering paste mode (ctrl-D to finish)

import scala.reflect.runtime.universe._
class Test[A: TypeTag] {
  def print[B: TypeTag](x: B): Unit = {
    println(typeOf[B] =:= typeOf[A])
    println(typeOf[B] =:= typeOf[B])
  }
}

// Exiting paste mode, now interpreting.

import scala.reflect.runtime.universe._
defined class Test

scala> val t = new Test[Int]
t: Test[Int] = Test@7276c8cd

scala> t.print("s")
false
true
Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Super! Can you share how can you see what is going on in the various phases of compilation? Thanks!! – Amit Paz Jun 29 '16 at 07:18
  • 1
    @AmitPaz You can use the `scalac` flags to monitor the phases of the compilation. Do `scalac -Xprint:all FileName.scala` If you have questions, feel free to ask. – Yuval Itzchakov Jun 29 '16 at 07:30
0

If you want to do the equivalent of x.isInstanceOf[A], scala.reflect.ClassTag may be a better fit than TypeTag:

import scala.reflect.{ClassTag, classTag}
class Test[A: ClassTag] {
  def print[B >: A : ClassTag](x : B): Unit = {
    println(x match { case _: A => true; case _ => false })
    // or println(classTag[A].runtimeClass.isInstance(x))
    println(x match { case _: B => true; case _ => false })
  }
}

val test = new Test[Int]
test.print("string")

Like isInstanceOf, the above works with the actual value of x, not with the static type. You can see the difference by calling

val test = new Test[Some[Int]]
val x: Option[Int] = Some(1)
val y: Option[Int] = None
test.print(x)
test.print(y)

with this and the other answer. (But note that ClassTag doesn't handle type parameters: new Test[Some[String]] will give the same result).

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487