Scala asInstanceOf с параметризованными типами

Я хотел бы написать функцию, которая отбрасывает тип A, где A может быть, например, Список [Int] или более сложный параметризованный тип, такой как Map [Int, List [Int]].

def castToType[A](x: Any): A = {
  // throws if A is not the right type
  x.asInstanceOf[A]
}

Прямо сейчас, из-за стирания типа (я считаю), код весело работает, даже если тип неверен. Ошибка проявляется только при доступе, с ClassCastException.

val x = List(1, 2, 3)
val y = castToType[List[String]](x)
y(0) --> throws java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

Есть ли способ использовать манифесты для правильной работы? Спасибо!

Ответ 1

К сожалению, это связано с присущим ему ограничением asInstanceOf. Я действительно удивлен, увидев, что скаладок упоминает об этом в подробнее:

Обратите внимание, что успех приведения во время выполнения по модулю Scala семантика стирания. Поэтому выражение 1.asInstanceOf[String] будет вызывать ClassCastException во время выполнения, тогда как выражение List(1).asInstanceOf[List[String]] не будет. В последнем примере, поскольку аргумент типа удаляется как часть компиляции, невозможно проверить, является ли содержимое списка запрошенным типом.

Если вы в основном обеспокоены ошибкой в ​​неправильном использовании для прохождения, что, вероятно, будет основной проблемой при возвращении материала из вашего интерфейса DB/memcached, я играл вокруг, заставляя отливку головы для объектов, проходящих через объект:

def failFastCast[A: Manifest, T[A] <: Traversable[A]](as: T[A], any: Any) = { 
  val res = any.asInstanceOf[T[A]]
  if (res.isEmpty) res 
  else { 
    manifest[A].newArray(1).update(0, res.head) // force exception on wrong type
    res
  }
}

На простом примере он работает:

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

scala> failFastCast(List[String](), x)
java.lang.ArrayStoreException: java.lang.Integer
[...]

scala> failFastCast(List[Int](), x)
res22: List[Int] = List(1, 2, 3)

Но не на более сложном:

val x = Map(1 -> ("s" -> 1L)): Any
failFastCast(Map[Int, (String, String)](), x) // no throw

Интересно, есть ли способ рекурсивного сверления вниз в A, чтобы продолжить кастинг, пока не будет больше параметров типа...

Ответ 2

Вы действительно правы - стирание типа означает, что вы не можете "отличать" таким образом, чтобы различать List[Int] и List[String], например. Тем не менее, вы можете улучшить выполняемый литой, в результате чего A стирается таким образом, что вы не можете различать Int и String:

def cast[A](a : Any) = a.asInstanceOf[A]
//... is erased to
def erasedCast(a : Any) = a.asInstanceOf[Any]

Что вам нужно, это reified generics, используя манифесты

def cast[A <: AnyRef : Manifest](a : Any) : A 
  = manifest[A].erasure.cast(a).asInstanceOf[A]

Пока окончательное приведение стирается до AnyRef, по крайней мере, у вас должен быть правильный экземпляр Class[_] (manifest.erasure), чтобы получить правильный тип верхнего уровня. В действии:

scala> cast[String]("Hey")
res0: String = Hey

scala> cast[java.lang.Integer]("Hey")
  java.lang.ClassCastException
    at java.lang.Class.cast(Class.java:2990)
    at .cast(<console>:7)
    at .<init>(<console>:9)

scala> cast[List[String]](List("Hey"))
res2: List[String] = List(Hey)

scala> cast[List[Int]](List("Hey"))
res3: List[Int] = List(Hey)

Мой совет - не использовать вложенное отражение, чтобы решить, действительно ли цель была List[Int]: это, как правило, невозможно. Для чего следует следующее возвращение?

cast[List[Int]](List[AnyVal](1, 2))

Ответ 3

Вы можете использовать shapeless Тип из Miles Sabin:

Набирать тип с использованием параметра типа

Он обрабатывает стирание во многих случаях, хотя только определенные:

scala> import shapeless._; import syntax.typeable._
import shapeless._
import syntax.typeable._

scala> val x = List(1, 2, 3)
x: List[Int] = List(1, 2, 3)

scala> val y = x.cast[List[String]]
y: Option[List[String]] = None

Чтобы увидеть набор случаев, с которыми он справляется, вы можете обратиться к его источнику:

https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala

Ответ 4

Да, проблема возникает из-за стирания типа. Если вы попробуете

val x = List(1,2,3)
val y = castToType[Int](x)

Исключение выбрано сразу, как и ожидалось. То же самое происходит при попытке применить к Array[String] или даже Array[Int].

Я не думаю, что вы можете создать универсальный конвертер типов, который будет работать внутри коллекций и других объектов. Вам потребуется создать конвертер для каждого типа объекта. Например:

def castToType[A](x: List[A]) = x.map(i => i.asInstanceOf[A])

Ответ 5

Рассмотрим это решение:

trait -->[A, B] {
  def ->(a: A): B
}

implicit val StringToInt = new -->[String, Int] {
  def ->(a: String): Int = a.toInt
}

implicit val DateToLong = new -->[java.util.Date, Long] {
  def ->(a: java.util.Date): Long = a.getTime
}

def cast[A,B](t:A)(implicit ev: A --> B):B= ev.->(t)

Преимущество состоит в том, что:

  • Это безопасный тип - компилятор скажет вам, не может ли тип быть литым
  • Вы можете определить правила кастинга, указав правильные импликации

Теперь вы можете использовать его так:

scala>  cast(new java.util.Date())
res9: Long = 1361195427192

scala>  cast("123")
res10: Int = 123

ИЗМЕНИТЬ

Я потратил некоторое время и написал этот расширенный код. Сначала позвольте мне показать, как его использовать:

scala>    "2012-01-24".as[java.util.Date]
res8: java.util.Date = Tue Jan 24 00:00:00 CET 2012

scala>    "2012".as[Int]
res9: Int = 2012

scala>    "2012.123".as[Double]
res12: Double = 2012.123

scala>    "2012".as[Object]   // this is not working, becouse I did not provide caster to Object
<console>:17: error: could not find implicit value for parameter $greater: -->[String,Object]
"2012".as[Object]
^

Довольно приятно? См. Магию scala:

trait -->[A, B] {
  def ->(a: A): B
}

implicit val StringToInt = new -->[String, Int] {
  def ->(a: String): Int = a.toInt
}

implicit val StringToDate = new -->[String, java.util.Date] {
  def ->(a: String): java.util.Date = (new java.text.SimpleDateFormat("yyyy-MM-dd")).parse(a)
}

implicit val StringToDouble = new -->[String, Double] {
  def ->(a: String): Double = a.toDouble
}

trait AsOps[A] {
  def as[B](implicit > : A --> B): B
}

implicit def asOps[A](a: A) = new AsOps[A] {
  def as[B](implicit > : A --> B) = > ->(a)
}