使用反射和解释器动态解析字符串和 return Scala 中的函数

dynamically parse a string and return a function in scala using reflection and interpretors

我正在尝试动态解释作为字符串给出的代码。 例如:

val myString = "def f(x:Int):Int=x+1".

我正在寻找一种方法来 return 真正的功能: 例如:

val myIncrementFunction = myDarkMagicFunctionThatWillBuildMyFunction(myString)
println(myIncrementFunction(3))

将打印 4

用例:我想稍后在我的代码中使用该解释代码中的一些简单函数。例如,他们可以提供类似 def fun(x: Int): Int = x + 1 作为字符串的内容,然后我使用解释器 compile/execute 该代码,然后我希望能够使用这个 fun (x) 例如在地图中。

问题是我不知道该函数类型,这是大问题之一,因为我需要从 IMain 中转换回来。 我读过有关反射、类型系统等的内容,经过一番谷歌搜索后,我达到了这一点。我还检查了 twitter 的 util-eval,但我无法从文档和他们测试中的示例中看到太多内容,这几乎是一回事。

如果我知道类型,我可以做类似的事情

val settings = new Settings
val imain = new IMain(settings)
val res = imain.interpret("def f(x:Int):Int=x+1; val ret=f _ ")
val myF = imain.valueOfTerm("ret").get.asInstanceOf[Function[Int,Int]]
println(myF(2))

可以正常工作并打印 3 但我被我上面说的问题阻止了,我不知道函数的类型,这个例子工作只是因为我转换为我定义字符串时使用的类型用于测试 IMain 工作原理的函数。

你知道我实现这个功能的方法吗?

我是新手,如有错误请多多包涵

谢谢

你可以使用 twitter-util 库来做到这一点,检查测试文件: https://github.com/twitter/util/blob/b0696d0/util-eval/src/test/scala/com/twitter/util/EvalTest.scala

如果你需要使用 IMain,可能是因为你想使用带有自定义设置的解释器,你可以这样做:

一个。首先创建一个 class 来保存你的结果:

    class ResHolder(var value: Any)

b。创建一个容器对象来保存结果并将代码解释到该对象中:

    val settings = new Settings()
    val writer = new java.io.StringWriter()
    val interpreter = new IMain(settings, writer)

    val code = "def f(x:Int):Int=x+1"

    // Create a container object to hold the result and bind in the interpreter
    val holder = new ResHolder(null) 

    interpreter.bind("$result", holder.getClass.getName, holder) match {
       case Success => 
       case Error => throw new ScriptException("error in: binding '$result' value\n" + writer)
       case Incomplete => throw new ScriptException("incomplete in: binding '$result' value\n" + writer)
    }

    val ir = interpreter.interpret("$result.value = " + code)

    // Return cast value or throw an exception based on result
    ir match {
       case Success =>
          val any = holder.value
          any.asInstanceOf[(Int) => Int]

       case Error => throw new ScriptException("error in: '" + code + "'\n" + writer)
       case Incomplete => throw new ScriptException("incomplete in :'" + code + "'\n" + writer)
    }

好的,我设法实现了我想要的功能,我仍在寻找改进此代码的方法,但此代码段可以满足我的要求。

我使用了 scala 工具箱和 quasiquotes

import scala.reflect.runtime.universe.{Quasiquote, runtimeMirror}
import scala.tools.reflect.ToolBox

object App {
    def main(args: Array[String]): Unit = {
        val mirror = runtimeMirror(getClass.getClassLoader)
        val tb = ToolBox(mirror).mkToolBox()

        val data = Array(1, 2, 3)

        println("Data before function applied on it")
        println(data.mkString(","))


        println("Please enter the map function you want:")
        val function = scala.io.StdIn.readLine()
        val functionWrapper = "object FunctionWrapper { " + function + "}"
        val functionSymbol = tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])

        // Map each element using user specified function
        val dataAfterFunctionApplied = data.map(x => tb.eval(q"$functionSymbol.function($x)"))

        println("Data after function applied on it")
        println(dataAfterFunctionApplied.mkString(","))
    }
}

这是终端中的结果:

Data before function applied on it
1,2,3
Please enter the map function you want:
def function(x: Int): Int = x + 2
Data after function applied on it
3,4,5

Process finished with exit code 0

我想用评论详细说明前面的 并对解决方案进行评估:

import scala.reflect.runtime.universe.{Quasiquote, runtimeMirror}
import scala.tools.reflect.ToolBox

object Runtime {

  def time[R](block: => R): R = {
    val t0 = System.nanoTime()
    val result = block    // call-by-name
    val t1 = System.nanoTime()
    println("Elapsed time: " + (t1 - t0) + " ns")
    result
  }

    def main(args: Array[String]): Unit = {
        val mirror = runtimeMirror(getClass.getClassLoader)
        val tb = ToolBox(mirror).mkToolBox()
        val data = Array(1, 2, 3)

        println(s"Data before function applied on it: '${data.toList}")
        val function = "def apply(x: Int): Int = x + 2"
        println(s"Function: '$function'")
        println("#######################")

        // Function with tb.eval
        println(".... with tb.eval")
        val functionWrapper = "object FunctionWrapper { " + function + "}"
        // This takes around 1sec!
        val functionSymbol = time { tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])}

        // This takes around 0.5 sec!
        val result = time {data.map(x => tb.eval(q"$functionSymbol.apply($x)"))}
        println(s"Data after function applied on it: '${result.toList}'")

        println(".... without tb.eval")
        val func = time {tb.eval(q"$functionSymbol.apply _").asInstanceOf[Int => Int]}
        // This takes around 0.5 sec!
        val result2 = time {data.map(func)}
        println(s"Data after function applied on it: '${result2.toList}'")

    }
}

如果我们执行上面的代码,我们会看到以下输出:

Data before function applied on it: 'List(1, 2, 3)
Function: 'def apply(x: Int): Int = x + 2'
#######################
.... with tb.eval
Elapsed time: 716542980 ns
Elapsed time: 661386581 ns
Data after function applied on it: 'List(3, 4, 5)'
.... without tb.eval
Elapsed time: 394119232 ns
Elapsed time: 85713 ns
Data after function applied on it: 'List(3, 4, 5)'

只是强调做评估提取一个函数的重要性,然后应用于数据,没有结束再次评估,如答案中的评论所示。