前回のエントリで
こんな感じのサンプルを示したのですが、実は、これ、意図した通りに
コンパイラの型のチェックがかからないことが判明してしまいました。
これでもコンパイルが通ってしまうのです。第一引数がKey[B]なら第二引数は(Injector)=>B
でないとエラーになるようにしたかったのに。。
このとき、defineメソッドのシグネチャがどんな風になっているのかというと、
こんなふうになっていました。
問題はdefineの呼び出し時に明示的に型パラメータを指定していないことでした。
こうやって、型を明示的に指定してあげると、ちゃんとコンパイルエラーになります。
おそらく、型を指定しないと、このケースでは戻り値の型から型推論することができないので、
コンパイラは
こういう風に解釈しているのだと思われます。
そして、KeyもFunctionもTに関して共変な型であったがためにこれで
普通にコンパイル通ってしまっていたのかと。
Javaには共変、反変という概念がないので、こういうはまり方は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ならではといえます。
いちいち型パラメータを明示的に指定しないといけない上に、
指定しないと中途半端にコンパイルが通ってしまうというのはよろしくないので、
対策を考えたいと思います。
推論できないからというか、むしろ推論できてしまうのでコンパイルが通ったという方が正確かと。この場合だと、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; } });
}
}
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; } });
}
}
確かにそうですね。AnyではなくScalaObjectに推論されるのですね。
-Xprint:typer試してみます。