lambdista
lambdista

Reputation: 1905

How to cast Any to Array[T] maintaining Scala's invariance for arrays

I tried to implement the following function maintaining Scala's invariant view over the JVM arrays:

def cast[T](a: Any): Option[Array[T]] = ???

That is, given:

class Foo
class Bar extends Foo

I want the following examples to return Some:

val arr1: Any = Array(new Bar)
cast[Bar](arr1) // OK

val arr2: Any = Array(1, 2, 3)
cast[Int](arr2) // OK

val arr3: Any = Array("a", "b", "c")
cast[String](arr3) // OK

For the following one, instead, I want it to return None:

val arr: Any = Array(new Bar)
cast[Foo](arr1) // I want it to be None

val arr2: Any = Array(List(new Bar))
cast[List[Foo]](arr2) // this must be None too!

I tried through reflection using ClassTag/TypeTag with no luck, but since I'm no reflection expert I could be missing something.

P.S.: I know that having Any there, is a bad practice but, please, try to see the question academically. You know, just to find out if there's a way to do it and how to.

Update: The solution provided below still does not work for Array(List(new Bar)) because of type erasure.

Upvotes: 2

Views: 916

Answers (1)

Aldo Stracquadanio
Aldo Stracquadanio

Reputation: 6237

I think this is what you are after:

import scala.reflect.ClassTag

def cast[T](a: Any)(implicit ct: ClassTag[Array[T]]): Option[Array[T]] = ct.unapply(a)

In the REPL:

scala> val x: Any = Array(1, 2, 3)
x: Any = Array(1, 2, 3)

scala> val y = cast[Int](x)
y: Option[Array[Int]] = Some([I@7a8f55c)

scala> val z = cast[String](x)
z: Option[Array[String]] = None

The solution above doesn't keep invariance; to do it the best way I found so far is this:

def cast[T](in: Any)(implicit ct: ClassTag[Array[T]]): Option[Array[T]] = 
  if (ct.runtimeClass == in.getClass) ct.unapply(in) else None

Not sure how nice it is but it works:

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

class Foo
class Bar extends Foo

val x: Any = Array(new Bar)

val y = cast[Foo](x)
val z = cast[Bar](x)

// Exiting paste mode, now interpreting.

defined class Foo
defined class Bar
x: Any = Array(Bar@6109a8cc)
y: Option[Array[Foo]] = None
z: Option[Array[Bar]] = Some([LBar;@7ccebaae)

Upvotes: 2

Related Questions