14

プログラムの実行をカスタマイズできるパラメーターとして Scala ファイルを受け入れる Scala プログラムを設計したいと考えています。特に、プログラムによって呼び出されるメソッドの実装を含む実行時ファイルを提供したいと考えています。外部ファイルに適切に依存し、実行時にそのメソッドを動的に呼び出すにはどうすればよいですか? 理想的には、これらのファイルがプログラムのメソッドとクラスに依存できるようにしたいと考えています。

シナリオ例: 行を含む関数がありval p: Plant = Greenhouse.getPlant()、メソッドGreenhouseを持つクラスは、getPlant実行時に提供されるファイルの 1 つで定義されます。そのファイルでは、メソッドは元のプログラムで定義されているを返しgetPlantます。ファイルが実行時にのみ結合され、コンパイル時には結合されないと仮定して、この相互依存性をどのように達成 (または概算) しますか?RoseRose <: 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に文書化されています。奇妙な imports以外に、本当に知っておく必要がある唯一のことは、文字列 (Scala ソース コード) を抽象構文木に変換し、抽象構文木を signature を持つ関数に変換することです。これは動的にコンパイルされるコードであるため、期待する型にキャストする必要があります。toolbox.parsetoolbox.compile() => AnyAny

于 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 ファイルには、 class自体ではなく、式を含める必要がありますが、基本的には次のように行うのは非常に簡単です。

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

私が理解しているように、Twitter はこの機能を使用して (または少なくとも使用して)、構成ファイルを properties/json/xml ではなく Scala として保持しています。

于 2014-05-26T19:15:20.063 に答える