什么是存在主义类型?

时间:2008-11-15 07:31:01

标签: language-agnostic types type-systems existential-type

我通读维基百科文章 Existential types 。我认为,由于存在运算符(∃),它们被称为存在类型。不过,我不确定它的重点是什么。

之间的区别是什么
T = ∃X { X a; int f(X); }

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

11 个答案:

答案 0 :(得分:170)

当有人定义通用类型∀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<?>)是一种非常有限的存在类型。

更新:忘了提及您可以使用通用类型来模拟存在类型。首先,包装通用类型以隐藏类型参数。第二,反转控制(这有效地交换了上面定义中的“你”和“我”部分,这是存在性和普遍性之间的主要区别。)

// 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,它具有通用类型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);
   }
}

答案 1 :(得分:96)

∃x. F(x) 这样的存在类型的值是一对,其中包含一些类型 x和一个<类型F(x)的em> value 。而像∀x. F(x)这样的多态类型的值是函数,它采用某种类型x生成类型为F(x)的值。在这两种情况下,类型都会关闭某些类型的构造函数F

请注意,此视图会混合使用类型和值。存在证明是一种类型和一种价值。通用证明是按类型索引的整个值系列(或从类型到值的映射)。

因此,您指定的两种类型之间的区别如下:

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

这意味着:类型T的值包含名为X的类型,值a:X和函数f:X->int。类型为T的值的生产者可以为X选择任何类型,并且消费者无法了解X的任何内容。除了有一个名为a的示例,并且可以通过将其赋予int将其转换为f。换句话说,类型T的值知道如何以某种方式生成int。好吧,我们可以删除中间类型X,然后说:

T = int

普遍量化的一个有点不同。

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

这意味着:T类型的值可以被赋予任何类型X,并且它将生成值a:X,并且函数f:X->int 否无论X是什么。换句话说:T类型的消费者可以为X选择任何类型。类型为T的值的生产者根本不了解X,但必须能够为a的任何选择生成值X ,并能够将这样的值转换为int

显然,实现这种类型是不可能的,因为没有程序可以产生每种可想象的类型的值。除非你允许null或者底部荒谬。

由于存在主义是一对,存在主义论证可以通过 currying 转换为通用论证。

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

与:

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

前者是 rank-2 存在主义。这导致以下有用的属性:

  

每个存在量化类型的排名n+1都是一种普遍量化的排名n

有一种标准算法可以将存在感变为普遍性,称为Skolemization

答案 2 :(得分:30)

我认为将存在主义类型与通用类型一起解释是有意义的,因为这两个概念是互补的,即一个是另一个概念的“对立面”。

我无法回答有关存在类型的所有细节(例如给出一个确切的定义,列出所有可能的用途,它们与抽象数据类型的关系等),因为我根本不够了解它。我将仅演示(使用Java)this HaskellWiki article状态是存在类型的主要影响:

  

存在类型可以用于用于多种不同目的。但他们的事情就是“隐藏”右侧的类型变量。通常,出现在右侧的任何类型变量也必须出现在左侧[...]

示例设置:

以下伪代码不是非常有效的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,并且引用了相同类型的可选leftright子树。

  • 函数height,它返回从任何叶节点到根节点t的最远距离。

现在,让我们将height的上述伪代码转换为正确的Java语法! (为了简洁起见,我将继续省略一些样板文件,例如面向对象和可访问性修饰符。)我将展示两种可能的解决方案。

<强> 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 更多泛型,那么我们应该期望存在类型具有相反的效果:使其 less 泛型,即隐藏/删除类型参数α

因此,您不能再在此方法中引用t.value的类型,也不能操纵该类型的任何表达式,因为没有绑定到它的标识符。 (? wildcard是一个特殊标记,而不是“捕获”类型的标识符。)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 :(得分:13)

这些都是很好的例子,但我选择的方式有点不同。回想一下数学,即∀x。 P(x)表示“对于所有x,我可以证明P(x)”。换句话说,它是一种功能,你给我一个x,我有一个方法来为你证明。

在类型理论中,我们不是在谈论证据,而是谈论类型。因此,在这个空间中,我们的意思是“对于任何类型的X,你给我,我会给你一个特定的类型P”。现在,既然我们不提供关于X的大量信息,除了它是一个类型,P不能用它做多少,但是有一些例子。 P可以创建“所有相同类型的对”的类型:P<X> = Pair<X, X> = (X, X)。或者我们可以创建选项类型:P<X> = Option<X> = X | Nil,其中Nil是空指针的类型。我们可以列出一个列表:List<X> = (X, List<X>) | Nil。请注意,最后一个是递归的,List<X>的值是对,其中第一个元素是X,第二个元素是List<X>,否则它是空指针。

现在,在数学∃x中。 P(x)表示“我可以证明存在特定的x使得P(x)为真”。可能有很多这样的x,但为了证明这一点,一个就足够了。另一种思考方式是必须存在一组非空的证据和证据对{(x,P(x))}。

