存在タイプとは何ですか?


171

Wikipediaの記事「存在タイプ」を読みました。実在演算子(∃)があるため、実在型と呼ばれていることを知りました。でも、その意味がよくわかりません。違いは何ですか

T = ∃X { X a; int f(X); }

そして

T = ∀x { X a; int f(X); }


8
これは、programmers.stackexchange.comにとって良いトピックかもしれません。programmers.stackexchange.com
jpierson 2010

回答:


192

誰か∀Xが言っているユニバーサルタイプを定義すると、次のようになります:好きなタイプをプラグインできXます。私の仕事をするためにタイプについて何も知る必要はありませんそれを不透明に参照するだけです。

誰かが存在タイプ∃Xを定義するとき、彼らは言っています:私はここで私が欲しいどんなタイプでも使います; あなたはタイプについて何も知らないので、あなたはそれを不透明に参照することができるだけですX

ユニバーサルタイプを使用すると、次のように記述できます。

void copy<T>(List<T> source, List<T> dest) {
   ...
}

copy機能はないアイデアを持っていないものをT、実際になりますが、それはする必要はありません。

存在するタイプは、次のようなものを書くことができます:

interface VirtualMachine<B> {
   B compile(String source);
   void run(B bytecode);
}

// Now, if you had a list of VMs you wanted to run on the same input:
void runAllCompilers(List<∃B:VirtualMachine<B>> vms, String source) {
   for (∃B:VirtualMachine<B> vm : vms) {
      B bytecode = vm.compile(source);
      vm.run(bytecode);
   }
}

リスト内の各仮想マシン実装は、異なるバイトコードタイプを持つことができます。runAllCompilersこの関数は、バイトコードの種類が何であるかわかりませんが、それはする必要はありません。それがするすべてはからVirtualMachine.compileにバイトコードをリレーすることですVirtualMachine.runです。

