ジェネリックJava型の完全性のチューリング

時々、Haberの記事で、C ++テンプレートでできることについて説明しています: 有限状態マシンラムダ計算チューリングマシンなど







Javaのパラメータ化された型は、伝統的にC ++テンプレートのパロディと見なされます(それらを比較することさえ何らかの形で間違っているという事実にもかかわらず)、そしてこの理由は理解しやすいです。 それにもかかわらず、すべてがそれほど悪いわけではなく、十分なRAMがある場合にのみ、Javaコンパイラが型チェック中に計算を実行することを強制できます。 これを行う特定の方法は、 この美しい出版物で 2016年11月に説明されました。 説明したいと思います。







シードとして、次のコードを提供します。 彼は正しいですか? 結果を推測した場合は、コンパイルして確認することをお勧めします。







class Sample { interface BadList<T> extends List<List<? super BadList<? super T>>> {} public static void main(String[] args) { BadList<? super String> badList = null; List<? super BadList<? super String>> list = badList; } }
      
      





答えを見つける

コンパイラは、スタックのサイズに関係なくjava.lang.StackOverflowError



をスローしjava.lang.StackOverflowError









これらの理由を理解することは有用であり、チューリングマシンはどこで動作するのか、コンパイラが正確にこのように動作する理由を理解します(バグとは呼びません)。










1.共分散と反分散について



まず、基本についてお話しましょう。 パラメーター化された型を使用する最も簡単な方法は、次のようなものです。







 List<Integer> integerList = new ArrayList<>(); List<Number> numberList = new ArrayList<>();
      
      





通常、これで十分ですが、問題があります-要素のタイプは固定されています。 List<Number>



必要な場所でintegerList



オブジェクトを使用することはできません。そのようなリストにDouble



型の要素を挿入すると、その整合性がintegerList



れるためです。 逆に、 List<Integer>



が期待される場所でnumberList



使用することはできません。これは、整数以外の数字が入った場合にリストアイテムを読み取るときにClassCastException



するためです。 もちろん、2番目の問題を解決するには、この小さなトリックを使用できます。







 <T extends Number> Number firstNum(List<T> xs) { return xs.get(0); }
      
      





このアプローチでは、追加のT



型を導入する必要があるため、これをトリックと呼びますT



さらに、この方法での挿入の問題は原則として解決できません。 私の意見では、この方法のより適切なバージョンは次のようになります。







 Number firstNum(List<? extends Number> xs) { return xs.get(0); }
      
      





そのような型は、パラメータが共変であると言われています。 これは、 A



B



サブタイプである場合、 List<A>



List<? extends A>



)はList<? extends B>



サブタイプであることを意味しList<? extends B>



List<? extends B>



ます。つまり、それらの間で割り当てが許可されます。 副作用-そのようなリストは読み取り専用になり、オブジェクトの整合性に違反することはできません。

録音ケースには、別の構造があります。







 void addOne(List<? super Integer> xs) { xs.add(1); }
      
      





List<? super Integer>



List<? super Integer>



は、整数を追加できるリストのコレクションを表します。つまり、そのサブタイプはList<Integer>



List<Number>



およびList<Object>



(さらにList<? super Integer>



List<? super Number>



およびList<? super Object>



)。 このようなクラスはパラメーターが反変です。つまり、 A



B



スーパータイプである場合、 List<A>



List<? super B>



サブタイプList<? super B>



List<? super B>



。 状況は共変の場合とまったく逆です。







反変型に注目しましょう。










2.反変型のサブセットの形式化



それでは、Javaのどのタイプが私たちにとって興味があるかを判断しましょう。 概念を帰納的に紹介します。









より複雑な型は次のように記述されます C 1 C 2C n t それはこのコードに対応しています:

C1<? super C2<? super ...Cn<? super t>...>>









継承の関係を定義します。 それで、 タイプがあります

interface C<T> extends C1<C2<? super ...Cn<? super t>...>>



interface C<T> extends C1<C2<? super ...Cn<? super t>...>>



、この場合、パラメーターX



持たない任意のまたはクラスについて、次の継承関係が成り立つと言います。 C X < C 1、C 2C n x 。 記号 < * 関係の推移的閉包を示す < (つまり、直接に加えて、クラスのチェーンを介した間接的な継承)。

関係がある < * 有用な特性が1つあります-非周期性です。 Javaには多重継承がないため、次のように記述できます。









