id:NetPenguinさんのimplicit conversions とブロックの組み合わせは失敗する?を以前に見たときに、「あれ?以前自分が同じことを試したときはうまくいったんだけどなあ…」と思って、色々試していたところ、どうもコンストラクタの引数と通常のメソッドの引数の場合とで、implicit conversionが適用される箇所に違いが現れるようだ。
まず、検証のために、以下のようなコードを書いてみた。
import javax.swing.SwingUtilities._ object Block { implicit def toRunnable(block: => Unit) = new Runnable { def run = block } def main(args: Array[String]) { new Thread({ println("foo"); println("bar") }) invokeLater({ println("foo"); println("bar") }) } }
このコードでは、toRunnableでUnit型の式からRunnable型への変換を定義しておいて、Runnable型が要求される箇所でUnit型の式が現れたらRunnable型へ変換されることを意図している。このコードを-Xprint:typer(コンパイラの処理途中の結果が見られるオプション)でコンパイルしてみると、次のような出力が得られた(本題に関係無い部分の出力は省いてある。
def main(args: Array[String]): Unit = { new java.this.lang.Thread(Block.this.toRunnable({ scala.this.Predef.println("foo"); scala.this.Predef.println("bar") })); javax.swing.SwingUtilities.invokeLater({ scala.this.Predef.println("foo"); Block.this.toRunnable(scala.this.Predef.println("bar")) }) }
これを見ると一目瞭然だが、Threadのコンストラクタ引数に渡された式の方は、ブロック全体に対してtoRunnableが適用されているのに対して、invokeLaterの引数に渡された式の方は、ブロックの最後の式に対してtoRunnableが適用されている。こうなる原因は実は理解しきれていないのだが、おそらくはコンストラクタ引数とメソッドの引数とで、型推論の方法が異なることによるものではないかと思う(後でScalaの仕様書を読んでみる)。ちなみに、下のようにして、:Unitとして、ブロック全体の型をUnit型だと明示してやると、メソッド引数の場合でもちゃんと意図通りにimplicit conversionが行われる。
import javax.swing.SwingUtilities._ object Block { implicit def toRunnable(block: => Unit) = new Runnable { def run = block } def main(args: Array[String]) { new Thread({ println("foo"); println("bar") }) invokeLater({ println("foo"); println("bar") }:Unit) } }
def main(args: Array[String]): Unit = { new java.this.lang.Thread(Block.this.toRunnable({ scala.this.Predef.println("foo"); scala.this.Predef.println("bar") })); javax.swing.SwingUtilities.invokeLater(Block.this.toRunnable(({ scala.this.Predef.println("foo"); scala.this.Predef.println("bar") }: Unit))) }