什么是存在型?
我通读维基百科文章存在的types 。 由于存在操作符(∃),我认为它们被称为存在types(existential types)。 不过,我不确定它的意义。 有什么区别
T = ∃X { X a; int f(X); }
和
T = ∀x { X a; int f(X); }
?
当某人定义了一个通用types∀X
他们说: 你可以插入任何你想要的types,我不需要知道任何关于types的任何事情,我只会把它称为X
当某人定义一个存在types∃X
他们会说: 我将在这里使用任何我想要的types; 你不会知道types的任何东西,所以你只能将它不透明地称为X
通用types让你写下如下的东西:
void copy<T>(List<T> source, List<T> dest) { ... }
copy
函数不知道T
实际上是什么,但是它不需要。
存在types会让你写下如下的东西:
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); } }
列表中的每个虚拟机实现可以具有不同的字节码types。 runAllCompilers
函数不知道字节码types是什么,但它不需要; 它所做的只是将VirtualMachine.compile
的字节码转发给VirtualMachine.run
。
Javatypes通配符(例如: List<?>
)是一种非常有限的存在types。
更新:忘了提及你可以用通用types来模拟存在types。 首先,包装你的通用types来隐藏types参数。 其次,倒置控制(这有效地互换了上面定义中的“你”和“我”部分,这是存在与普遍之间的主要区别)。
// 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
调用我们自己的VMHandler
,它具有通用types的handle
函数。 净效果是一样的,我们的代码必须把B
视为不透明。
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); } }); } }
一个虚拟机实现的例子
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); } }
像∃x. F(x)
这样的存在types的值∃x. F(x)
∃x. F(x)
是包含某种types x
和F(x)
types值的对。 而像∀x. F(x)
这样的多态types的值∀x. F(x)
∀x. F(x)
是一个函数 ,它接受某种types的x
并产生一个types为F(x)
。 在这两种情况下,typesclosures在某种types的构造函数F
。
请注意,这个视图混合了types和值。 存在certificate是一种types和一种价值。 通用certificate是按types(或从types到值的映射)索引的整个值族。
所以你指定的两种types的区别如下:
T = ∃X { X a; int f(X); }
这意味着:typesT
的值包含一个名为X
的types,一个值a:X
和一个函数f:X->int
。 T
types值的生产者可以select任何types的X
,消费者不可能知道X
除了有一个叫做a
例子,并且这个值可以通过赋给f
来变成一个int
。 换句话说,typesT
的值知道如何以某种方式产生一个int
。 那么,我们可以消除中间typesX
,只是说:
T = int
普遍量化的有点不同。
T = ∀X { X a; int f(X); }
这意味着:typesT
的值可以被赋予任何types的X
,并且无论X
是什么 ,都会产生一个值a:X
和一个函数f:X->int
。 换句话说:typesT
的值的消费者可以为X
select任何types。 而T
types值的生产者根本不知道X
任何内容,但是必须能够为X
任何select产生一个值a
,并且能够把这个值转化为一个int
。
显然实现这种types是不可能的,因为没有任何程序可以产生每种可想象的types的值。 除非你允许荒谬像null
或底部。
由于存在主义是一对,所以存在主义论证可以通过柯里化转化为普遍主义论证。
(∃b. F(b)) -> Int
是相同的:
∀b. (F(b) -> Int)
前者是一个二级存在。 这导致以下有用的属性:
每个存在量化types的秩
n+1
是一个普遍量化types的秩n
。
有一个标准的algorithm将存在转化为共相,称为Skolemization 。
我认为解释存在主义types和普遍types是有意义的,因为这两个概念是互补的,即一个是另一个的“相反”。
我不能回答关于存在types的每一个细节(比如给出一个确切的定义,列出所有可能的用途,它们与抽象数据types的关系等),因为我根本就不够知识。 我只会演示(使用Java) HaskellWiki这篇文章所说的存在types的主要作用:
存在types可以用于几个不同的目的。 但他们所做的是在右侧“隐藏”一个typesvariables。 通常情况下,右侧出现的任何typesvariables也必须出现在左侧[…]
设置示例:
下面的伪代码并不是非常有效的Java,尽pipe它很容易解决这个问题。 事实上,这正是我要做的这个答案!
class Tree<α> { α value; Tree<α> left; Tree<α> right; } int height(Tree<α> t) { return (t != null) ? 1 + max( height(t.left), height(t.right) ) : 0; }
让我简单地为你解释这一点。 我们正在界定…
-
表示二叉树中的节点的recursiontypes
Tree<α>
。 每个节点存储一些types为α的value
,并引用相同types的可选right
子树。 -
函数
height
返回从任何叶节点到根节点t
的最远距离。
现在,让我们把上面的伪代码转换成适当的Java语法! (为了简洁起见,我将继续省略一些样板,比如面向对象和可访问性修饰符)。我将展示两种可能的解决scheme。
1.通用型解决scheme:
最明显的解决方法是通过在其签名中引入types参数α来简单地生成height
通用:
<α> int height(Tree<α> t) { return (t != null) ? 1 + max( height(t.left), height(t.right) ) : 0; }
这将允许你声明variables,并在该函数内部创buildtypesα的expression式,如果你想的话。 但…
2.存在型解决scheme:
如果你看看我们的方法的身体,你会注意到我们实际上并没有访问或使用任何types的α ! 没有这种types的expression式,也没有用这种types声明的任何variables…那么,为什么我们必须做height
通用呢? 为什么我们不能简单的忘记α ? 事实certificate,我们可以:
int height(Tree<?> t) { return (t != null) ? 1 + max( height(t.left), height(t.right) ) : 0; }
正如我在这个答案一开始所写的那样,存在主义和普遍主义是互补的/双重的。 因此,如果通用types的解决scheme是使height
更通用,那么我们应该期望存在types具有相反的效果:使其不那么通用,即通过隐藏/去除types参数α 。
因此,在这个方法中你不能再引用t.value
的types,也不能操作这个types的任何expression式,因为没有标识符被绑定到它。 ( ?
通配符是一个特殊的标记,而不是“捕获”一个types的标识符。) 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 | =====================+=====================================
这些都是很好的例子,但我select回答有点不同。 从math中回想起那个∀x。 P(x)的意思是“对于所有的x,我可以certificateP(x)”。 换句话说,这是一种function,你给我一个x,我有一个方法来certificate你的。
在types理论中,我们不是在谈论证据,而是在谈论types。 所以在这个空间中,我们的意思是“对于任何typesX你给我,我会给你一个特定的typesP”。 现在,由于除了它是一个types的事实之外,我们没有给出关于X的许多信息,所以P不能用它做很多事情,但是也有一些例子。 P可以创build“相同types的所有对”的types: P<X> = Pair<X, X> = (X, X)
。 或者我们可以创build选项types: P<X> = Option<X> = X | Nil
P<X> = Option<X> = X | Nil
,其中Nil是空指针的types。 我们可以列出它: List<X> = (X, List<X>) | Nil
List<X> = (X, List<X>) | Nil
。 请注意,最后一个是recursion的, List<X>
值或者是第一个元素是X的元素,第二个元素是List<X>
,否则它是空指针。
现在,在math∃x。 P(x)的意思是“我可以certificate有一个特定的x使得P(x)是真的”。 可能有许多这样的x,但是为了certificate这一点,就足够了。 另一种考虑的方法是必须存在一组非空的证据和certificate对{(x,P(x))}。
转化为types论:族中的一个types∃XP<X>
是一个typesX和相应的typesP<X>
。 注意,在我们给X给P之前(这样我们知道关于X的一切,但是P很less),现在恰恰相反。 P<X>
不保证给出有关X的任何信息,只是有一个,而且确实是一个types。
这有用吗? 那么,P可能是一种暴露其内部typesX的方式。一个例子是隐藏其状态X的内部表示的对象。虽然我们没有办法直接操纵它,但我们可以通过在P上戳。可能有很多这种types的实现,但是无论select哪一种,都可以使用所有这些types。
存在型是一种不透明的types。
想一想Unix中的文件句柄。 你知道它的types是int,所以你可以很容易地伪造它。 例如,你可以尝试从句柄43中读取。如果程序有这个特定句柄打开的文件,你可以从中读取。 你的代码不必是恶意的,只是马虎(例如,句柄可能是一个未初始化的variables)。
一个存在types是从你的程序隐藏的。 如果fopen返回一个存在types,你所能做的就是将它与一些接受这个存在types的库函数一起使用。 例如,下面的伪代码将被编译:
让exfile = fopen(“foo.txt”); //没有types的exfile! 读取(exfile,buf,size);
接口“read”被声明为:
有一个typesT,这样:
size_t read(T exfile,char * buf,size_t size);
variablesexfile不是一个int,不是一个char *,不是一个结构文件 – 你可以在types系统中expression任何东西。 你不能声明一个types未知的variables,也不能将一个指针转换成未知types的指针。 语言不会让你。
要直接回答你的问题:
对于通用types, T
使用必须包含types参数X
例如T<String>
或T<Integer>
。 对于T
的存在types使用,不包含该types参数,因为它是未知的或不相关的 – 只要使用T
(或在Java T<?>
)。
更多信息:
通用/抽象types和存在types是对象/function的消费者/客户与其生产者/实施者之间透视的双重性。 当一方看到一个普遍types时,另一个看到一个存在types。
在Java中,您可以定义一个generics类:
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();
- 从
MyClass
客户端的angular度来看,T
是通用的,因为当你使用这个类时,你可以用T
代替任何types的T
,而且当你使用MyClass
的实例时,你必须知道T的实际types - 从
MyClass
本身的实例方法来看,T
是存在的,因为它不知道T
的真实types - 在Java中,
?
代表存在主义的types – 因此,当你在课堂上时,T
基本上是?
。 如果你想用T
存在处理MyClass
一个实例,你可以在上面的secretMessage()
例子中声明MyClass<?>
。
存在types有时用于隐藏某些东西的实现细节,正如其他地方所讨论的那样。 这个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并不是。 但是这里要指出的是,你正在捕获某种状态以及在该状态下操作的函数列表,而且你不知道状态部分的实际types,但是函数已经与该types匹配。
现在,在Java中,所有非最终的非原始types都是部分存在的。 这可能听起来很奇怪,但是因为声明为Object
的variables可能是Object
的子类,所以不能声明特定的types,只能是“this type or subclass”。 因此,对象被表示为一个状态位加上一个在该状态下运行的函数列表 – 确切地说哪个函数是在运行时通过查找来确定的。 这与使用上面的存在types非常相像,在这个types中存在一个存在状态部分和一个在该状态下运行的函数。
在没有子types和强制types的静态types编程语言中,存在types允许pipe理不同types对象的列表。 T<Int>
列表不能包含T<Long>
。 但是, T<?>
的列表可以包含T
任何变体,允许将许多不同types的数据放入列表中,并根据需要将它们全部转换为int(或者在数据结构内部进行任何操作)。
一个人几乎总是可以将一个存在types的logging转换成一个logging而不使用闭包。 闭包也是存在的types,因为closures的自由variables对调用者是隐藏的。 因此,一种支持闭包而不是存在types的语言可以让你创build共享隐藏状态的闭包,这些隐藏状态将放入对象的存在部分。
似乎我来得晚了一点,但无论如何,这个文件增加了对存在types的另一种观点,虽然不是专门语言不可知的,但是理解存在types应该是相当容易的: http://www.cs.uu .nl / groups / ST / Projects / ehc / ehc-book.pdf (第8章)
普遍存在和存在量化types之间的差异可以通过以下观察来表征:
具有量化types的值的使用决定了量化typesvariables实例化时要select的types。 例如,身份函数“id ::∀aa→a”的调用者为这个特定的id应用程序确定为typesvariablesaselect的types。 对于函数应用程序“id 3”,这个types等于Int。
具有量化types的值的创build决定并隐藏了量化typesvariables的types。 例如,“∃a。(a,a→Int)”的创build者可能已经从“(3,λx→x)”构造了该types的值。 另一个创build者已经从“('x',λx→ord x)”构造了一个相同types的值。 从用户的angular度来看,这两个值具有相同的types,因此可以互换。 该值具有为typesvariablesaselect的特定types,但是我们不知道哪种types,所以这个信息不能再被利用。 这个价值特定types的信息已经被“遗忘”了。 我们只知道它存在。
对抽象数据types和信息隐藏的研究将存在types引入了编程语言。 使数据types抽象隐藏有关该types的信息,所以这种types的客户端不能滥用它。 假设你有一个对象的引用…一些语言允许你把这个引用强制转换为对字节的引用,并对你想要的内存做任何事情。 为了保证程序的行为,对于一种语言来说,强制执行的操作是非常有用的,即只能通过对象的devise者所提供的方法来操作对象的引用。 你知道types的存在,但没有更多。
看到:
抽象types有存在types,MITCHEL和PLOTKIN
所有types参数的值都存在通用types。 存在types仅存在满足存在types的约束的types参数的值。
例如,在Scala中,expression存在types的一种方式是被限制在一些上限或下限的抽象types。
trait Existential { type Parameter <: Interface }
相当于一个受约束的通用types是一个存在types,如下例所示。
trait Existential[Parameter <: Interface]
任何使用站点都可以使用Interface
因为Existential
任何可实例化的子types必须定义必须实现Interface
的type Parameter
。
Scala中一个存在types的退化情况是一个从不被引用的抽象types,因此不需要被任何子types定义。 这实际上有一个Scala中的List[_]
和Java中的List[?]
的简写符号。
我的回答受到奥德斯基(Martin Odersky) 关于统一抽象和存在types的提议的启发。 随附的幻灯片帮助理解。
据我所知,这是一个描述接口/抽象类的math方法。
至于T =∃X{X a; int f(X); }
对于C#它将转化为一个通用的抽象types:
abstract class MyType<T>{ private T a; public abstract int f(T x); }
“存在”只是意味着某种types服从于这里定义的规则。