依存型とは何ですか?


82

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


では、たとえばウィキペディアの記事について、正確に何を理解していなかったのでしょうか。
Karl Knechtel 2012

124
さて、記事はラムダキューブで始まります。これは私にはある種の羊の肉のように聞こえます。次に、λΠ2システムについて説明します。私はエイリアンを話さないので、そのセクションをスキップしました。次に、誘導構造の微積分について読みました。これは、偶然にも微積分、熱伝達、または構造とはほとんど関係がないようです。言語比較表を提出した後、記事は終了し、ページにたどり着いたときよりも混乱したままになります。
ニック

3
@Nickそれはウィキペディアの一般的な問題です。数年前にあなたのコメントを見ましたが、それ以来覚えています。今ブックマークしています。
ダニエルH

回答:


111

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

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

ここで、関数の結果タイプ実際の引数値に依存するjため、用語が異なります。


ただし、このBoundedInt例は実際には詳細化タイプではありませんか?これは「かなり近い」ですが、たとえばIdrisがdep.typingに関するチュートリアルで最初に言及している「依存型」の種類とは異なります。
Narfanar 2015年

3
@Noein、詳細化タイプは確かに依存型の単純な形式です。
アンドレアスロスバーグ2015年

21

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

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

依存型がないと、次のようなことができます。

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

これは、このような要件を満たす関数の実装を試みる方法のScalaパス依存型を使用した実例fです。

case class Integer(v: Int) {
  object IsEven { require(v % 2 == 0) }
  object IsOdd { require(v % 2 != 0) }
}

def f(n: Integer)(implicit proof: n.IsEven.type) =  { 
  // do something with n safe in the knowledge it is even
}

val `42` = Integer(42)
implicit val proof42IsEven = `42`.IsEven

val `1` = Integer(1)
implicit val proof1IsOdd = `1`.IsOdd

f(`42`) // OK
f(`1`)  // compile-time error

キーは、値がどのように通知にあるn値の種類に表示されます。proofすなわちn.IsEven.type

def f(n: Integer)(implicit proof: n.IsEven.type)
      ^                           ^
      |                           |
    value                       value

タイプ n.IsEven.type値に 依存すると言うnので、依存型という用語があります


5
ランダムな値をどのように処理しますか?たとえばf(random())、コンパイルエラーが発生しますか?
Wong Jia Hau 2018年

5
fある式に適用するには、コンパイラが(あなたの助けの有無にかかわらず)式が常に偶数であることを提供する必要があり、そのような証明は存在しないためrandom()(実際には奇数である可能性があるため)、f(random())コンパイルに失敗します。
Matthijs 2018

18

C ++を知っている場合は、やる気を起こさせる例を簡単に提供できます。

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

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

そして、このコードフラグメントを検討してください(fooは空ではないと思われるかもしれません):

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

これは明らかなゴミです(そしておそらくデータ構造を破壊します)が、「iteratorintofoo」と「iteratorintobar」はIIMap::iterator意味的に完全に互換性がないにもかかわらず、同じタイプであるため、タイプチェックは問題なく行われます。

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

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

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

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


4

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

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

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

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

  • λX::K1.T2 タイプによってインデックス付けされたタイプのファミリー

このリストを見ると、まだ検討していない可能性が1つあることは明らかです。それは、用語で索引付けされた型族です。この形式の抽象化も、依存型のルーブリックの下で広く研究されてきました。

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