Javaタイプのワイルドカード(例: List<?>は、存在タイプの非常に限定された形式です。

更新:ユニバーサルタイプを使用して存在タイプをシミュレートできることを忘れてしまいました。まず、ユニバーサル型をラップして、型パラメーターを非表示にします。次に、制御を反転します(これにより、上記の定義の「あなた」と「I」の部分が効果的に入れ替わります。これは、存在と普遍の主な違いです。)

// A wrapper that hides the type parameter 'B'
interface VMWrapper {
   void unwrap(VMHandler handler);
}

// A callback (control inversion)
interface VMHandler {
   <B> void handle(VirtualMachine<B> vm);
}

これで、普遍的に型付けされた関数を持つVMWrapper独自の呼び出しを作成できます。正味の効果は同じで、コードは不透明として処理する必要があります。VMHandlerhandleB

void runWithAll(List<VMWrapper> vms, final String input)
{
   for (VMWrapper vm : vms) {
      vm.unwrap(new VMHandler() {
         public <B> void handle(VirtualMachine<B> vm) {
            B bytecode = vm.compile(input);
            vm.run(bytecode);
         }
      });
   }
}

VMの実装例:

class MyVM implements VirtualMachine<byte[]>, VMWrapper {
   public byte[] compile(String input) {
      return null; // TODO: somehow compile the input
   }
   public void run(byte[] bytecode) {
      // TODO: Somehow evaluate 'bytecode'
   }
   public void unwrap(VMHandler handler) {
      handler.handle(this);
   }
}

12
@Kannan、非常に便利な、やや難把握答えを1:1。私はあなたが実存&ユニバーサルタイプの二重の性質についてより明確にすることができれば、それが役立つだろうと思います。最初の2つの段落の表現が非常に似ているのは偶然でした。後になって、両方の定義は基本的に同じであるが、「私」と「あなた」が逆になっていることを明確に述べますか。また、「私」や「あなた」が何を指すのか、すぐにはわかりませんでした。
stakx-2011年

2
(続き:) 2.List<∃B:VirtualMachine<B>> vmsまたはの数学的表記の意味が完全に理解できませんfor (∃B:VirtualMachine<B> vm : vms)。(これらはジェネリック型なので?、「自作」構文の代わりにJavaのワイルドカードを使用できませんでしたか?)などのジェネリック型が含まれ∃B:VirtualMachine<B>ていないが、代わりに「ストレート」なコード例があると役立つと思います。∃Bなぜなら、ジェネリック型は最初のコード例の後で簡単にユニバーサル型に関連付けられるからです。
stakx-2011年

2
∃Bは以前、定量化が行われている場所を明確に示していました。ワイルドカード構文では、量指定子が暗黙に含まれます(List<List<?>>実際にはを意味し∃T:List<List<T>>、を意味しませんList<∃T:List<T>>)。また、明示的な数量化によって型を参照できます(この例を変更して、型のバイトコードをB一時変数に格納することでこれを活用しています)。
Kannan Goundan 2012年

2
ここで使用されている数学的表記は地獄のようにずさんなものですが、私はそれが回答者のせいではないと思います(それは標準です)。それでも、おそらくこのような方法で実存的および普遍的な数量詞を乱用しないのが最善です...
ノルドリン2012

2
@ Kannan_Goundan、Javaワイルドカードはこれの非常に限定されたバージョンであると言う理由を教えてください。純粋なJavaで最初のrunAllCompilersサンプル関数を実装できることを知っていますか(ワイルドカードパラメーターを取得する(名前を付ける)ヘルパー関数を使用)。
LP_ 14

107

実存型などが∃x. F(x) ペアであるいくつかの含有タイプ x及びタイプのをF(x)。以下のような多相型の値に対し、∀x. F(x)ある機能ある種かかりx且つ生産タイプの値F(x)。どちらの場合も、型はいくつかの型コンストラクターで閉じますF

このビューではタイプと値が混在していることに注意してください。存在証明は、1つのタイプと1つの値です。普遍的な証明は、タイプ(またはタイプから値へのマッピング)でインデックス付けされた値のファミリー全体です。

したがって、指定した2つのタイプの違いは次のとおりです。

T = ∃X { X a; int f(X); }

つまり、typeの値にTは、という型X、value a:X、およびfunction が含まれますf:X->int。タイプの値のプロデューサは、任意のタイプTを選択できX、コンシューマはについて何も知ることができませんX。その一つの例があると呼ばれることを除いてa、この値をに変えることができることをintにそれを与えることによってf。つまり、typeの値はTintどういうわけか生成する方法を知っています。さて、私たちは中間型Xを排除し、ただ次のように言うことができます:

T = int

普遍的に定量化されたものは少し異なります。

T = ∀X { X a; int f(X); }

つまり、typeの値にはT任意のtypeを指定できX、値が何であって、値a:Xと関数を生成します。つまり、タイプの値のコンシューマは、の任意のタイプを選択できます。また、タイプの値のプロデューサは、について何も知ることができませんが、の任意の選択に対して値を生成でき、そのような値をに変換できる必要がありますf:X->int XTXTXaXint

考えられるすべての型の値を生成できるプログラムがないため、この型の実装は明らかに不可能です。あなたが不条理を許可しない限りnullまたは底の。

実存論はペアであるため、カリー化によって実存論の引数を普遍的なものに変換できます。

(∃b. F(b)) -> Int

と同じです:

∀b. (F(b) -> Int)

前者は、ランク2の存在です。これにより、次の便利なプロパティが得られます。

すべての実存的に定量化されたタイプのランクn+1は、普遍的に定量化されたタイプのランクnです。

Skolemizationと呼ばれる、存在をユニバーサルに変換するための標準アルゴリズムがあります。



34

2つの概念は相補的であるため、つまり、一方が他方の「反対」であるため、存在型をユニバーサル型と一緒に説明することには意味があると思います。

実存型についてのすべての詳細(正確な定義を与える、考えられるすべての使用法、抽象データ型との関係などをリストするなど)に答えることはできません。このHaskellWiki記事が実存型の主な効果であると述べていることだけを(Javaを使用して)示します。

存在タイプは、いくつかの異なる目的に使用できます。しかし、彼らがていることは、右側の型変数を「隠す」ことです。通常、右側に表示される型変数は左側にも表示される必要があります[…]

設定例:

次の疑似コードは、それを修正するのは簡単ですが、Javaとしては有効ではありません。実際、それがまさに私がこの答えでやろうとしていることです!

class Tree<α>
{
    α       value;
    Tree<α> left;
    Tree<α> right;
}

int height(Tree<α> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

これについて簡単に説明します。定義しています…

  • Tree<α>二分木のノードを表す再帰型。各ノードvalueはあるタイプの αを、同じタイプのオプションleftおよびrightサブツリーへの参照を持っています。

  • height葉ノードからルートノードまでの最遠距離を返す関数t

さて、上の疑似コードを height適切なJava構文に!(簡潔にするために、オブジェクト指向やアクセシビリティ修飾子などのボイラープレートは省略し続けます。)2つの可能な解決策を示します。

1.ユニバーサルタイプソリューション:

最も明白な修正はheight、型パラメーターαをシグニチャーに導入することにより、単純にジェネリックにすることです。

<α> int height(Tree<α> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

これにより、変数を宣言し、タイプαの式を作成できます。応じて、、その関数内。だが...

2.実存型ソリューション:

メソッドの本体を見ると、タイプαの何かに実際にアクセスしたり、操作したりしていないことがわかります。その型を持つ式も、その型で宣言された変数もないので、なぜheight総称にする必要があるのでしょうか。なぜαを単純に忘れられないのですか?結局のところ、次のことが可能です。

int height(Tree<?> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

この回答の冒頭で書いたように、実存型と普遍型は本質的に相補的/二重です。したがって、普遍的な型の解決策がheight よりジェネリックにすることである場合、存在型は逆の効果があることを期待する必要があります:ジェネリックを少なくする、つまり型パラメーターαを非表示 /削除する

その結果、t.value識別子がバインドされていないため、このメソッドでの型を参照したり、その型の式を操作したりすることができなくなります。(?ワイルドカードは特殊なトークンであり、タイプを「キャプチャーする」識別子ではありません。)t.value事実上不透明になりました。おそらくそれを使ってできる唯一のことは、それを型キャストすることですObjectです。

概要:

===========================================================
                     |    universally       existentially
                     |  quantified type    quantified type
---------------------+-------------------------------------
 calling method      |                  
 needs to know       |        yes                no
 the type argument   |                 
---------------------+-------------------------------------
 called method       |                  
 can use / refer to  |        yes                no  
 the type argument   |                  
=====================+=====================================

3
良い説明。t.valueをObjectにキャストする必要はありません。単にObjectとして参照できます。そのため、存在型はメソッドをより汎用的にするものだと思います。t.valueについて知ることができる唯一のことは、それがオブジェクトであることですが、αについて特定のことを言うことができます(αがSerializableを拡張するなど)。
クレイグP.モトリン

1
その間私は私の答えが実在性が何であるかを実際に説明しないと信じるようになりました、そして私は私が「真実」に近いと思う私はKannan Goudanの答えの最初の2つの段落にもっと似ている別のものを書くことを考えています。そうは言っても、@ Craig:ジェネリックとの比較Objectは非常に興味深いです:どちらも静的に型に依存しないコードを記述できるという点で似ていますが、前者(ジェネリック)は利用可能な型情報のほとんどすべてを破棄するだけではありませんこの目標を達成する。この特定の意味では、ジェネリック薬はObjectIMOの改善策です。
stakx-2012年

1
@stakx、(有効Javaから)このコードではpublic static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); } private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); } Eあるuniversal type?表しますかexistential type
ケビンメレディス14