これで、最終的に関係を入力できます。 <







  1. タイプ T



    については、それが適切なサブタイプであると仮定します。 T<T
  2. 継承関係が満たされている場合 CT1<::C1C2...CnT1 そして、それは知られています T2<T1 その後実行 CT1<C1C2...CnT2 (注文と間違われませんでした T1 そして T2 、これはすべて反則です)。


この時点で、重大な制限が表示されます。 態度に < Javaのサブタイプを決定する標準的な方法と一致したため、段落2でn



の値n



奇数であることが必要です。







なぜそうなのか

私は完全に正式な証明をしません、それは不適切に長いでしょう。 例を挙げて状況を検討する方が正しいでしょう。







n = 1

interface S<T> extends C<T> {}





この場合、 S<? super X>



S<? super X>



C<? super X>



サブタイプC<? super X>



C<? super X>



、これはパラメーターT



直接の置換であるため







n = 3

interface S<T> extends C1<C2<C3<? super T>>> {}





それが真実であることを証明しましょう S X < C 1 C 2 C 3 X







  • タイプを取る Z そのような X < Z ;
  • それに続く C 3 Z < C 3 X ;
  • したがって C 2 C 3 X < C 2 C 3 Z ;
  • したがって C1C2C3Z<C1C2C3X ;
  • C1<C2<? super C3<? super Z>>>



    C1<C2<? super C3<? super Z>>>



    C1<C2<? super C3<? super Z>>>



    それはサブタイプです C1C2C3Z 、それはサブタイプでもあります C1C2C3X ;
  • つまり、 S<Z>



    はサブタイプです C1C2C3X ;
  • Capture#1 of (? super X)



    Z



    として考えると、真実が得られます SX<C1C2C3X 。 この結論は、JLS 4.10.2のおかげで有効です。

    ジェネリック型宣言がある場合 C<F1...Fn> (n> 0)、パラメーター化された型の直接のスーパータイプ C<R1...Rn> ここで、少なくとも1つ Ri1 leqi leqn はワイルドカード型の引数であり、パラメータ化された型の直接のスーパータイプです C<X1...Xn> これは、キャプチャ変換を適用した結果です C<R1...Rn> (§5.1.10)



n = 2

interface S<T> extends C1<C2<? super T>> {}





