mechanikos
mechanikos

Reputation: 777

Scala trouble with casting to generics

I tried to write some function which does different things according to the input type. For example, I do this:

def Foo[T](inList: List[String]): ArrayBuffer[T] = {
  val out: ArrayBuffer[T] = new ArrayBuffer[T]()
  inList.map ( x => {
    val str = x.substring(1)
    out += str.asInstanceOf[T]
  })
  out
}

but if I call this function with Foo[Long](new List("123","2342")), I get an ArrayBuffer with Strings, not Longs. Sorry for my noob question, i want understand scala and generics.

Upvotes: 2

Views: 324

Answers (2)

chemikadze
chemikadze

Reputation: 815

  1. Scala runs ontop of JVM, that does not know anything about generics, and concrete type of generic is not available at runtime.

So that, runtime equivalent of your code will look like

  def Foo(inList: List[String]): (ArrayBuffer[Object]) = {
    val out: ArrayBuffer[Object] = new ArrayBuffer[Object]()
    inList.map ( x => {
      val str=x.substring(1)
      out += str.asInstanceOf[Object]
    })
    (out)
  }
  1. asInstanceOf will not convert strings to longs, instead it will throw exception about incompatible types. Instead, you should supply your function with a conversion from strings to another type.

Summing up, your code should look like this:

  import scala.collection.mutable.ArrayBuffer

  // here we define how convert string to longs
  implicit def stringToLong(s: String) = s.toLong

  // now this function requires to have converter from string to T in context
  def Foo[T](inList: List[String])(implicit f: (String) => T): (ArrayBuffer[T]) = {
    val out: ArrayBuffer[T] = new ArrayBuffer[T]()
    inList.map { x => 
      val str = x.substring(1)
      out += f(str) // here we apply converter
    }
    out
  }

  // when function is called, appropriate implicit converter from context will be used
  Foo[Long](List("51", "42"))

Upvotes: 4

johanandren
johanandren

Reputation: 11479

Casting a String to a Long does not make it a Long, it is an error, so you should get a runtime error saying that you cannot cast String to Long. Since T does not place any restrictions on what it can be, you cannot expect to call any method that isn't on all objects (toString is for example). You could provide that function yourself (which is almost the exact signature of map, so you could just use that):

def convert[T](in: List[String])(convert: String => T): List[T] =
  in.map(convert)

Or make sure the objects are of a type that you know of the methods on (using a type bound):

trait CanConvertTo[T] {
  def convert: T
}

case class Example(value: String) extends CanConvertTo[Long] {
  def convert = value.toLong
}

def convert[T](in: List[CanConvertTo[T]]): List[T] = in.map(_.convert)

Upvotes: 1

Related Questions