適切な汎用型システム


29

Javaジェネリックがいくつかの重要な方法で失敗したことは一般に受け入れられています。ワイルドカードと境界の組み合わせにより、いくつかの深刻な読み取り不能コードが発生しました。

しかし、他の言語を見ると、プログラマが満足している一般的な型システムを見つけることができないようです。

このような型システムの設計目標として次を採用する場合:

  • 常に読みやすい型宣言を生成します
  • 簡単に習得できます(共分散、反分散などをブラッシュアップする必要はありません)
  • コンパイル時エラーの数を最大化します

それを正しくした言語はありますか?私がグーグルで見た場合、私が見る唯一のことは、型システムが言語Xをどのように吸うかについての苦情です。この種の複雑さはジェネリック型に固有のものですか?コンパイル時に型安全性を100%検証しようとするのをあきらめるだけですか?

私の主な質問は、これらの3つの目標に関して最も「うまくいく」言語です。私はそれが主観的であることを認識していますが、これまでのところ、すべてのプログラマーが汎用型システムが混乱していることに同意するわけではない言語を見つけることさえできません。

補遺:前述のように、サブタイピング/継承とジェネリックの組み合わせが複雑さを引き起こすので、両方を組み合わせ、複雑さの爆発を回避する言語を本当に探しています。


2
どういう意味easy-to-read type declarationsですか?3番目の基準も曖昧です。たとえば、コンパイル時にインデックスを計算できない限り、配列にインデックスを付けないようにすることで、配列インデックスを境界外例外からコンパイル時エラーに変えることができます。また、2番目の基準はサブタイピングを除外します。それは必ずしも悪いことではありませんが、あなたが何を求めているのかを知っておく必要があります。
ドーバル14年


9
@gnat、これは間違いなくJavaに対する暴言ではありません。私はほとんど専らJavaでプログラミングしています。私のポイントは、ジェネリックに欠陥があるということはJavaコミュニティ内で一般に受け入れられているということです(全体的な失敗ではなく、おそらく部分的な失敗です)。なぜ彼らは間違っており、他の人がそれらを正しくしたのですか?それとも、実際にジェネリックを絶対に正しくすることは不可能ですか?
ピーター14年

1
誰もがC#から盗んだだけで苦情は減りました。特に、Javaはコピーによって追いつく立場にあります。代わりに、彼らは劣ったソリューションを決定します。Java設計委員会がまだ議論している質問の多くは、すでに決定され、C#で実装されています。見た目さえないようです。
usr 14年

2
@emodendroket:C#ジェネリックに関する私の2つの最大の不満は、「スーパータイプ」制約(たとえばFoo<T> where SiameseCat:T)を適用する方法がないことと、に変換できないジェネリック型を持つ可能性がないことだと思いObjectます。私見、.NETは、構造体に似ているがさらに骨が粗い集約型の恩恵を受けるでしょう。そのKeyValuePair<TKey,TValue>ような型であれば、IEnumerable<KeyValuePair<SiameseCat,FordFocus>>をにキャストできますIEnumerable<KeyValuePair<Animal,Vehicle>>が、型をボックス化できない場合のみです。
supercat

回答:


24

ジェネリックは機能プログラミングコミュニティで何十年もの間主流でしたが、ジェネリックをオブジェクト指向プログラミング言語に追加すると、いくつかのユニークな課題、特にサブタイプとジェネリックの相互作用が生じます。

ただし、オブジェクト指向プログラミング言語、特にJavaに焦点を当てたとしても、はるかに優れたジェネリックシステムを設計できたはずです。

  1. ジェネリック型は、他の型がある場合はいつでも受け入れられるべきです。特に、Tが型パラメーターである場合、次の式は警告なしでコンパイルする必要があります。

    object instanceof T; 
    T t = (T) object;
    T[] array = new T[1];
    

    はい、これには、言語の他のすべてのタイプと同様に、ジェネリックを具体化する必要があります。

  2. ジェネリック型の共分散と反分散は、ジェネリック型が使用されるたびにではなく、その宣言で指定(または推論)する必要があります。

    Future<Provider<Integer>> s;
    Future<Provider<Number>> o = s; 
    

    のではなく

    Future<? extends Provider<Integer>> s;
    Future<? extends Provider<? extends Number>> o = s;
    
  3. ジェネリック型はかなり長くなる可能性があるため、それらを重複して指定する必要はありません。つまり、書くことができるはずです

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (var e : map.values()) {
        for (var list : e.values()) {
            for (var person : list) {
                greet(person);
            }
        }
    }
    

    のではなく

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (Map<String, List<LanguageDesigner>> e : map.values()) {
        for (List<LanguageDesigner> list : e.values()) {
            for (LanguageDesigner person : list) {
                greet(person);
            }
        }
    }
    
  4. すべての型は、参照型だけでなく、型パラメーターとして受け入れられる必要があります。(を持つことができる場合int[]、なぜできないのList<int>ですか)?