と仮定する SX<C1C2X







  • この場合、タイプZ = Capture of (? super X)



    (つまり、 X<ZS<Z>



    がサブタイプであるようなもの C1C2X ;
  • この場所は議論の余地があるように見えるかもしれません。 S<Z>



    C1<C2<? super Z>>



    サブタイプだからだと思いC1<C2<? super Z>>



    C1<C2<? super Z>>



    、次にC1<C2<? super Z>>



    C1<C2<? super Z>>



    もサブタイプです C1C2X ;
  • 前の段落から C2X<C2Z ;
  • したがって Z<X それは確かに真実ではありません。 矛盾が生じました。


n> 2の場合も同様に考慮されます。







これで、後で証明される次の定理を述べることができます。







定理1

与えられた2つのタイプのアルゴリズムはありません T1 そして T2 ステートメントが真かどうかを判断できます T1<T2

つまり、Javaでは一般に 、あるタイプが別のタイプのサブタイプであるかどうか判断すること できません










3.チューリングマシンとは



チューリングマシン  tau 5つです QqIqH\シ\デ どこで Q 状態の有限集合です qI inQ 初期状態です qH inQ 最終状態です \シ 最後のアルファベットであり、 \ delta:Q \ times \ Sigma_ \ bot \ rightarrow Q \ times \ Sigma \ times \ {\ bf {L}、\ bf {S}、\ bf {R} \} 遷移関数です。 \ボ に含まれていない追加文字です \シ







チューリングマシンの構成は4 q alphab gamma その中で q inQ 現在の状態は alpha in Sigma テープの左側b in Sigma bot 現在のキャラクターであり、  gamma in\シ -これはテープの右側です。







機械ステップ  tau 次のようにそれ自体で多くの構成を表示します。









境界の場合も考慮されます(記号 \イ ここでは空の文字列を意味します)。 行の終わり(左または右)に達すると、文字が自動的に追加されることを示しています \ボ









インレットマシンの起動  alphaI 構成から始まる一連のステップです qI\イ\ボ alphaI 。 もし  tau 届く qH それから私達は機械を言う  tau 入り口で終了( 停止 alphaI 。 遷移関数で次のステップを実行できない場合、マシンは  tau 入り口で休憩  alphaI










4.サブタイピングマシン



私たちが持っている概念を一緒にリンクしてみましょう。 目標は、チューリングマシンの実行手順をJavaの型チェックプロセスと比較することです。 このリクエストがあるとします:









C1C2...CmZ<D1D2...DnZ







この声明は、その真実または虚偽を証明するために提案されています。 便宜上、録音の2つの代替形式を紹介します。





 beginarrayllZCmCm1...C1 blacktriangleleftD1D2...DnZ=ZDnDn1...D1 blacktrianglerightC1C2...CmZ end







2種類の実行ルールを導入します  cdot rightsquigarrow cdot 私たちの車の場合:



 bullet -これはhaltingと呼ばれる特別な設定です。

継承ルールがあることに注意してください C1t<::D1E2...Ept および置換タイプ t=C2...CmZ 、次の実行ルールを取得します。









 beginarrayllZCm...C2C1 blacktriangleleftD1D2...DnZ rightsquigarrowZCm...C2Ep...E2 blacktrianglerightD2...DnZ endarray







また、使用されるタイプの矛盾により、次のルールが当てはまります。





 beginarrayllZCm...C1N blacktriangleleftND1...DnZ rightsquigarrowZCm...C1 blacktrianglerightD1...DnZ endarray







導入されたルールを使用して、記事の最初から例のタイプをチェックするときに何が起こるかを理解できます(これらは、反変タイプの選択されたサブセットでJavaタイプをチェックするアルゴリズムに完全に対応します)。 継承関係を設定します BadListt<::BadListt そしてリクエスト BadListString<BadListString

実行ルールのチェーンは次のとおりです。





 beginarrayllStringBadList blacktriangleleftListBadListString rightsquigarrowStringBadListListList blacktriangleleftListBadListString rightsquigarrowStringBadListList blacktrianglerightBadListString rightsquigarrowStringBadListList blacktrianglerightListListBadListString rightsquigarrowStringBadList blacktriangleleftListBadListString rightsquigarrow... endarray







ご覧のとおり、型チェックはループされています。これは、コンパイル中のスタックオーバーフローが原因です。 一般的に、説明されているプロセスは次のように表現できます。

表現 C1...CmZ<D1...DnZ 完了プロセスがある場合にのみtrue ZCm...C1 blacktriangleleftD1...DnZ rightsquigarrow bullet


より複雑な例



次のクラステーブルを検討してください。











 beginarrayrllrllQLt<::LNQLLNtQRt<::LNQRLNtQLt<::EQLRNtQRt<::EQRLNtEt<::QLRNQREEtEt<::QRLNQLEEt endarray







対応するソースコード
 interface N<T> {} interface L<T> {} interface QLR<T> {} interface QRL<T> {} interface E<T> extends QLR<N<? super QR<? super E<? super E<? super T>>>>>, QRL<N<? super QL<? super E<? super E<? super T>>>>> {} interface QL<T> extends L<N<? super QL<? super L<? super N<? super T>>>>>, E<QLR<? super N<? super T>>> {} interface QR<T> extends L<N<? super QR<? super L<? super N<? super T>>>>>, E<QRL<? super N<? super T>>> {}
      
      





入り口でマシンの実行を監視します QREEZ<LNLNEEZ (短縮版):





% Q ^ Leeez \ \ end {array} $







詳細オプション





% blacktriangleleft LNeez EEZ&(Q ^ Rt <:EQ ^ {RL} Nt)\\ \ rightsquigarrow&ZEENLNLNQ ^ {RL} \ blacktriangleright EZ \\ \ rightsquigarrow&ZEENLNLNQ ^ {RL} \ blacktriangleright Q ^ {RL} NQ ^ LEEZ&(Et <: RL} NQ ^ LEEt)\\ \ rightsquigarrow&ZEENLNL \ blacktriangleright LNQ ^ LLNLNL&NLTNL&LNLNL&NLTNL&NLNL&LNT&nbsp; Blackoutriangleright LNQ ^ LLNNL&NL&Znlnlnl&nlnl&nln&lt; n&gt;&gt;&gt;&gt;&gt;&gt; ^ LLNEEZ \\ \ rightsquigarrow&ZEENL \ blacktriangleright Q ^ LLNEEZ \\ \ rightsquigarrow&ZEENL \ blacktriangleright LNQ ^ LLNLNEEZ&(Q ^ Lt <:LNQ ^ LLNt)\\ \ rig htsquigarrow&ZEEN \ blacktriangleleft NQ ^ LLNLNEEZ \\ \ rightsquigarrow&ZEE \ blacktriangleright Q ^ LLNLNEEZ \\ \ rightsquigarrow&ZEE \ blacktriangleright EQ ^ {LR} NLNLNEEZ&(Q ^ Lt <:Eq ^ ^ {黒^ Ritle} {Q ^ L}} {^ Qtl} } Nlnlneez LNLNEEZ \\ \ rightsquigarrow&... \\ \ end {array} $