この答えは正しくありません。?型のinはint height(Tree<?> t)関数内ではまだ不明であり、渡すツリーを選択するのは呼び出し元であるため、呼び出し元によって決定されます。Javaでこれを存在型と呼んでも違います。?プレースホルダができるいくつかの状況では、Javaでexistentialsのフォームを実装するために使用されるが、これはその一つではありません。
ピーターホール

15

これらはすべて良い例ですが、少し違った方法で答えることにしました。数学から思い出してください。P(x)は、「すべてのxについて、P(x)であることを証明できる」という意味です。言い換えれば、それは一種の関数であり、あなたは私にxを与え、私はあなたのためにそれを証明する方法を持っています。

型理論では、証明についてではなく、型について話します。したがって、このスペースでは、「あなたが私に与えるどんなタイプXについても、私はあなたに特定のタイプPを与える」という意味です。さて、PはXについて、それが型であるという事実を除いて、あまり情報を提供しないので、PはXについてはあまり実行できませんが、いくつかの例があります。Pは、「同じタイプのすべてのペア」のタイプを作成できますP<X> = Pair<X, X> = (X, X)。または、オプションtype:を作成することもできますP<X> = Option<X> = X | Nil。ここで、Nilはnullポインターのタイプです。それからリストを作ることができます:List<X> = (X, List<X>) | Nil。最後のList<X>要素は再帰的であり、の値は、最初の要素がXで2番目の要素がaであるペア、List<X>またはnullポインタであることに注意してください。

