Creating an Array[T] from T without loosing type T information

Seem to have painted myself in to a corner. I am trying to use a container
class that generically allows me to wrap and unwrap values of
different types used in a Scala collection.

I have the this class:

  sealed trait Val[V] {
    val v: V

    override def toString: String = s"Val($v)"

    override def equals(obj: scala.Any): Boolean = {
      if (obj.isInstanceOf[Val[V]]){
        val other = obj.asInstanceOf[Val[V]]
        v.equals(other.v)
      }
      else
        false
    }

    def arr(n: Int)(implicit tag: ClassTag[V]): Array[V] = Array.ofDim[V](n)
    def vec(n: Int)(implicit tag: ClassTag[V]): Vector[V] = arr(n).toVector
    def arrX(n: Int)(implicit tag1: ClassTag[V], tag2: TypeTag[V]): Array[V] = {
      Array.ofDim[V](n)
    }

  }

I use the following functions to pack and unpack the values:

  implicit def pack[A:TypeTag](s:A) : Val[A] = new Val[A] {
    override val v: A = s
  }

  implicit def unpack[T:TypeTag](t:Val[_]): Either[String,T] = {
    try {
      val v = t.v.asInstanceOf[T]
      Right(v)
    } catch {
      // When used via implicits should never reach this
      case e: Exception => Left(e.getMessage)
    }
  }

Now I use the following function to see what type information is available:

  def paramInfo[T: TypeTag](x: T): Unit = {
    val targs = typeOf[T] match { case TypeRef(_, _, args) => args }
    println(s"type of $x (${x.getClass}) has type arguments $targs")
  }

So now I wrap and unwrap a String value so:

    val avt:Val[_] = "100"
    print("pack Val[_]: "); paramInfo(avt.v)
    val tavt: Either[String, Val[_]] = unpack(avt)
    print("unpack Val[_]: "); paramInfo(tavt.right.get)

And I get this output:

pack Val[_]: type of 100 (class java.lang.String) has type arguments List()
unpack Val[_]: type of 100 (class java.lang.String) has type arguments List(_$8)

So far so good. Now I try to create an Array[String] from the Val[String] so:

   //val at: Val[Array[_]] = avt.arr(5)
    val at: Val[Array[_]] = avt.arrX(5)
    paramInfo(at)

which results in:

type of Val([Ljava.lang.Object;@2219f3b5) (class pt.inescn.etl.stream.LoadX$$anon$5) has type arguments List(Array[_])

I can see that we have an Array, but not an array of String. So this won’t compile:

    val arr:Array[String] = aut.right.get

and this won’t execute:

    val arr = aut.right.get

Strangely enough the error is:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

So 2 questions:

  • how did the compiler know it was a String?
  • how can I get this to run?

Any other comments are welcome.

TIA

This may help:

https://stackoverflow.com/questions/6085085/why-cant-i-create-an-array-of-generic-type

Thanks for the link. I have already scoured the net for posts related to type erasure, hence the
use of class and type tags. Note that I am already creating the array with the class tag. Seems
like the issue is when unpacking the array, i.e: when casting to an array. More concretely this
seems to be related to dependent types: when I type the wrapper as a Val[_] instead of
Val[Array[String]] I assumed that the type V in trait Val[T] would be available
to the JVM even if opaque to the compiler (contained element has type v.type).

I have kept experimenting and have found that:

  1. If I unpack to the correct type but cast to a more generic type Array[_] it works:
    val wvut = unpack[Array[String]](wvvat)
    println(wvut)
    val wvec:Array[_] = wvut.right.get
    println(s"wvec(0) = ${wvec(0)} @ ${wvec(0).getClass}")

gives me:

Right([Ljava.lang.Object;@2effaa62)
wvec(0) = 100 @ class java.lang.String
  1. When using Scala’s Vector this works:
    val vut = unpack(vvat) // Either[String, Nothing] ??
    println(vut)
    val vec:Vector[String] = vut.right.get
    println(s"vec(0) = ${vec(0)} @ ${vec(0).getClass}")

    val arrVut: Array[String] = vec.toArray
    println(s"arrVut = ${arrVut.mkString(",")}")

And I get:

Right(Vector(100, 100, 100, 100, 100))
vec(0) = 100 @ class java.lang.String
arrVut = 100,100,100,100,100

So it seems like the JVM retains the correct type. The compilation error I
referred to at the beginning of the post seems to be correct (compiler
knows it is an array, but the v.type is unknown.

However the failure too execute:

    val arr = aut.right.get

seems strange, especially when the compiler says.

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

especially when I have not indicated that I want a String (or any other type for that matter).
I think internally a cast for the array element are being made and failing. But why is it casting
to a String?