Подтвердить что ты не робот

Получить TypeTag [A] из класса [A]

У меня есть метод createOld, который мне нужно переопределить, и я не могу его изменить. Я хотел бы использовать TypeTag для типа соответствия шаблону в createNew. Цель состоит в том, чтобы узнать, как вызвать createNew из createOld. Мое настоящее понимание заключается в том, что компилятор не имеет достаточной информации о типе A в createOld, если он еще не пришел с TypeTag[A].

object TypeTagFromClass {
  class C1
  class C2

  // How to get TypeTag[A] needed by createNew?
  def createOld[A](c: Class[A]): A = createNew ???

  def createNew[A : TypeTag]: A = {
    val result = typeOf[A] match {
      case a if a =:= typeOf[C1] => new C1()
      case a if a =:= typeOf[C2] => new C2()
    }
    result.asInstanceOf[A]
  }
}
4b9b3361

Ответ 1

Можно создать TypeTag из Class, используя отражение Scala, хотя я не уверен, что эта реализация TypeCreator абсолютно правильная:

import scala.reflect.runtime.universe._

def createOld[A](c: Class[A]): A = createNew {
  val mirror = runtimeMirror(c.getClassLoader)  // obtain runtime mirror
  val sym = mirror.staticClass(c.getName)  // obtain class symbol for `c`
  val tpe = sym.selfType  // obtain type object for `c`
  // create a type tag which contains above type object
  TypeTag(mirror, new TypeCreator {
    def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
      if (m eq mirror) tpe.asInstanceOf[U # Type]
      else throw new IllegalArgumentException(s"Type tag defined in $mirror cannot be migrated to other mirrors.")
  })    
}

Однако вам не нужен полный TypeTag, если вам не нужно проверять общие параметры и полную информацию типа Scala. Вы можете использовать ClassTag для этого:

def createNew[A: ClassTag]: A = {
  val result = classTag[A].runtimeClass match {
    case a if a.isAssignableFrom(classOf[C1]) => new C1()
    case a if a.isAssignableFrom(classOf[C2]) => new C2()
  }
  result.asInstanceOf[A]
}

Или с неявным сахаром:

implicit class ClassTagOps[T](val classTag: ClassTag[T]) extends AnyVal {
  def <<:(other: ClassTag[_]) = classTag.runtimeClass.isAssignableFrom(other.runtimeClass)
}

def createNew[A: ClassTag]: A = {
  val result = classTag[A] match {
    case a if a <<: classTag[C1] => new C1()
    case a if a <<: classTag[C2] => new C2()
  }
  result.asInstanceOf[A]
}

Это можно упростить, используя простой старый метод Java newInstance():

def createNew[A: ClassTag]: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A]

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

Вызов этого createNew из createOld намного проще, чем тот, у которого TypeTag s:

def createOld[A](c: Class[A]): A = createNew(ClassTag[A](c))

Ответ 2

Таким образом, это не очень безопасно и правильно (потому что вы не используете возможности системы типа scala), но вы можете сделать (используя свою логику) следующее:

def createNew[A](implicit t: TypeTag[A]): A = {
  val result: Any = t.tpe.toString match {
    case "C1" => new C1
    case "C2" => new C2
  }
  result.asInstanceOf[A]
}

createNew[C1] //> its all ok
createNew[C2] //> its all ok
createNew[C3] //> crashes here; lets pretend we got C3 class

Чтобы использовать его с createOld, просто передайте неявный аргумент:

def createOld[A](c: Class[A])(implicit t: TypeTag[A]): A = createNew[A]

createOld[C1] //> its all ok
createOld[C2] //> its all ok
createOld[C3] //> crashes here; lets pretend we got C3 class

Думаю, я не должен вам дважды говорить, что это не очень хорошо. Мы можем улучшить этот код, используя shapeless:

Позволяет создать полифункцию, которая имеет TypeTag в качестве аргумента:

 import shapeless._; import scala.reflect.runtime.universe._;

 def getTypeTag[T](implicit t: TypeTag[T]) = t //> to get TypeTag of a class

 // here is low prority implicit
 trait createPolyNewErr extends Poly1 {
   implicit def newErr[T] = at[T](_ => "Error can not create object of this class")
 } 

 object createPolyBew extends createPolyNewError {
   implicit def newC1 = at[TypeTag[C1]](_ => new C1)
   implicit def newC2 = at[TypeTag[C2]](_ => new C2)
 }

 createPolyNew(getTypeTag[C1]) //> success
 createPolyNew(getTypeTag[C2]) //> success
 createPolyNew(getTypeTag[C3]) //> String: Error can not create object of this class no crash!

Мы также можем написать функцию, чтобы не использовать функцию getTypeTag[T] каждый раз:

 def createPoly[T]
 (implicit t: TypeTag[T],
         cse: poly.Case[createPolyNew.type, TypeTag[T] :: HNil]) = cse(t)

 createPoly[C1] //> its all ok
 createPoly[C2] //> its all ok
 createPoly[C3] //> String: Error can not create object of this class no crash!