今、数学∃xで。P(x)は、「P(x)が真になるような特定のxがあることを証明できる」ことを意味します。そのようなxはたくさんあるかもしれませんが、それを証明するには1つで十分です。それを考える別の方法は、空ではない証拠と証明のペアのセット{(x、P(x))}が存在しなければならないということです。

型理論に変換:ファミリー∃X.P<X>の型は、型Xと対応する型P<X>です。XをPに与える前に(Xについてはすべて知っているが、Pはほとんど知らないように)、反対が今は真であることに注意してください。P<X>Xに関する情報を提供することを約束するものではありません。Xが存在し、それが実際に型であるということだけです。

これはどのように役立ちますか?ええと、Pはその内部型Xを公開する方法を持つ型である可能性があります。例として、その状態Xの内部表現を非表示にするオブジェクトがあります。直接操作する方法はありませんが、その効果を観察することができます。 Pを突っ込んでください。このタイプの実装は多数ある可能性がありますが、どのタイプを選択しても、これらすべてのタイプを使用できます。


2
うーん、しかし、それがのP<X>代わりであることを知ることによって、関数は何を得るのですかP(同じ機能とコンテナータイプ、たとえば、それが含まれていることを知らないX)?
Claudiu 2012

3
厳密に言えば、∀x. P(x)の証明可能性については何も意味せずP(x)、真実のみを意味します。
R .. GitHub ICEのヘルプ停止

11

質問に直接回答するには:

ユニバーサルタイプでは、の使用にTタイプパラメータを含める必要がありますX。たとえばT<String>またはT<Integer>。存在する型のT場合、その型パラメーターは不明または無関係であるため、使用しないでください。使用するのはT(またはJavaでT<?>)だけです。

さらに詳しい情報:

ユニバーサル/抽象型と実存型は、オブジェクト/関数のコンシューマー/クライアントとそのプロデューサー/実装の間の二重の視点です。一方が普遍的なタイプを見るとき、もう一方は存在タイプを見る。

Javaでは、ジェネリッククラスを定義できます。

public class MyClass<T> {
   // T is existential in here
   T whatever; 
   public MyClass(T w) { this.whatever = w; }

   public static MyClass<?> secretMessage() { return new MyClass("bazzlebleeb"); }
}

// T is universal from out here
MyClass<String> mc1 = new MyClass("foo");
MyClass<Integer> mc2 = new MyClass(123);
MyClass<?> mc3 = MyClass.secretMessage();
  • 観点からクライアントMyClassTあなたがのために任意の型に置き換えることができますので、普遍的ですTあなたは、そのクラスを使用するときに、あなたがのインスタンスを使用するたびに、Tの実際の型を知っている必要がありますMyClass
  • インスタンスメソッドMyClass自体の観点から、T、実際のタイプがわからないため存在T
  • Javaでは?、存在型を表します-したがって、クラスの内部にいるときTは基本的に?です。のインスタンスMyClassTexistentialで処理する場合は、上記MyClass<?>secretMessage()例のように宣言できます。

