Scala cast to generic type
Asked Answered
D

3

4

I'm confused about the generic type. I expect that 2.asInstanceOf[A] is cast to the type A, meanwhile, it's cast to Int. Besides that, the input is java.lang.Long whereas the output is a list of Int (according to the definition the input and the output should be the same type). Why is that?

def whatever[A](x: A): List[A] = {
  val two = 2.asInstanceOf[A]
  val l = List(1.asInstanceOf[A],2.asInstanceOf[A])

  println(f"Input type inside the function for 15L: ${x.getClass}")
  println(f"The class of two: ${two.getClass}, the value of two: $two")
  println(f"The class of the first element of l: ${l.head.getClass}, first element value: ${l.head}")
  l
}

println(f"Returned from whatever function: ${whatever(15L)}")

the outupt:

Input type inside the function for 15L: class java.lang.Long
The class of two: class java.lang.Integer, the value of two: 2
The class of the first element of l: class java.lang.Integer, first element value: 1
Returned from whatever function: List(1, 2)
Deduction answered 4/3, 2020 at 7:41 Comment(0)
R
16

a.asInstanceOf[B] means:

Dear compiler;

Please forget what you think the type of a is. I know better. I know that if a isn't actually type B then my program could blow up, but I'm really very smart and that's not going to happen.

Sincerely yours, Super Programmer

In other words val b:B = a.asInstanceOf[B] won't create a new variable of type B, it will create a new variable that will be treated as if it were type B. If the actual underlying type of a is compatible with type B then everything is fine. If a's real type is incompatible with B then things blow up.

Rubberneck answered 4/3, 2020 at 8:2 Comment(2)
This is the sort of answer that makes me wish I had more than one vote :)Inflatable
One up! for such an intriguing approach as an answer.Physiology
H
3

Type erasure. For the purposes of type checking 2 is cast to A; but at a later compilation stage A is erased to Object, so your code becomes equivalent to

def whatever(x: Object): List[Object] = {
  val two = 2.asInstanceOf[Object]
  val l = List(1.asInstanceOf[Object],2.asInstanceOf[Object])

  println(f"Input type inside the function for 15L: ${x.getClass}")
  println(f"The class of two: ${two.getClass}, the value of two: $two")
  println(f"The class of the first element of l: ${l.head.getClass}, first element value: ${l.head}")
  l
}

2.asInstanceOf[Object] is a boxing operation returning a java.lang.Integer.

If you try to actually use the return value as a List[Long] you'll eventually get a ClassCastException, e.g.

val list = whatever(15L)
val x = list(0)

x will be inferred to be Long and a cast inserted to unbox the expected java.lang.Long.

Hamish answered 4/3, 2020 at 8:29 Comment(0)
P
1

The answer from @jwvh is on point. Here I'll only add a solution in case you want to fix the problem of safely converting an Int to an A in whatever, without knowing what A is. This is of course only possible if you provide a way to build a particular A from an Int. We can do this in using a type-class:

trait BuildableFromInt[+A] {
  def fromInt(i: Int): A
}

Now you only have to implicitly provide BuildableFromInt for any type A you wish to use in whatever:

object BuildableFromInt {
    implicit val longFromInt: BuildableFromInt[Long] = Long.box(_)
}

and now define whatever to only accept compliant types A:

def whatever[A : BuildableFromInt](x: A): List[A] = {
  val two = implicitly[BuildableFromInt[A]].fromInt(2)
  // Use two like any other "A"
  // ...
}

Now whatever can be used with any type for which a BuildableFromInt is available.

Prudish answered 4/3, 2020 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.