Динамически анализировать строку и возвращать функцию в Scala, используя отражение и интерпретаторы

Я пытаюсь динамически интерпретировать код, заданный как String. Например:

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

Я ищу метод, который будет возвращать реальную функцию из этого: Например:

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

напечатает 4

Вариант использования: я хочу использовать некоторые простые функции из этого интерпретируемого кода позже в моем коде. Например, они могут предоставить что-то вроде def fun(x: Int): Int = x + 1 в виде строки, затем я использую интерпретатор для компиляции / выполнения этого кода, а затем я бы хотел использовать эту забаву (x) на карте, например.

Проблема в том, что этот тип функции мне неизвестен, и это одна из больших проблем, потому что мне нужно откатиться из IMain. Я читал об отражении, системе типов и тому подобном, и после некоторого поиска в Google достиг этой точки. Также я проверил утилиту twitter-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 работает.

Знаете ли вы какой-нибудь способ, как я мог бы достичь этой функциональности?

Я новичок, поэтому, пожалуйста, извините, если я написал какие-либо ошибки.

Спасибо

3 ответа

Решение

Хорошо, мне удалось достичь желаемой функциональности, я все еще ищу способ улучшить этот код, но этот фрагмент кода выполняет то, что я хочу.

Я использовал набор инструментов Scala и квазицитаты

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

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

Для этого вы можете использовать библиотеку twitter-util, проверьте тестовый файл: https://github.com/twitter/util/blob/develop/util-eval/src/test/scala/com/twitter/util/EvalTest.scala

Если вам нужно использовать IMain, возможно, потому что вы хотите использовать intepreter со своими собственными настройками, вы можете сделать что-то вроде этого:

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

    class ResHolder(var value: Any)

б. Создайте контейнерный объект для хранения результата и интерпретируйте код в этом объекте:

    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)
    }
Другие вопросы по тегам