他の場所で説明されているように、存在するタイプは、何かの実装の詳細を隠すために使用されることがあります。このJavaバージョンは次のようになります。

public class ToDraw<T> {
    T obj;
    Function<Pair<T,Graphics>, Void> draw;
    ToDraw(T obj, Function<Pair<T,Graphics>, Void>
    static void draw(ToDraw<?> d, Graphics g) { d.draw.apply(new Pair(d.obj, g)); }
}

// Now you can put these in a list and draw them like so:
List<ToDraw<?>> drawList = ... ;
for(td in drawList) ToDraw.draw(td);

これを適切にキャプチャするのは少し難しいです。Javaがそうではない、ある種の関数型プログラミング言語にいるふりをしているからです。しかし、ここでのポイントは、ある種の状態とその状態を操作する関数のリストをキャプチャしていて、状態部分の実際のタイプがわからないということですが、関数はすでにそのタイプと一致しているため、 。

現在、Javaでは、すべての非最終的な非プリミティブ型が部分的に存在しています。これは奇妙に聞こえるかもしれませObjectんが、として宣言された変数が潜在的にObject代わりにのため、特定の型を宣言することはできず、「この型またはサブクラス」のみを宣言できます。したがって、オブジェクトは状態のビットとその状態で動作する関数のリストとして表されます-呼び出す関数は実行時にルックアップによって決定されます。これは、存在状態の部分とその状態を操作する関数がある場合の上記の存在型の使用によく似ています。

サブタイプやキャストのない静的に型付けされたプログラミング言語では、存在型を使用すると、型の異なるオブジェクトのリストを管理できます。のリストにT<Int>を含めることはできませんT<Long>。ただし、のリストにT<?>はのバリエーションを含めることができ、Tさまざまな種類のデータをリストに入れて、それらをすべてintに変換する(またはデータ構造内で提供される操作を実行する)ことができます。

ほとんどの場合、存在型のレコードをクロージャーを使用せずにレコードに変換できます。クロージャも実在的に型付けされており、閉じられた自由変数は呼び出し元から隠されています。したがって、クロージャをサポートするが存在型をサポートしない言語を使用すると、オブジェクトの存在部分に入れたのと同じ隠された状態を共有するクロージャを作成できます。


11

存在型は不透明な型です。

Unixのファイルハンドルについて考えてみます。あなたはその型がintであることを知っているので、簡単に偽造することができます。たとえば、ハンドル43から読み取ろうとすることができます。プログラムがこの特定のハンドルでファイルを開いている場合は、そこから読み取ります。あなたのコードは悪質である必要はありません、ずさんなだけです(例えば、ハンドルは初期化されていない変数であるかもしれません)。

存在タイプはプログラムから隠されています。fopen存在タイプが返された場合、それを使用してできることは、この存在タイプを受け入れるいくつかのライブラリ関数で使用することだけです。たとえば、次の疑似コードはコンパイルされます。

let exfile = fopen("foo.txt"); // No type for exfile!
read(exfile, buf, size);

インターフェース「read」は次のように宣言されています。

次のようなタイプTが存在します。

size_t read(T exfile, char* buf, size_t size);

変数exfileは、int、a char*、struct Fileではなく、型システムで表現できるものではありません。型が不明な変数を宣言することはできず、ポインタをその不明な型にキャストすることもできません。言語はあなたをさせません。


9
これは機能しません。の署名read∃T.read(T file, ...)次にある場合、最初のパラメーターとして渡すことができるものはありません。何が仕事を持っているだろうfopenファイルハンドルを返すと、 read関数と同じ実存によってスコープ∃T.(T, read(T file, ...))
Kannan Goundan

2
これは単にADTについて話しているようです。
kizzx2

7

私は少し遅れているようですが、とにかく、このドキュメントは存在タイプが何であるかについての別の見方を追加します、特に言語に依存しませんが、存在タイプを理解することはかなり簡単になるはずです: .nl / groups / ST / Projects / ehc / ehc-book.pdf(第8章)

普遍的と実存的に定量化されたタイプの違いは、次の観察によって特徴付けることができます。

