読者です 読者をやめる 読者になる 読者になる

kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

implicit conversionの適用される箇所

Scala

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)))
    }