转换为类型理论:族∃X.P<X>中的类型是类型X和对应类型P<X>。请注意,在我们将X赋予P之前,(因此我们知道关于X的所有内容但P非常小),现在情况正好相反。 P<X>不承诺提供有关X的任何信息,只是存在一个,并且它确实是一种类型。

这有用吗?好吧,P可以是一种暴露其内部类型X的方式。一个例子是隐藏其状态X的内部表示的对象。虽然我们无法直接操纵它,但我们可以通过戳P。可能有很多这种类型的实现,但无论选择哪一种,都可以使用所有这些类型。

答案 4 :(得分:12)

存在类型是不透明类型。

想想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,不是char*,不是struct File - 你可以在类型系统中表达任何内容。您不能声明类型未知的变量,也不能将指针转换为该未知类型。语言不会让你。

答案 5 :(得分:9)

直接回答你的问题:

使用通用类型时,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();
  • MyClass客户端的角度来看,T是通用的,因为您在使用该类时可以替换T的任何类型,并且您必须每当您使用MyClass
  • 的实例时,都要知道T的实际类型
  • MyClass本身的实例方法的角度来看,T是存在的,因为它不知道T
  • 的真实类型
  • 在Java中,?代表存在类型 - 因此当您进入课堂时,T基本上是?。如果您要处理存在MyClass存在的T实例,可以在上面的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(或者在数据中提供任何操作)结构)按需。

人们几乎总是可以将具有存在类型的记录转换为记录而不使用闭包。闭包也是存在类型的,因为它被关闭的自由变量对调用者是隐藏的。因此,支持闭包但不支持存在类型的语言可以允许您创建共享隐藏状态的闭包,这些隐藏状态可以放入对象的存在部分。

答案 6 :(得分:6)

似乎我来得有点迟了,但无论如何,这个文档增加了另一种关于存在类型的看法,虽然不是特定于语言不可知的,但应该更容易理解存在类型:http://www.cs.uu.nl/groups/ST/Projects/ehc/ehc-book.pdf(第8章)

  

普遍存在量和存在量化类型之间的差异可以通过以下观察来表征:

     
      
  • 使用具有∀量化类型的值确定了为量化类型变量的实例化选择的类型。例如,标识函数“id ::∀a.a→a”的调用者确定要为此特定id应用程序选择类型变量a的类型。对于函数应用程序“id 3”,此类型等于Int。

  •   
  • 使用∃量化类型创建值可确定并隐藏量化类型变量的类型。例如,“∃a。(a,a→Int)”的创建者可以从“(3,λx→x)”构造该类型的值;另一个创建者从“('x',λx→ord x)”构造了一个具有相同类型的值。从用户的角度来看,两个值具有相同的类型,因此是可互换的。该值具有为类型变量a选择的特定类型,但我们不知道哪种类型,因此无法再利用此信息。该值特定类型信息已被“遗忘”;我们只知道它存在。

  •   

答案 7 :(得分:4)

对于类型参数的所有值,存在通用类型。存在类型仅存在满足存在类型约束的类型参数的值。

例如,在Scala中,表达存在类型的一种方法是一种抽象类型,它被约束到某些上限或下限。

trait Existential {
  type Parameter <: Interface
}

等效约束通用类型是一种存在类型,如下例所示。

trait Existential[Parameter <: Interface]

任何使用网站都可以使用Interface,因为Existential的任何可实例化的子类型都必须定义必须实现type Parameter的{​​{1}}。

Scala中存在类型的degenerate case是一种从不引用的抽象类型,因此不需要由任何子类型定义。这实际上在Java中有Interface in ScalaList[_]的简写符号。

我的答案受到了马丁奥德斯基的proposal to unify抽象和存在主义类型的启发。 accompanying slide有助于理解。

答案 8 :(得分:3)

对抽象数据类型和信息隐藏的研究将存在类型带入了编程语言。使数据类型抽象隐藏有关该类型的信息,因此该类型的客户端不能滥用它。假设您有一个对象的引用...某些语言允许您将该引用转换为对字节的引用,并对该内存执行任何您想要的操作。为了保证程序的行为,对于一种语言来说,强制您只通过对象设计者提供的方法对对象的引用起作用是很有用的。你知道这种类型存在,但仅此而已。

  

请参阅:

     

抽象类型具有存在类型,MITCHEL&amp;普洛特金

     

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

答案 9 :(得分:1)

我创建了这个图。我不知道它是否严格。但是,如果有帮助,我很高兴。 enter image description here

答案 10 :(得分:-5)

据我所知,这是描述接口/抽象类的数学方法。

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

对于C#,它将转换为通用抽象类型:

abstract class MyType<T>{
    private T a;

    public abstract int f(T x);
}

“存在主义”只意味着某种类型符合此处定义的规则。