N2 ToolBox(跡地)

跡地です。引っ越しました。http://d.hatena.ne.jp/nosen

共変の罠

2009-10-17 10:15:12 | Scala
前回のエントリで

object A extends Key[A]
class A {
    def methodA = "a"
}

object B extends Key[B]

class B(inject:Injector) {
    val a = inject(A)
}

val component = Component.define(B, new B(_))
                         .define(A, new A)
val b = component(B)

こんな感じのサンプルを示したのですが、実は、これ、意図した通りに
コンパイラの型のチェックがかからないことが判明してしまいました。

val component = Component.define(B, new B(_))
                         .define(B, new A) //本当はエラー

これでもコンパイルが通ってしまうのです。第一引数がKey[B]なら第二引数は(Injector)=>B
でないとエラーになるようにしたかったのに。。

このとき、defineメソッドのシグネチャがどんな風になっているのかというと、
def define[T](key:Key[T], factory: (Injector)=> T) 
def define[T](key:Key[T], factory: => T) 

こんなふうになっていました。
問題はdefineの呼び出し時に明示的に型パラメータを指定していないことでした。
val component = Component.define[B](B, new B(_))
                         .define[A](B, new A) //コンパイルエラーになる

こうやって、型を明示的に指定してあげると、ちゃんとコンパイルエラーになります。
おそらく、型を指定しないと、このケースでは戻り値の型から型推論することができないので、
コンパイラは
val component = Component.define[Any](B, new B(_))
                         .define[Any](B, new A) 

こういう風に解釈しているのだと思われます。
そして、KeyもFunctionもTに関して共変な型であったがためにこれで
普通にコンパイル通ってしまっていたのかと。

Javaには共変、反変という概念がないので、こういうはまり方はScalaならではといえます。

いちいち型パラメータを明示的に指定しないといけない上に、
指定しないと中途半端にコンパイルが通ってしまうというのはよろしくないので、
対策を考えたいと思います。

最新の画像もっと見る

3 コメント

コメント日が  古い順  |   新しい順
Unknown (みずしま)
2009-10-22 17:48:15
> おそらく、型を指定しないと、このケースでは戻り値の型から型推論することができないので、

推論できないからというか、むしろ推論できてしまうのでコンパイルが通ったという方が正確かと。この場合だと、AとBの共通のスーパークラスとしてScalaObject型が型パラメータとして推論されます。ちなみに、scalaコマンドのオプション-Xprint:typerとかすると、どういう風に型が推論されたか等かがわかるので便利です

> Javaには共変、反変という概念がないので、こういうはまり方はScalaならではといえます。

Javaでもワイルドカード使った場合、一応は同じような問題が発生する可能性はあるかとは思います。

public class VarianceTest {
interface Lazy<T> { T force(); }
static class Key<T> {}
static class A
static class B
static Key A = new Key();
static Key B = new Key();

static class Component {
static <T> Component define(
Key<? extends T> key, Lazy<? extends T> factory
) {
return null;
}
}
public static void main(String[] args) {
//コンパイル通る
Component.define(A, new Lazy() { public B force() { return null; } });
}
}
返信する
Unknown (みずしま)
2009-10-22 17:52:03
すいません。Java版のサンプルコード後半の部分が途切れてしまっていました。正しくは、以下です。

public class VarianceTest {
interface Lazy<T> { T force(); }
static class Key<T> {}
static class A
static class B
static Key A = new Key();
static Key B = new Key();

static class Component {
static <T> Component define(
Key<? extends T> key, Lazy<? extends T> factory
) {
return null;
}
}
public static void main(String[] args) {
Component.define(A, new Lazy() { public B force() { return null; } });
}
}
返信する
Unknown (のせ)
2009-10-26 13:03:11
なるほど。。
確かにそうですね。AnyではなくScalaObjectに推論されるのですね。
-Xprint:typer試してみます。
返信する