また、この要求は型チェックのループにつながりますが、現在ではすでに重要なサイクルです。 その場しのぎのテープに、両方向の「三角形」の本格的な放浪を実装しました。 次のコードを使用して、タイプチェックがループしていることを確認できます。







 interface Z {} class Main { L<? super N<? super L<? super N<? super E<? super E<? super Z>>>>>> doit(QR<? super E<? super E<? super Z>>> v) { return v; } }
      
      








5.チューリングマシンの構築



チューリングマシンを持つ  tau および入力テープ  alphaI 、ビルドタイプ t1 そして t2 そのような t1<t2 場合にのみ  tau 入り口で止まる  alphaI

各州について qs inQ 6つのクラスを紹介します。 QwLsQwRsQLsQRsQLRs そして QRLs ; 各キャラクター a in\シ クラスを作ります La 。 記号 \ボ 便宜上チューリングマシンの定義から \# 、クラスに対応します L\# 。 また、4つのクラスを紹介します。 NEML そして MR







最後の例のように見えます。 次に、それらの意味のおおよその説明と、対応するチューリングマシンの遷移関数をエミュレートする特定の方法を説明します。












6.流interfaceなインターフェイス



現実の世界に戻ります。 私たちはJavaについて話しているのです。つまり、私たちの推論は最終的に何らかのコードを書くことにつながるはずです。

OOPの世界では、 流動的なインターフェースのようなものがあります 。 この名前に出会ったことはないかもしれませんが、コードで実装を見たことがあるはずです。 実際、これは単なるカスケードメソッド呼び出しであり、その使用は読みやすさの向上などを目的としています。 次のようになります。







 String s = new StringBuilder(a) .append(b) .append(c) .toString();
      
      





このような一連の呼び出しは、 単語のような新しい方法で見ることができます。 この例では、アルファベット{append, toString}



と単語append append toString



ます。

対応する単語が事前定義された文法に属しているかどうかを確認することにより、Javaコンパイラに呼び出しチェーンを検証するように要求するとします。 たとえば、10文字より長い単語は許可しません。 StringBuilder



クラスでは、この種のことは何もできないことは明らかですが、独自のインターフェイスを試すことができます。







たとえば、メソッドa



b



c



呼び出しのチェーンを検証したいとしc



。 また、対応する言語の単語を検証できるチューリングマシンを用意します(すべてのインターフェイスを備えたサブタイピングマシンが接続されています)。

私たちの目標は、コンパイラにa().b().c()



呼び出しチェーンに対して次のクエリを実行させることa().b().c()



ZEENL\#NMLNLaNLbNLcNL\#QwRI blacktriangleleftEEZ







これを行うには、次のクラスを作成します。







 abstract class B<T> { static B<ML<? super N<? super LHash<? super N<? super E<? super E<? super Z>>>>>>> start; abstract QWRStart<? super LHash<? super N<? super T>>> stop(); abstract B<La<? super N<? super T>>> a(); abstract B<Lb<? super N<? super T>>> b(); abstract B<Lc<? super N<? super T>>> c(); }
      
      





ここでは、対応するメソッドの実装ではなく、署名のみを説明していることを理解してください。 実装については後で行われます。







よく見ると、式のタイプがB.start.a().b().c().stop



、次のように書かれていることがB.start.a().b().c().stop









 QWRStart<? super LHash<? super N<? super Lc<? super N<? super Lb<? super N<? super La<? super N<? super ML<? super N<? super LHash<? super N<? super E<? super E<? super Z>>>>>>>>>>>>>>>
      
      





要求されたリクエストの左側を完全に繰り返します。 つまり、次のコード行は、型チェックアルゴリズムの実行中に、必要なチューリングマシンを実際に起動します。







 E<? super E<? super Z>> res = B.start.a().b().c().stop;
      
      








7. Safe Builder



わかった! このような豊富なツールキットを使用して、わかりやすいものを作成します。

