14

我想设计一个 Scala 程序,它接受 Scala 文件作为可以自定义程序执行的参数。特别是,我想在运行时提供包含将由程序调用的方法实现的文件。如何正确依赖外部文件并在运行时动态调用它们的方法?理想情况下,我还希望这些文件能够依赖于我程序中的方法和类。

示例场景:我有一个包含 line 的函数val p: Plant = Greenhouse.getPlant(),并且Greenhouse具有该getPlant方法的类定义在将在运行时提供的文件之一中。在该文件中,该方法getPlant返回 a Rose,其中Rose <: PlantPlant在原始程序中定义。假设文件仅在运行时而不是在编译时加入,我如何实现(或近似)这种相互依赖性?

4

2 回答 2

25

以下是仅使用标准 Scala 的方法。不明显的东西都在GreenhouseFactory

package customizable

abstract class Plant

case class Rose() extends Plant

abstract class Greenhouse {
  def getPlant(): Plant
}

case class GreenhouseFactory(implFilename: String) {
  import reflect.runtime.currentMirror
  import tools.reflect.ToolBox
  val toolbox = currentMirror.mkToolBox()
  import toolbox.u._
  import io.Source

  val fileContents = Source.fromFile(implFilename).getLines.mkString("\n")
  val tree = toolbox.parse("import customizable._; " + fileContents)
  val compiledCode = toolbox.compile(tree)

  def make(): Greenhouse = compiledCode().asInstanceOf[Greenhouse]
}

object Main {
  def main(args: Array[String]) {
    val greenhouseFactory = GreenhouseFactory("external.scala")
    val greenhouse = greenhouseFactory.make()
    val p = greenhouse.getPlant()

    println(p)
  }
}

将您的覆盖表达式放在external.scala

new Greenhouse {
  override def getPlant() = new Rose()
}

输出是:

Rose()

唯一棘手的事情是GreenhouseFactory需要预先添加该import语句以提供对外部文件所需的所有类型和符号的访问。为了简单起见,用所有这些东西制作一个单独的包。

编译器ToolBox有点记录在这里除了奇怪的导入之外,您真正需要知道的唯一一件事是toolbox.parse将字符串(Scala 源代码)转换为抽象语法树,toolbox.compile并将抽象语法树转换为带有签名的函数() => Any。由于这是动态编译的代码,因此您必须忍受将其转换Any为您期望的类型。

于 2014-05-26T23:48:25.630 回答
7

Scala 本身并不提供这种功能。我知道的最简单的方法是使用 Twitter“util-eval”库。这个库包含了对 Scala 编译器和各种类加载程序的必要调用,为您节省了大量的精力。执行您所描述的调用序列看起来像

val eval = new Eval()
val greenhouse = eval.apply[Greenhouse](new File("path/to/MyGreenhouse.scala"))
val plant = greenhouse.getPlant()

您的动态加载的 Scala 文件需要包含一个表达式,而不是一个类本身,但这很容易做到,基本上就像这样。

new Greenhouse{
    def getPlant() = //thing to return the plant 
}

据我了解,Twitter 使用(或至少使用)此功能将其配置文件作为 Scala 而不是 properties/json/xml。

于 2014-05-26T19:15:20.063 回答