これはすべてC#で可能です。


1
これも自己参照ジェネリックを取り除くでしょうか?比較可能なオブジェクトが同じタイプまたはサブクラスの何かとそれ自体を比較できると言いたい場合はどうなりますか?それはできますか?または、比較可能なオブジェクトを含むリストを受け入れるソートメソッドを記述する場合、それらはすべて互いに比較可能である必要があります。Enumはもう1つの良い例です。Enum<EはEnum <E >>を拡張します。型システムがこれらを実行できるはずだと言っているのではなく、C#がこれらの状況をどのように処理するかを知りたいだけです。
ピーター14年

1
Java 7のジェネリック型推論C ++の自動はこれらの懸念のいくつかを支援しますが、構文糖であり、基礎となるメカニズムを変更しません。

@Snowman Javaの型推論には、匿名クラスをまったく使用せず、ジェネリックメソッドを別のジェネリックメソッドの引数として評価するときにワイルドカードの正しい境界を見つけられないなど、いくつかの本当に不快なコーナーケースがあります。
ドーバル14年

@Dovalは、私がそれがいくつかの懸念に役立つと言った理由です:それは何も修正せず、すべてに対処しません。Javaジェネリックには多くの問題があります。生の型よりも優れていますが、確かに多くの頭痛の種です。

34

サブタイプを使用すると、一般的なプログラミングを行う際に多くの問題が生じます。サブタイプを持つ言語の使用を主張する場合、それに付随する汎用プログラミングには特定の固有の複雑さがあることを受け入れなければなりません。一部の言語は他の言語よりも優れていますが、これまでのところしか使用できません。

たとえば、Haskellのジェネリックと比較してください。型推論を使用すると、誤って正しい汎用関数を記述できるほど単純です。実際には、あなたは、単一のタイプを指定した場合、コンパイラは、多くの場合、自分自身に言う、「まあ、私はされた、これは一般的なするつもりが、あなたが唯一のint型のためにそれを作るために私に尋ねた、何でもそう。」

確かに、人々が使用し、いくつかの驚くほど複雑な方法でHaskellの型システムをそれをすべての初心者の悩みの種を作りますが、基になる型システム自体は、エレガントで非常に賞賛です。


1
この答えをありがとう。この記事は、ジェネリックが複雑になりすぎるというJoshua Blochの例から始まります:artima.com/weblogs/viewpost.jsp?thread=222021 これは、JavaとHaskellの文化の違いですか?Haskellでそのような構成体は問題ないと見なされますか、またはそのような状況を回避するHaskellの型システムに本当の違いがありますか?
ピーター14年

10
@Peter Haskellにはサブタイピングがありません。Karlが言ったように、コンパイラは「タイプaは整数である必要があります」などの制約を含むタイプを自動的に推測できます。
ドーバル14年

言い換えれば、Scalaなどの言語での共分散です。
ポールドレイパー14年

14

ジェネリックとサブタイプの組み合わせについては、約20年前にかなりの研究が行われました。MITのBarbara Liskovの研究グループによって開発されたThorプログラミング言語には、パラメーター化する型の要件を指定できる「where」句の概念がありました。(これは、C ++がConceptsでやろうとしていることに似ています。)

Thorのジェネリック医薬品とThorのサブタイプとの相互作用を説明する論文は次のとおりです。グルーバー、R; リスコフ、B; マイヤーズ、AC:サブタイプ対where句:パラメトリック多型を制約するOBJの指向プログレは、SYS、ラングやアプリ上のACMコンファレンス、(OOPSLA-10):156-158、1995。

彼らは、1980年代後半にエメラルドで行われた仕事の上に成り立っていると思います。(私はその作品を読んでいませんが、参考文献:Black、A; Hutchinson、N; Jul、E; Levy、H; Carter、L:Distribution and Abstract Types in Emerald、_IEEE T. Software Eng。、13( 1):65-76、1987

ThorとEmeraldはどちらも「アカデミック言語」であったため、where句(概念)が実際の問題を本当に解決するかどうかを人々が本当に理解するのに十分な使用法を得られなかったでしょう。Stroustrup氏、B:C ++での概念の最初の試みが失敗した理由についてビャーネ・ストロヴストルップの記事読むために興味深いですC ++ 0xの「削除の概念」決定博士ドブス 7月22日、2009年に(さらに詳細Stroustrup氏のホームページを。 )

人々が試みている別の方向は特性と呼ばれるものです。たとえば、MozillaのRustプログラミング言語は特性を使用します。私が理解しているように(これは完全に間違っているかもしれません)、クラスが特性を満たしていると宣言することは、クラスがインターフェースを実装するということと非常によく似ていますが、「is a」ではなく「aのように振る舞う」と言っています。Appleの新しいSwiftプログラミング言語は、同様のプロトコルの概念を使用て、ジェネリックに対するパラメーターの制約指定しているようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.