  • ∀数量化タイプの値を使用すると、数量化タイプ変数のインスタンス化に選択するタイプが決まります。たとえば、識別関数「id ::∀aa→a」の呼び出し元は、この特定のidアプリケーションのタイプ変数aに選択するタイプを決定します。関数アプリケーション「id 3」の場合、このタイプはIntと同じです。

  • ∃数量化タイプの値を作成すると、数量化タイプ変数のタイプが決定され、非表示になります。たとえば、「∃a。(a、a→Int)」の作成者は、「(3、λx→x)」からその型の値を作成した可能性があります。別の作成者が「( 'x'、λx→ord x)」から同じ型の値を作成しました。ユーザーの観点から見ると、両方の値は同じタイプであり、したがって交換可能です。値には、型変数aに対して選択された特定の型がありますが、どの型かわからないため、この情報を利用することはできません。この値固有のタイプ情報は「忘れられました」。私たちはそれが存在することを知っているだけです。


1
このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。
2015年

1
@sheilak:答えは、提案に感謝更新
themarketka

5

タイプパラメータのすべての値にユニバーサルタイプが存在します。存在タイプは、存在タイプの制約を満たすタイプパラメータの値に対してのみ存在します。

たとえば、Scalaで存在型を表現する1つの方法は、いくつかの上限または下限に制約されている抽象型です。

trait Existential {
  type Parameter <: Interface
}

同様に、制約付きユニバーサルタイプは、次の例のように存在タイプです。

trait Existential[Parameter <: Interface]

Interfaceインスタンス化可能なサブタイプはExistentialtype Parameterを実装する必要があるを定義する必要があるため、どの使用サイトでもを使用できますInterface

縮重場合 Scalaで実存タイプのが参照されることはないので、任意のサブタイプによって定義される必要はない抽象型です。これは事実上List[_] 、ScalaList<?>Javaの略記です。

私の答えは、抽象型と実存型を統合するというマーティンオデルスキーの提案に触発されました。付属のスライドは、理解の助けとなります。


1
上記の資料のいくつかを読んだことで、私の理解をうまくまとめたようです。ユニバーサルタイプは∀x.f(x)受信機能に対して不透明であり∃x.f(x)、存在タイプは特定のプロパティを持つように制限されています。通常、すべてのパラメーターは、その機能が直接操作するため、存在します。しかし、一般的なパラメータは、関数は、そのようなのような参照を取得するなどの基本的なユニバーサル操作を超えて、それらを管理しませんので、ユニバーサルのあるタイプを持っている可能性があります∀x.∃array.copy(src:array[x] dst:array[x]){...}
ジョージ・

ここで説明するように、stackoverflow.com / a / 19413755/3195266タイプメンバーは、IDタイプを介してユニバーサル数量化をエミュレートできます。そして、確かにforSome型パラメーターの存在量の定量化があります。
Netsu

3

抽象データ型と情報隠蔽の研究により、実存型がプログラミング言語に取り入れられました。データ型を抽象化すると、その型に関する情報が非表示になるため、その型のクライアントはそれを悪用することはできません。オブジェクトへの参照を持っているとしましょう...一部の言語では、その参照をバイトへの参照にキャストして、そのメモリの一部に対して必要なことを何でも実行できます。プログラムの動作を保証するために、オブジェクトの設計者が提供するメソッドを介して、オブジェクトへの参照のみを操作することを言語が強制すると便利です。タイプが存在することはわかっていますが、それ以上はありません。

見る:

抽象型には存在型、MITCHEL&PLOTKINがあります

http://theory.stanford.edu/~jcm/papers/mitch-plotkin-88.pdf


1

この図を作成しました。厳密かどうかわかりません。しかし、それが役に立てば、私は嬉しいです。 ここに画像の説明を入力してください


-6

私が理解しているように、それはインターフェース/抽象クラスを記述する数学的な方法です。

T =∃X{X a; int f(X); }

C#の場合、汎用の抽象型に変換されます。

abstract class MyType<T>{
    private T a;

    public abstract int f(T x);
}

「存在する」とは、ここで定義された規則に従うタイプがあることを意味します。

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