1. ホーム
  2. java

[解決済み] Java の型消去のメリットは何ですか?

2022-10-18 01:23:12

質問

私は ツイート 今日、こんなことを言った。

Javaユーザーが、Javaが間違っていたことをすべて無視して、唯一正しかった型消去について文句を言うのはおかしい。

したがって、私の質問は

Javaの型消去から利点がありますか?後方互換性と実行時のパフォーマンスのためのJVM実装の好み以外に、それが提供する(かもしれない)技術的またはプログラミングスタイルの利点は何でしょうか?

どのように解決するのですか?

タイプ・イレーザーは良い

事実に忠実であろう

ここまでの回答の多くは、Twitterユーザーに過度に配慮したものです。 メッセンジャーではなく、メッセージに焦点を当て続けることは有用です。 これまでに挙げられた抜粋だけでも、かなり一貫したメッセージがあります。

Java ユーザーが、Java が間違っていたすべてのことを無視して、Java が正しかった唯一のことである型消去について文句を言うのはおかしな話です。

巨大なメリット(パラメトリック性など)を得て、コストはゼロ(コストと言われるのは想像力の限界)。

new Tは壊れたプログラムである。それは "all propositions are true." という主張と同型であり、私はこれに大きな意味を持ちません。

目標:合理的なプログラム

これらのツイートは、機械に以下のことをさせることができるかどうかには興味がない、という視点を反映しています。 何か をさせることができるかどうかではなく、マシンが私たちが実際に望むことをすることを推論できるかどうかに関心がある視点を反映しています。 良い推論とは証明である。 証明は形式的な記法で指定することもできますし、より形式的でないもので指定することもできます。 仕様言語に関係なく、証明は明確で厳密でなければなりません。 非公式な仕様は、正しく構成することは不可能ではありませんが、実用的なプログラミングではしばしば欠陥が生じます。 結局、自動テストや探索的テストのような改善策で、非公式な推論で抱える問題を補うことになるのです。 これは、テストが本質的に悪い考えであると言うわけではありませんが、引用したTwitterユーザーは、もっと良い方法があることを示唆しています。

ですから、私たちの目標は、マシンが実際にどのようにプログラムを実行するかに対応する方法で、明確かつ厳密に推論できる正しいプログラムを持つことなのです。 しかし、これだけが目標ではありません。 論理にはある程度の表現力も必要です。 例えば、命題論理で表現できることは限られている。 一階論理のようなものから普遍量化(∀)や存在量化(∃)ができるのはいいことです。

推論のための型システムの使用

これらの目標は、型システムによって非常にうまく対処することができます。 このことは、特に カレー・ハワード対応 . この対応関係はしばしば次のようなアナロジーで表現されます:型はプログラムにとって定理であり、証明にとって定理である。

この対応はやや奥が深い。 論理式は、型への対応を通じて変換することができます。 そして、同じ型署名でコンパイルできるプログラムがあれば、その論理式が普遍的に真であることを証明したことになります(トートロジー)。 これは、対応が双方向であるためである。 型/プログラムと定理/証明の世界の変換は機械的であり、多くの場合自動化することができます。

Curry-Howardは、私たちがプログラムの仕様で行いたいことにうまく対応します。

Javaで型システムは有用か?

Curry-Howardを理解していても、型システムの価値を簡単に否定してしまう人がいますが、その場合は

  1. で作業するのが非常に困難である
  2. は、(Curry-Howard を通して)表現力が制限された論理に対応する。
  3. が壊れている (これは "weak" または "strong" としてのシステムの特徴づけになる)。

最初のポイントについて、おそらくIDEはJavaの型システムを十分に作業しやすくしています(これは非常に主観的です)。

2点目について、Javaは偶然にも <項目 ほとんど は一階論理に相当する。 ジェネリックスは普遍量化(universal quantification)に相当する型システム を与える。 残念ながら、ワイルドカードは実存的数量化のごく一部を与えるに過ぎない。 しかし、普遍的な数量化というのは、かなり良いスタートである。 例えば List<A> に対して普遍的に働くと言えるのは素晴らしいことです。 すべての可能な のリストに対して普遍的に動作します。これは、Aが完全に無制約であるためです。 これは、Twitterユーザーが言っている「パラメトリック性」に関してです。

パラメトリック性に関してよく引用される論文に、Philip Wadlerの Theorems for free! . この論文の面白いところは、型署名だけで、いくつかの非常に興味深い不変量を証明できることです。 もし、これらの不変量に対して自動テストを書くとしたら、非常に時間を無駄にすることになる。 例えば List<A> に対して、型シグネチャだけから flatten

<A> List<A> flatten(List<List<A>> nestedLists);

という推論ができます。

flatten(nestedList.map(l -> l.map(any_function)))
    ≡ flatten(nestList).map(any_function)

これは簡単な例で、おそらく非公式に推論することができるでしょう。しかし、このような証明を型システムから無料で正式に得て、コンパイラでチェックすることができれば、さらに素晴らしいことでしょう。

消去しないことは悪用につながる可能性がある

言語実装の観点から、Javaのジェネリックス(普遍型に対応)は、私たちのプログラムが何をするかについての証明を得るために使用されるパラメトリックに非常に大きく関わっています。 これは、3つ目の問題につながります。 これらの証明と正しさを得るためには、欠陥なく実装された健全な型システムが必要です。 Javaは間違いなく、私たちの推論を打ち砕くことを可能にするいくつかの言語機能を備えています。 これらは以下を含みますが、これらに限定されるものではありません。

  • 外部システムとの副作用
  • リフレクション

非消去型ジェネリックは多くの点でリフレクションと関連しています。 消去がなければ、アルゴリズムを設計するために使用できる、実装とともに運ばれる実行時情報があります。 このことが意味するのは、静的にプログラムについて推論するとき、私たちは全体像を持っていないということです。 リフレクションは、私たちが静的に推論する証明の正しさを著しく脅かす。 反射がさまざまな厄介な欠陥につながるのは、偶然ではありません。

では、非消去型ジェネリックスはどのように役に立つのでしょうか。

<T> T broken { return new T(); }

Tが引数のないコンストラクタを持たない場合、どうなるでしょうか? いくつかの言語では、nullを取得します。 あるいは、NULL 値をスキップして、そのまま例外を発生させるかもしれません(NULL 値はいずれにせよ発生するようです)。 我々の言語はチューリング完全なので、どの broken への呼び出しが引数なしのコンストラクタを持つ安全な型とそうでない型を含むかどうかを推論することは不可能です。 私たちのプログラムが普遍的に動作するという確信がなくなりました。

消すことは推論したことを意味する(だから消してしまおう)。

つまり、プログラムについて推論したいのであれば、推論を強く脅かすような言語機能を採用しないことを強く勧める。 そうしたら、実行時に型を削除すればよいのではないでしょうか。 型は必要ない。 キャストに失敗したり、メソッドの呼び出し時に欠落することがないという満足感を得ながら、ある程度の効率と簡素化を得ることができます。

消去は推論を促します。