web-dev-qa-db-ja.com

優れたジェネリック型システム

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

しかし、他の言語を見ると、プログラマーが満足しているジェネリック型システムを実際に見つけることができないようです。

このような型システムの設計目標として、次のことを考えます。

  • 常に読みやすい型宣言を生成する
  • 習得が容易(共分散、反分散などをブラッシュアップする必要がない)
  • コンパイル時エラーの数を最大化

それを正しくした言語はありますか?グーグルで見ると、型システムが言語Xでどのように機能しないかについての不満だけが表示されます。この種の複雑さは、ジェネリック型に固有のものですか?コンパイル時にタイプセーフを100%検証することをあきらめる必要がありますか?

私の主な質問は、これらの3つの目標に関して、どれが「正しく理解した」言語であるかです。私はそれが主観的であることを理解していますが、これまでのところ、ジェネリック型システムが混乱しているとプログラマー全員が同意しているわけではない1つの言語を見つけることもできません。

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

29
Peter

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

ただし、オブジェクト指向プログラミング言語、特に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#で可能です。

24
meriton

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

たとえば、Haskellのジェネリックと比較してください。型推論を使用する場合、accidentによって正しいジェネリック関数を記述できるほど単純です。実際、単一の型を指定すると、コンパイラーはしばしば「まあ、私はwasこれをジェネリックにするつもりですが、intに対してのみ作成するように頼んだので、何でもそうです。」

確かに、人々se Haskellの型システムは驚くほど複雑な方法ですべての初心者の悩みの種になっていますが、基礎となる型システム自体はエレガントで、賞賛されています。

34
Karl Bielefeldt

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

トールのジェネリックと、それらがトールのサブタイプとどのように相互作用するかを説明した論文は、Day、M、グルーバー、R;リスコフ、B;マイヤーズ、AC: サブタイプとwhere句:パラメトリック多態性の制約オブジェクト指向のプログラム、システム、ラング、およびアプリでのACMの設定、(OOPSLA-10):156-158、1995。

彼らは、1980年代後半にエメラルドで行われた作業に基づいて構築されたと思います。 (私はその作品を読んでいませんが、参照は次のとおりです:黒、A、ハチンソン、N、7月、E、レビー、H、カーター、L: エメラルドの分布と抽象型 、_ IEEE T 。Software Eng。、13(1):65-76、1987。

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

人々が試みているように見える別の方向は traits と呼ばれるものです。たとえば、Mozillaの Rustプログラミング言語 は特性を使用します。私が理解しているように(これは完全に間違っている可能性があります)、クラスが特性を満たしていると宣言することは、クラスがインターフェイスを実装していると言うのとよく似ていますが、「is a」ではなく「aのように動作する」と言っています。 Appleの新しいSwiftプログラミング言語はprotocolsの類似の概念を使用して ジェネリックのパラメーター

14
Wandering Logic