【问题标题】:How to pass Type at Runtime to Polymorphic function scala如何在运行时将类型传递给多态函数scala
【发布时间】:2019-12-11 16:26:59
【问题描述】:

下面是一个示例。我需要将存储在 String 中的值转换为 Type 并将其传递给 scala 中的多态函数。

import scala.reflect.runtime.universe._
import scala.reflect.api

object Test {

    def convert[T](l: String)(implicit typeTag: TypeTag[T]): T = l.asInstanceOf[T]

    implicit def stringToTypeTag[A](name: String): TypeTag[A] = {
        val c = Class.forName(name)
        val mirror = runtimeMirror(c.getClassLoader)
        val sym = mirror.staticClass(name)
        val tpe = sym.selfType
        TypeTag(mirror, new api.TypeCreator {
            def apply[U <: api.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.")
        })
    }

    def main(args:Array[String]): Unit = {
        val typ = "Integer"
        val x = convert("10")(stringToTypeTag("java.lang." + typ))
        val y = convert("20")(stringToTypeTag("java.lang." + typ))

        println(x.getClass)
        println(y.getClass)

        val z = x + y
        println(z)

        // Expected OP 30

        val typ = "String"
        val x1 = convert("10")(stringToTypeTag("java.lang." + typ))
        val y1 = convert("20")(stringToTypeTag("java.lang." + typ))

        println(x1.getClass)
        println(y1.getClass)

        val z1 = x1 + y1
        println(z1)
        // Expected OP 1020

    }
}

预期 OP:整数时为 30,字符串时为 1020

【问题讨论】:

