简体   繁体   中英

How to pass Type at Runtime to Polymorphic function scala

Below is a sample example. Where i need to convert value stored in String as a Type and pass that to polymorphic function in 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

    }
}

Expected OP: 30 when Integer and 1020 when String

There are too many problems in this code, I am stating just the most obvious one as the deeper problems require a blog and not an answer.

So, this method,

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

The most obvious problem here is trying to convert a String to T by using l.asInstanceOf[T] . This is not how things work, you can not just convert a String to a T by doing asInstanceOf . Let me show when is this asInstanceOf can be used.

// 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

But only because the value was already an Int , just the variable was of type Any .

What you are trying to do, it to cast a value of type String to some T .

Other than this you are mixing a lot of compile time things with run time things, which will not work out for a lot of reasons.

You should look into a config library such as lightbend-config or pureconfig. Or a reader implementation such as following.

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")

This is not going to work because function polymorphism is done at compile time, not run time. So you can't select a polymorphic function based on the name of a type that is read from a file.

The underlying problem is that the type of a variable is determined at compile time. So the compiler has to pick the type of x and y before the type is read from the configuration. Since x and y could one of a number of types, the compiler probably chooses Any .

This is theoretically OK so far because an Any variable can hold either Int or String . But things go wrong when you try to add them: x + y . This is telling the compiler to add an Any to an Any and this is clearly going to go badly. The program does not use the run-time type of x and y to pick the appropriate + function.

Considering the new requirement details given in the comments, you can do something like following.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM