CLOVER🍀

That was when it all began.

Scala 2.10.0 Dependent method types

以下の新機能紹介の部分には、
http://www.scala-lang.org/node/27499
こんな感じにしか紹介されていませんが、

def identity(x: AnyRef): x.type = x // the return type says we return exactly what we got

Dependent method typesについてちょっと試してみました。

とりあえず、こんなコードを用意。
DependentMethodType.scala

object DependentMethodType {
  def main(args: Array[String]): Unit = {
    val s = "Hello World"
    val xs = List(1, 2, 3)

    str(identity(s))
    list(identity(xs))
  }

  def identity(x: AnyRef): x.type = x

  def str(s: String): Unit = println(s)
  def list[T](xs: List[T]): Unit = println(xs)
}

Scala 2.9.2でコンパイルしてみます。

$ /usr/local/scala/scala-2.9.2/bin/scalac DependentMethodType.scala 
DependentMethodType.scala:10: error: illegal dependent method type
  def identity(x: AnyRef): x.type = x
               ^
DependentMethodType.scala:6: error: type mismatch;
 found   : x.type (with underlying type AnyRef)
 required: String
    str(identity(s))
                ^
DependentMethodType.scala:7: error: type mismatch;
 found   : x.type (with underlying type AnyRef)
 required: List[?]
    list(identity(xs))
                 ^
three errors found

そもそも、

  def identity(x: AnyRef): x.type = x

の部分のコンパイルが通らない。

では、Scala 2.10.0で。

$ /usr/local/scala/scala-2.10.0/bin/scalac DependentMethodType.scala 
$ /usr/local/scala/scala-2.10.0/bin/scala DependentMethodType.scala 
Hello World
List(1, 2, 3)

コンパイルが通りました、動きました。

というわけで、

  def identity(x: AnyRef): x.type = x

のように引数の型がAnyRefであっても、その引数に渡した型でメソッドの戻り値が返ってくるという話のようです。

でなければ、

  def str(s: String): Unit = println(s)
  def list[T](xs: List[T]): Unit = println(xs)

これらのメソッドの呼び出しを行っている以下の部分で、コンパイルエラーになるでしょうから…。

    val s = "Hello World"
    val xs = List(1, 2, 3)

    str(identity(s))
    list(identity(xs))

ちなみに、こういう表記にしてみると

    list(identity(List(1, 2, 3)))

コンパイルエラーになりました…。

error: type mismatch;
 found   : x.type (with underlying type AnyRef)
 required: List[?]
    list(identity(List(1, 2, 3)))
                 ^

1度変数に落とさないとダメ?

この他、少し調べてみると日本語でも1年以上前の日付のブログがヒットします。もしかして、だいぶ遅れてるのかなぁ…?

Stack Overflowに詳しく載っていたらしいです。
http://stackoverflow.com/questions/7860163/what-are-some-compelling-use-cases-for-dependent-method-types

出回ってた情報を、適当に変えて遊んでみました。

object Main {
  def main(args: Array[String]): Unit = {
    val i: Int = Foo.withFoo(Foo1)
    val s: String = Foo.withFoo(Foo2)

    println(i == 10)  // => true
    println(s == "Hello World")  // => true

    val i2: Foo1.Bar = Foo.withFoo(Foo1)
    val s2: Foo2.Bar = Foo.withFoo(Foo2)

    println(classOf[Foo1.Bar])  // => int
    println(classOf[Foo2.Bar])  // => class java.lang.String

    println(Out.intFoo(Foo1)(Foo1.v))  // => true
    println(Out.stringFoo(Foo2)(Foo2.v))  // => true

    // val i3: String = Foo.withFoo(Foo1)  // <= コンパイルエラー
    // val s3: Int = Foo.withFoo(Foo2)  // <= コンパイルエラー
    // println(Out.intFoo(Foo1)(Foo2.v))  // <= コンパイルエラー
    // println(Out.stringFoo(Foo2)(Foo1.v))  // <= コンパイルエラー
  }
}

object Out {
  def intFoo(f: Foo)(b: f.Bar) = b == 10
  def stringFoo(f: Foo)(b: f.Bar): Boolean = b == "Hello World"
}

object Foo {
  def withFoo(f: Foo): f.Bar = f.v
}

trait Foo {
  type Bar
  def v: Bar
}

object Foo1 extends Foo {
  type Bar = Int
  def v: Bar = 10
}

object Foo2 extends Foo {
  type Bar = String
  def v: Bar = "Hello World"
}

なるほどねー、引数の型とそれに関連するtypeキーワードの参照先(の型)が分かってるってことなのね。

とはいえ、コンパイラが分かってる型から解決してるっぽいので、最初の例で

    val any: AnyRef = "Hello World"
    str(identity(any))

みたいに意図的に型宣言を操作してしまうと、辻褄が合わなければコンパイルエラーですね。

 found   : any.type (with underlying type AnyRef)
 required: String
    str(identity(any))
                ^

最初の例で、Listを生成していきなりメソッドに渡した時にコンパイルエラーになったのは、この辺りの理由と同じなのかな?

いろんな機能があるんだなぁと…。