  • 问题到底是什么? - 但更重要的是,为什么?运行时反射极其困难且不安全,总体而言代码不会利用 Scala 的高级类型系统。因此,它的使用应仅限于绝对必要的情况。 - 如果你能分享你原来的问题,也许我们可以推荐一种不同的方法。
  • 这是一个用于解释的示例。实际上我正在从配置文件加载各种参数,需要将值转换为从配置文件加载的类型。
  • 你看过pureconfig吗?
  • 这是我的完整场景 - args { runDate = { type = int, required = true } , schema = { type = String }。这是使用纯配置加载的。现在我打算使用现有的选项解析器 scpopt 或扇贝。要创建一个 argunemnt 选项,我需要调用 opt[String] 或 opt[Int],这个字符串 / int 来自配置,我需要将它们转换为类型,以便我可以将它们传递给这个函数和一个类型
  • 我认为如果您关闭此问题并使用当前问题、示例输入、预期输出和您尝试过的所有代码打开一个新问题会更好。

标签: scala generics types polymorphism scala-reflect


【解决方案1】:

这段代码有太多问题,我只说最明显的一个,因为更深层次的问题需要博客而不是答案。

那么,这个方法,

def convert[T](l: String)(implicit typeTag: TypeTag[T]): T = l.asInstanceOf[T]

这里最明显的问题是尝试使用l.asInstanceOf[T]String 转换为T。这不是事情的运作方式,您不能通过执行asInstanceOfString 转换为T。让我看看这个asInstanceOf什么时候可以使用。

// Lets say, there was an Int
scala> val i: Int = 10
// i: Int = 10

// but it was somehow assigned to a variable of type Any
scala> val a: Any = i
// a: Any = 10

// Now, even if we know that the value is an Int 
// but since the variable is Any, we can not do Int like things on it
scala> a / 2
// <console>:13: error: value / is not a member of Any .
//       a / 2
//         ^

// you can use `asInstanceOf` again access it as an Int
scala> a.asInstanceOf[Int] / 2
// res4: Int = 5

但只是因为value 已经是Int,所以variable 的类型是Any

您正在尝试做的是将value 类型为StringT 转换为一些T

除此之外,您将很多编译时的东西与运行时的东西混在一起,这由于很多原因都行不通。

您应该查看配置库,例如 lightbend-config 或 pureconfig。或如下的阅读器实现。

trait ConfigReader[A] {
  def read(input: String): A
}

object ConfigReader {

  object Implicits {

    implicit val intConfigReader = new ConfigReader[Int] {
      override def read(input: String): Int = input.toInt
    }

    implicit val doubleConfigReader = new ConfigReader[Double] {
      override def read(input: String): Double = input.toDouble
    }

    implicit val stringConfigReader = new ConfigReader[String] {
      override def read(input: String): String = input
    }

  }

  def read[A](input: String)(implicit configReader: ConfigReader[A]) = configReader.read(input)


}

import ConfigReader.Implicits._

val i = ConfigReader.read[Int]("5")

val d = ConfigReader.read[Double]("5.0")

【讨论】:

  • 直到运行时我才知道数据类型,所以我需要为所有场景编写隐式...
  • 我认为值得澄清的是,运行时没有 types,只有 classesisInstanceOf 在后者上工作。因此,在您的示例中,i 不是 Int 类型,而是 Any。但是它的类是 Int 这就是 asInstanceOf 起作用的原因。
  • @arjunchopra 当你说只有运行时知道 String 应该是什么类型的那一刻,事情变得非常复杂。方法是将事情提升到您自己的“Repr 实施”中。但是你需要确保你的“Repr implelmentation”支持你需要的所有操作,并且只有当你不再需要它时(当保存到数据库或打印等时)你才映射出你的提升上下文。 Slick 拥有最好的提升上下文之一,但您不需要任何远程复杂的东西来实现您的目的。
【解决方案2】:

这是行不通的,因为函数多态性是在编译时完成的,而不是在运行时完成的。所以你不能根据从文件中读取的类型的名称来选择多态函数。

潜在的问题是变量的类型是在编译时确定的。所以编译器必须在从配置中读取类型之前选择xy 的类型。由于xy 可能是多种类型之一,因此编译器可能会选择Any

到目前为止,这在理论上是可以的,因为Any 变量可以保存IntString。但是当您尝试添加它们时出现问题:x + y。这告诉编译器将Any 添加到Any,这显然会很糟糕。该程序不使用xy 的运行时类型来选择适当的+ 函数。

【讨论】:

    【解决方案3】:

    考虑到 cmets 中给出的新需求详细信息,您可以执行以下操作。

    import scala.reflect.ClassTag
    import scala.util.{Try, Success, Failure}
    
    case class Repr[+A: ClassTag](value: A)
    
    def readInputAsRepr(
      input: String,
      valueType: String
    ): Try[Repr[Any]] =
      valueType match {
        case "Int" => Try(Repr[Integer](input.toInt))
        case "Double" => Try(Repr[Double](input.toDouble))
        case "String" => Try(Repr[String](input))
        case _ => Failure(new UnsupportedOperationException)
      }
    
    def intSumAction(i1: Int, i2: Int) = i1 + i2
    def doubleSumAction(d1: Double, d2: Double) = d1 + d2
    def stringSumAction(s1: String, s2: String) = s1 + s2
    
    def selectAndEvaluateAction(
      repr1: Repr[Any],
      repr2: Repr[Any]
    ): Try[Repr[Any]] =
      (repr1.value, repr2.value) match {
        case (a1: Int, a2: Int) => Success(Repr(intSumAction(a1, a2)))
        case (a1: Double, a2: Double) => Success(Repr(doubleSumAction(a1, a2)))
        case (a1: String, a2: String) => Success(Repr(stringSumAction(a1, a2)))
        case _ => Failure(new UnsupportedOperationException)
      }
    
    val x1 = readInputAsRepr("10", "Int").get
    val y1 = readInputAsRepr("20", "Int").get
    val z1 = selectAndEvaluateAction(x1, y1)
    
    val x2 = readInputAsRepr("10", "String").get
    val y2 = readInputAsRepr("20", "String").get
    val z2 = selectAndEvaluateAction(x2, y2)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-03
      • 2019-06-04
      • 1970-01-01
      • 2021-10-23
      • 2011-12-01
      • 1970-01-01
      相关资源
      最近更新 更多