web-dev-qa-db-ja.com

依存型タイピングとは何ですか?

誰かが依存型入力について説明してもらえますか?私はHaskell、Cayenne、Epigram、またはその他の関数型言語の経験がほとんどないので、使用できる用語が単純であればあるほど、感謝します!

72
Nick

これを考慮してください:すべてのまともなプログラミング言語で関数を書くことができます。

def f(arg) = result

ここで、fは値argを取り、値resultを計算します。値から値への関数です。

現在、一部の言語では、ポリモーフィック(別名ジェネリック)値を定義できます。

def empty<T> = new List<T>()

ここで、emptyは型Tを取り、値を計算します。型から値への関数です。

通常、ジェネリック型の定義を持つこともできます。

type Matrix<T> = List<List<T>>

この定義は型を取り、型を返します。型から型への関数と見なすことができます。

通常の言語が提供するものについてはこれで終わりです。言語は、4番目の可能性、つまり値から型への関数の定義も提供する場合、依存型付けと呼ばれます。または言い換えると、値に対して型定義をパラメータ化する:

type BoundedInt(n) = {i:Int | i<=n}

いくつかの主流言語には、混乱しないように、この偽の形式があります。例えば。 C++では、テンプレートは値をパラメーターとして取ることができますが、適用する場合はコンパイル時の定数でなければなりません。本当に依存型付けされた言語ではそうではありません。たとえば、次のように上記のタイプを使用できます。

def min(i : Int, j : Int) : BoundedInt(j) =
  if i < j then i else j

ここでは、関数の結果タイプdependsが実際の引数値jに依存しているため、用語が使用されます。

90

たまたまC++を知っているなら、やる気を起こさせる例を提供するのは簡単です:

いくつかのコンテナタイプとその2つのインスタンスがあるとします

typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;

そして、このコードの断片を検討してください(fooは空ではないと想定できます)。

IIMap::iterator i = foo.begin();
bar.erase(i);

これは明らかなガベージです(そしておそらくデータ構造を破壊します)が、 "iterator into foo"と "iterator into bar"は完全に同じタイプIIMap::iteratorであるため、タイプチェックは問題なく行われます意味的に互換性がありません。

問題は、イテレータタイプがコンテナtypeだけに依存するのではなく、実際にコンテナobjectに依存することです。つまり、 「非静的メンバータイプ」である必要があります。

foo.iterator i = foo.begin();
bar.erase(i);  // ERROR: bar.iterator argument expected

このような機能、つまり用語(foo)に依存する型(foo.iterator)を表現する機能は、依存型入力が意味するものです。

この機能があまり見られないのは、ワームの大きな缶が開くためです。2つの型が同じかどうかをコンパイル時に確認するために、2つの式を証明しなければならない状況が突然発生します。同等です(実行時に常に同じ値が生成されます)。その結果、ウィキペディアの 依存型付き言語のリスト定理証明のリスト を比較すると、疑わしい類似性に気付く場合があります。 ;-)

14
Matthijs

依存型を使用すると、 論理エラー のより大きなセットをコンパイル時に削除できます。これを説明するために、関数fに関する次の仕様を検討してください。

関数fは、入力としてeven整数のみを取る必要があります。

依存型がなければ、次のようなことをするかもしれません:

def f(n: Integer) := {
  if  n mod 2 != 0 then 
    throw RuntimeException
  else
    // do something with n
}

ここで、コンパイラはnが本当に偶数であるかどうかを検出できません。つまり、コンパイラの観点からは、次の式は問題ありません。

f(1)    // compiles OK despite being a logic error!

このプログラムは実行時に実行時に例外をスローします。つまり、プログラムに論理エラーがあります。

依存型を使用すると、表現力が大幅に向上し、次のような記述が可能になります。

def f(n: {n: Integer | n mod 2 == 0}) := {
  // do something with n
}

ここでnは依存型{n: Integer | n mod 2 == 0}。これを大声で読むと役立つかもしれません

nは、各整数が2で割り切れる整数セットのメンバーです。

この場合、コンパイラーは奇数をfに渡した論理エラーをコンパイル時に検出し、プログラムが最初から実行されないようにします。

f(1)    // compiler error
10
Mario Galic

本のタイプとプログラミング言語(30.5)を引用すると:

この本の多くは、さまざまな種類の抽象化メカニズムの形式化に関係しています。単純に型指定されたラムダ計算で、用語を取得してサブ用語を抽象化する操作を形式化し、異なる用語に適用することで後でインスタンス化できる関数を生成しました。 System Fでは、用語を取得して型を抽象化し、さまざまな型に適用してインスタンス化できる用語を生成する操作を検討しました。にλω、単純に型指定されたラムダ計算の「1レベル上の」メカニズムを要約し、型を取得して部分式を抽象化し、後で別の型に適用することでインスタンス化できる型演算子を取得しました。これらすべての抽象化形態を考える便利な方法は、他の式によってインデックスが付けられた式のファミリーという観点からです。通常のラムダ抽象化λx:T1.t2は用語ファミリー[x -> s]t1用語で索引付けs。同様に、型の抽象化λX::K1.t2はタイプによって索引付けされた用語のファミリーであり、タイプ演算子はタイプによって索引付けされたタイプのファミリーです。

  • λx:T1.t2用語で索引付けされた用語ファミリー

  • λX::K1.t2タイプで索引付けされた用語のファミリー

  • λX::K1.T2タイプで索引付けされたタイプのファミリー

このリストを見ると、まだ検討していない可能性が1つあることは明らかです。用語で索引付けされたタイプのファミリーです。この形式の抽象化は、依存型のルーブリックの下で、広範囲にわたって研究されてきました。

5
namin