Scalaを使ったプラガブルな実装

昔作ったScalaの言語構文とEvalの仕組みを利用した自作プラグインの作り方を紹介します。

Evalする部分については、こちらの記事を参考にさせて頂きました。
http://d.hatena.ne.jp/xuwei/20140607/1402128646

package parsers

import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
import java.io.File
import scala.io.Source

object Parser {
  def eval[T](code: String): T = {
    val toolbox = currentMirror.mkToolBox()
    val tree = toolbox.parse(code)
    toolbox.eval(tree).asInstanceOf[T]
  }

  def evalFile[T](file: File): T = {
    eval[T](Source.fromFile(file).getLines.mkString("\n"))
  }
} 

次に、Pluginの基底クラスを用意します。

package plugins

trait Plugin {
  def abstractMethod()
}

次は、Pluginの実装ファイル(PluginImpl.scala)を用意します。

new plugins.Plugin {  
  def abstractMethod() {
    println("hello")
  }
}

最後に、動的にファイルを実行してPluginをインスタンス化し、Plugin#abstractMethodを呼び出すメインとなるコードを実装します。

object Main {
  def main(args: Array[String]) {
    val pluginFile = new java.io.File("./PluginImpl.scala")
    val plugin = parsers.Parser.evalFile[plugins.Plugin](pluginFile)
    plugin.abstractMethod
  }
}

scalaファイルはコンパイルしてjarにし、PluginImpl.scala を同じ階層に配置して実行してみます。

$ java -jar Main.jar
hello

実際のシステムに実装する際には、特定のディレクトリ以下の.scalaファイルをすべて読んでインスタンス化したり、
一度読んだ後も、定期的にファイルのタイムスタンプをポーリングし、アプリケーションの再起動なしにプラグインをリロードできるようにしていました。
タイプセーフな設定ファイルなどもこの仕組みを使って実装し、システムを堅牢にするのに一役買っていました。

使いにくいScalaライブラリ

ScalaのHTTPライブラリってイケてないと思うんです。
何が気にくわないかというと、全体的にScalaのライブラリって、俺俺DSLを開発していて、
本当に使い方が分かりにくいと思うんです。

たとえば昔の Dispatch。(極端な例として、dispatch-classicのサンプルを記載します。)

// ex) http://dispatch-classic.databinder.net/URLs+and+Paths.html
import dispatch._
val sl = :/("www.scala-lang.org")
val learnScala = sl / "node" / "1305"

// ex) http://dispatch-classic.databinder.net/Response+Bodies.html
Http(learnScala >- { str =>
  str.length
})

もはやね。なんのこっちゃですよ。暗号ですか。
POSTリクエストを送りたくなってもどうしたらよいか全く想像がつきません。

私は、サンプルからオプションの使い方まで想像できるのが
使いやすいライブラリと考えています。

新しいDispatchではこのあたりは割と改善されているのですが、
ORMapperのSlick等も含めて、分かりにくい俺俺DSLを作り上げてしまうのはなんとかなりませんかね。
ライブラリの利用を通じて、タイプ量が少ない、Syntaxが短い事と、
シンプルで使いやすい事はイコールではない、ということを痛感しました。