私はこの記事を読んだことを覚えています、私は思った:「面白いアイデア!」。 それで、今は安全なBuilder



を実行する番です。 前の部分のクラスB



の外観はいくつかの制限を規定しているため、私のBuilder



は少し冗長になります。







そのため、コンパイラで検証できる次のコードが必要です。







 User user = build(user() .withFirstName("John") .withLastName("Doe") .please() );
      
      





要件は最も単純です-そのため、両方のフィールドが初期化され、その順序で1回しか初期化されません(このような単純なタスクはより適切な方法で解決できることを理解しています;文法の例は後でより複雑になります)。







この場合のチューリングマシンは非常に単純です。 Start



First



Last



Finish



Halt



5つの状態しかありません。 アルファベットには、 FirstName



LastName



、およびHash



3文字が含まれます。

遷移関数は次のとおりです。







\右 bfR

\右 bfR

\右 bfR

\右 bfR







とても簡単です。 車は入り口でのみ停止します FirstNameLastName







このマシンを記述するソースコード(UserBuilder内にあります)
 public interface N<X> {} public interface ML<X> {} public interface MR<X> {} public interface LFirstName<X> {} public interface LLastName<X> {} public interface LHash<X> {} public interface E<X> extends QLRStart<N<? super QWRStart<? super E<? super E<? super X>>>>>, QRLStart<N<? super QWLStart<? super E<? super E<? super X>>>>>, QLRFirst<N<? super QWRFirst<? super E<? super E<? super X>>>>>, QRLFirst<N<? super QWLFirst<? super E<? super E<? super X>>>>>, QLRLast<N<? super QWRLast<? super E<? super E<? super X>>>>>, QRLLast<N<? super QWLLast<? super E<? super E<? super X>>>>>, QLRFinish<N<? super QWRFinish<? super E<? super E<? super X>>>>>, QRLFinish<N<? super QWLFinish<? super E<? super E<? super X>>>>>, QLRHalt<N<? super QWRHalt<? super E<? super E<? super X>>>>>, QRLHalt<N<? super QWLHalt<? super E<? super E<? super X>>>>> {} public interface QLRStart<X> {} public interface QWLStart<X> extends ML<N<? super QLStart<? super X>>>, MR<N<? super QWLStart<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLStart<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLStart<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLStart<? super LHash<? super N<? super X>>>>>, E<QLRStart<? super N<? super X>>> {} public interface QRLStart<X> {} public interface QWRStart<X> extends MR<N<? super QRStart<? super X>>>, ML<N<? super QWRStart<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRStart<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRStart<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRStart<? super LHash<? super N<? super X>>>>>, E<QRLStart<? super N<? super X>>> {} public interface QLRFirst<X> {} public interface QWLFirst<X> extends ML<N<? super QLFirst<? super X>>>, MR<N<? super QWLFirst<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLFirst<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLFirst<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLFirst<? super LHash<? super N<? super X>>>>>, E<QLRFirst<? super N<? super X>>> {} public interface QRLFirst<X> {} public interface QWRFirst<X> extends MR<N<? super QRFirst<? super X>>>, ML<N<? super QWRFirst<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRFirst<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRFirst<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRFirst<? super LHash<? super N<? super X>>>>>, E<QRLFirst<? super N<? super X>>> {} public interface QLRLast<X> {} public interface QWLLast<X> extends ML<N<? super QLLast<? super X>>>, MR<N<? super QWLLast<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLLast<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLLast<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLLast<? super LHash<? super N<? super X>>>>>, E<QLRLast<? super N<? super X>>> {} public interface QRLLast<X> {} public interface QWRLast<X> extends MR<N<? super QRLast<? super X>>>, ML<N<? super QWRLast<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRLast<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRLast<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRLast<? super LHash<? super N<? super X>>>>>, E<QRLLast<? super N<? super X>>> {} public interface QLRFinish<X> {} public interface QWLFinish<X> extends ML<N<? super QLFinish<? super X>>>, MR<N<? super QWLFinish<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLFinish<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLFinish<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLFinish<? super LHash<? super N<? super X>>>>>, E<QLRFinish<? super N<? super X>>> {} public interface QRLFinish<X> {} public interface QWRFinish<X> extends MR<N<? super QRFinish<? super X>>>, ML<N<? super QWRFinish<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRFinish<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRFinish<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRFinish<? super LHash<? super N<? super X>>>>>, E<QRLFinish<? super N<? super X>>> {} public interface QLRHalt<X> {} public interface QWLHalt<X> extends ML<N<? super QLHalt<? super X>>>, MR<N<? super QWLHalt<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLHalt<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLHalt<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLHalt<? super LHash<? super N<? super X>>>>>, E<E<? super User>> {} public interface QRLHalt<X> {} public interface QWRHalt<X> extends MR<N<? super QRHalt<? super X>>>, ML<N<? super QWRHalt<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRHalt<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRHalt<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRHalt<? super LHash<? super N<? super X>>>>>, E<E<? super User>> {} public interface QLStart<X> extends LHash<N<? super QWLFirst<? super LHash<? super N<? super LHash<? super N<? super MR<? super N<? super X>>>>>>>>> {} public interface QRStart<X> extends LHash<N<? super QWRFirst<? super LHash<? super N<? super MR<? super N<? super LHash<? super N<? super X>>>>>>>>> {} public interface QLFirst<X> extends LFirstName<N<? super QWLLast<? super LFirstName<? super N<? super MR<? super N<? super X>>>>>>> {} public interface QRFirst<X> extends LFirstName<N<? super QWRLast<? super MR<? super N<? super LFirstName<? super N<? super X>>>>>>> {} public interface QLLast<X> extends LLastName<N<? super QWLFinish<? super LLastName<? super N<? super MR<? super N<? super X>>>>>>> {} public interface QRLast<X> extends LLastName<N<? super QWRFinish<? super MR<? super N<? super LLastName<? super N<? super X>>>>>>> {} public interface QLFinish<X> extends LHash<N<? super QWLHalt<? super LHash<? super N<? super MR<? super N<? super LHash<? super N<? super X>>>>>>>>> {} public interface QRFinish<X> extends LHash<N<? super QWRHalt<? super LHash<? super N<? super ML<? super N<? super LHash<? super N<? super X>>>>>>>>> {} public interface QLHalt<X> {} public interface QRHalt<X> {}
      
      





