Scalaの型推論システムを破るシンプルなJavaコード

単純なJavaコード:汎用インターフェース、それを実装するクラス、およびそのインスタンスを取得するメソッド:


//Gen.java: public interface Gen<A> { A value(); } //GenInt.java: public class GenInt implements Gen<Integer> { private final int i; public GenInt(int i) { this.i = i; } @Override public Integer value() { return i; } } //GenTest.java: public class GenTest { public static <A extends Gen<T>, T> T test(A a) { return a.value(); } public static void main(String[] argv) { GenInt g = new GenInt(42); Integer i = test(g); } } 

コンパイルして起動します。 Scalaからtestメソッドを呼び出したい場合はどうなると思いますか?


 object TestFail extends App { val genInt = new GenInt(42) val i = GenTest.test(genInt) } 

コンパイルして、すべてが悪いことを確認しようとしています。


 Error:(3, 11) inferred type arguments [GenInt,Nothing] do not conform to method test's type parameter bounds [A <: Gen[T],T] GenTest.test(genInt) Error:(3, 16) type mismatch; found : GenInt required: A GenTest.test(genInt) 

これが、強力なScala型システムが、通常Javaをダイジェストするジェネリックメソッドについて分類する方法です。


どうしたの?


Javaとは異なり、Scalaは親クラスから一般的なパラメーターを導出できません。 Javaには何もなかったからかもしれませんか? 知っているなら、教えてください。


UPD: Scala型宣言システムでは宣言サイトの分散を使用しているのに対し、Javaではuseサイトの分散、つまりScalaは宣言からではなく宣言から典型的なパラメーターを導出しようとすることを暗示するコメントのdarkdimius呼び出します。 また、元の例がDottyで機能することを喜ばしく思います。


型システムで2つを混在させると、非常に危険であり、さらには型推論システム(無害)でさらに危険です。

さらにそれと一緒に暮らすには?


もちろん、このような場合は、メソッドを呼び出すときに通常のパラメーターを明示的に指定できます。


 object TestExplicit extends App { val genInt = new GenInt(42) GenTest.test[GenInt, Integer](genInt) } 

しかし、あなたは認めなければなりません、これはまだ私たちが望んだものではありません。


そして、親クラスGen[T]が私たちに合わないものは何ですか? まず、それはそれ自体のサブタイプではないため、引数がサポートする型境界に対応していません。 第二に、そうすることで、元のタイプAを失いますが、必要になるかもしれません。


回避策


依存型が助けになります。


継承者Gen[T]のクラスタイプをGenS[T]ラッパーに依存するものとして保存します。


 trait GenS[T] extends Gen[T] { type SELF <: GenS[T] def self: SELF } class GenIntS(i: Int) extends GenInt(i) with GenS[Integer] { type SELF = GenIntS def self: SELF = this //       } 

これで、静的に保存されているため、元の型を失うことを恐れずに、親型の下のGenS[T]特性の相続人のオブジェクトを安全に受け入れることができます。


GenTest.testメソッドのラッパーを作成して、コンパイラーが型を推測できるようにします。


 object TestWrapped extends App { def test[T](g: GenS[T]): T = { GenTest.test[g.SELF, T](g.self) } private val v = new GenIntS(42) val i = test(v) } 

おわりに


説明したアプローチは理想的ではありません。クラスのラッパーを記述する必要がありますが、それでも各呼び出しのすべての一般的なパラメーターを明示的に示す必要がなく、JavaライブラリーのScalaラッパーを記述するときに役立ちます。


また、一般化されたインターフェイスが引数から直接派生していない場合、たとえばメソッドがタイプClass[A]とる場合、それを簡単に装飾することはできず、他のトリックに頼らなければならない場合、それに困難があることに注意する価値があります。



Source: https://habr.com/ru/post/J335812/


All Articles