Scala type mismatch error even after checking type with isInstanceOf

Viewed 256

I have a function unlist taking as first argument a list of any type:

def unlist(xs: List[Any], ys: List[Any]): List[Any] = ...

and I call this function on the first element of an external list, which may or may not be a list of lists. Thus, I first need to check if this head element is itself a list, in which case I can call my unlist function. I do it with InsintanceOf method, like so:

...
if (xs.head.isInstanceOf[List[Any]]) unlist(xs.head, ys)

However, this does not compile because of a type mismatch on xs.head:

Error: type mismatch;
found   : Any
required: List[Any]

What am I doing wrong?


P.S.: since many of you have suggested to avoid type Any , I have to specify that this is part of a coding exercise aiming at having a function as general as possible

2 Answers

To illustrate Mateusz's comment

isInstanceOf is not being remembered

it is meant we would have to follow up with asInstanceOf like so

if (list.head.isInstanceOf[List[_]]) unlist(list.head.asInstanceOf[List[_]])

Pattern matching, as demonstrated by Tim, implicitly performs isInstanceOf/asInstanceOf combination. Also consider related answer.

If you are unable to refactor out Any, which is the weakest of types, maybe try to recover as much typing information as early as possible, perhaps like so

val lists: List[List[_]] = list.collect { case xs: List[_] => xs }
val ints: List[Int] = list.collect { case i: Int => i }
val strings: List[String] = list.collect { case s: String => s }

Note due to type erasure we cannot easily do much better than List[List[_]], for example, the following assertion passes

val list: List[Any] = List(List("woo", "hoo"), 42, "kerfuffle")
assert(list.head.isInstanceOf[List[Double]])

In this last case, a compiler warning will tell you that type argument Double in type List[Double] (the underlying of List[Double]) is unchecked since it is eliminated by erasure. It is usually not a good idea to ignore such warnings.

As mentioned in the comments, using Any like this is the sign of some bad design choices. But for the specific problem you have, you can change your if to a match like this:

def unlist(xs: List[Any], ys: List[Any]): List[Any] = ???

val xs: List[Any] = ???

xs.head match {
  case l: List[Any] => unlist(l, ???)
  case _ =>
}

The match checks that the head value is List[Any] then assigns the value to a variable l which has type List[Any] and can therefore be used in the unlist call.

Note: This only works because you are testing for List[Any]. You cannot test for a list of a specific type (e.g. List[Int]) because a process called type erasure removes the runtime information that would be required for this to work. The compiler will warn you when it can't implement a match for this reason.

The real solution to this question is to fix the design to use specific types rather than Any.

Related