【问题标题】:Scala - How to compile code from an external file at runtime?Scala - 如何在运行时从外部文件编译代码?
【发布时间】:2014-05-26 16:24:52
【问题描述】:

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

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

【问题讨论】:

    标签: scala scala-compiler external-dependencies


    【解决方案1】:

    以下是仅使用标准 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 有点像记录在案的here。除了weird imports,您真正需要知道的唯一一件事是toolbox.parse 将字符串(Scala 源代码)转换为抽象语法树,而toolbox.compile 将抽象语法树转换为带有签名@ 的函数987654333@。由于这是动态编译的代码,因此您必须将Any 转换为您期望的类型。

    【讨论】:

    • 解释得很清楚,谢谢。当您说GreenhouseFactory 需要导入将在外部访问的所有类型和符号时,您指的是在传递给parse 之前添加到fileContents 之前的导入字符串,对吧?还是说GreenhouseFactory 方法本身实际上需要导入所有内容?
    • 我的意思是fileContents前面的字符串。我现在就修改。
    • 好的。我需要关心文件的类型检查,还是已经在被调用的工具箱方法中处理过?
    • 如果出现任何语法或其他错误,ToolBox 方法将抛出运行时异常。如果外部文件中的new 没有生成与.make 中的转换一起使用的对象,那也会导致运行时异常。
    • 是否可以用类似的方式编译一串Java代码?
    【解决方案2】:

    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。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-26
    • 2022-07-15
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多