User



UserBuilder



( ):







 public class User { private final String firstName; private final String lastName; private User(UserBuilder<?> builder) { this.firstName = builder.firstName; this.lastName = builder.lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public static User build(E<? super E<? super User>> e) { ... } public static UserBuilder<ML<? super N<? super LHash<? super N<? super E<? super E<? super User>>>>>>> user() { return new UserBuilder<>(); } public static class UserBuilder<X> { private String firstName; private String lastName; public UserBuilder<LFirstName<? super N<? super X>>> withFirstName(String firstName) { this.firstName = firstName; return (UserBuilder<LFirstName<? super N<? super X>>>) this; } public UserBuilder<LLastName<? super N<? super X>>> withLastName(String lastName) { this.lastName = lastName; return (UserBuilder<LLastName<? super N<? super X>>>) this; } public QWRStart<? super LHash<? super N<? super X>>> please() { ... } // interfaces ... } }
      
      







.







, . , , , .. ()(()())



, ()())(()



— . A



B



( x



— , , ).

:







(Start,Hash)(Scan,Hash,R)







(Scan,B)(Back,x,L)

(Scan,Hash)(Check,Hash,L)

(Scan,A)(Scan,A,R)

(Scan,x)(Scan,x,R)







(Back,A)(Scan,x,R)

(Back,B)(Back,B,L)

(Back,x)(Back,x,L)







(Check,Hash)(Halt,Hash,S)

(Check,x)(Check,x,L)







, , .

, .







:







 E<? super E<? super String>> e = start.A().A().B().B().stop();
      
      





:







 E<? super E<? super String>> e = start.B().A().B().A().stop();
      
      








8.



. , .







— .

-, , Builder



.

-, , . — .

-, . - ( ):







 incompatible types: QWRStart<capture#1 of ? super LHash<? super N<? super LFirstName<? super N<? super LLastName<? super N<? super ML<? super N<? super LHash<? super N<? super E<? super E<? super User>>>>>>>>>>>>> cannot be converted to E<? super E<? super User>>
      
      





. , :









? , , :









Kotlin, , , Java , , .. Non-Expansive Inheritance Restriction . :







 interface A<T> interface B<T> : A<B<Anything<T>>>
      
      





Kotlin, . - , B



B



, T



, . , .







— " " , . - Kotlin . , Kotlin .







, Java-, Kotlin , Java — . , Kotlin Java, , .










参照資料



  1. (Radu Grigore. Java Generics are Turing Complete).
  2. .
  3. github.



All Articles