O que é um tipo existencial?

171

Eu li o artigo da Wikipedia Tipos existenciais . Concluí que eles são chamados de tipos existenciais por causa do operador existencial (∃). Não tenho certeza de qual é o objetivo disso. Qual é a diferença entre

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

e

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

?

Claudiu
fonte
8
Este pode ser um bom tópico para programmers.stackexchange.com. programmers.stackexchange.com
jpierson

Respostas:

192

Quando alguém define um tipo universal, ∀Xeles estão dizendo: Você pode conectar o tipo que quiser, não preciso saber nada sobre o tipo para fazer meu trabalho, apenas me referirei a ele de forma opostaX .

Quando alguém define um tipo existencial, ∃Xeles estão dizendo: usarei o tipo que quiser aqui; você não saberá nada sobre o tipo; portanto, você só pode se referir a ele de forma oposta comoX .

Os tipos universais permitem escrever coisas como:

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

A copyfunção não tem idéia do que Trealmente será, mas não precisa.

Os tipos existentes permitem escrever coisas como:

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);
   }
}

Cada implementação de máquina virtual na lista pode ter um tipo de bytecode diferente. A runAllCompilersfunção não tem idéia de qual é o tipo de bytecode, mas não precisa; tudo o que faz é retransmitir o bytecode de VirtualMachine.compilepara VirtualMachine.run.

Os curingas do tipo Java (ex:) List<?>são uma forma muito limitada de tipos existenciais.

Atualização: Esqueci de mencionar que você pode simular tipos existenciais com tipos universais. Primeiro, envolva seu tipo universal para ocultar o parâmetro type. Segundo, controle invertido (isso efetivamente troca a parte "você" e "I" nas definições acima, que é a principal diferença entre existenciais e universais).

// 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);
}

Agora podemos ter VMWrappernossa própria chamada, VMHandlerque possui uma handlefunção de tipo universal . O efeito líquido é o mesmo, nosso código deve ser tratado Bcomo opaco.

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);
         }
      });
   }
}

Um exemplo de implementação de 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);
   }
}
Kannan Goundan
fonte
12
@ Kannan, +1 para uma resposta muito útil, mas um pouco difícil de entender: 1. Acho que ajudaria se você pudesse ser mais explícito sobre a natureza dupla dos tipos existencial e universal. Só percebi por acidente como você formulou os dois primeiros parágrafos de maneira muito semelhante; somente mais tarde você afirma explicitamente que ambas as definições são basicamente as mesmas, mas com "I" e "você" invertidos. Além disso, não entendi imediatamente o que "eu" e "você" devem se referir.
stakx - não está mais contribuindo com
2
(continuação :) 2. Eu não entendo completamente o significado da notação matemática em List<∃B:VirtualMachine<B>> vmsor for (∃B:VirtualMachine<B> vm : vms). (Como esses são tipos genéricos, você não poderia ter usado os ?curingas de Java em vez da sintaxe "criada por você"?). Acho que pode ser útil ter um exemplo de código em que nenhum tipo genérico ∃B:VirtualMachine<B>esteja envolvido, mas sim um "direto" ∃B, porque tipos genéricos são facilmente associados a tipos universais após seus primeiros exemplos de código.
stakx - não está mais contribuindo com
2
Eu costumava ∃Bser explícito sobre onde a quantificação está acontecendo. Com a sintaxe curinga, o quantificador está implícito ( List<List<?>>na verdade significa ∃T:List<List<T>>e não List<∃T:List<T>>). Além disso, a quantificação explícita permite que você se refira ao tipo (modifiquei o exemplo para tirar vantagem disso armazenando o bytecode do tipo Bem uma variável temporária).
Kannan Goundan
2
A notação matemática usada aqui é tão desleixada como o inferno, mas não acho que seja culpa do atendente (é padrão). Ainda assim, melhor não abusar os quantificadores existenciais e universais em forma de um talvez ...
Noldorin
2
@Kannan_Goundan, eu gostaria de saber o que faz você dizer que os curingas Java são uma versão muito limitada disso. Você sabia que poderia implementar sua primeira função de exemplo runAllCompilers em Java puro (com uma função auxiliar para recuperar (dar um nome) ao parâmetro wilcard)?
LP_
107

Um valor de um tipo existencial como ∃x. F(x) é um par que contém algum tipo x e um valor do tipo F(x). Considerando que um valor de um tipo polimórfico como ∀x. F(x)é uma função que pega algum tipo xe produz um valor de tipo F(x). Nos dois casos, o tipo fecha sobre algum construtor de tipo F.

Observe que essa visão combina tipos e valores. A prova existencial é um tipo e um valor. A prova universal é uma família inteira de valores indexados por tipo (ou um mapeamento de tipos para valores).

Portanto, a diferença entre os dois tipos que você especificou é a seguinte:

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

Isso significa: Um valor do tipo Tcontém um tipo chamado X, um valor a:Xe uma função f:X->int. Um produtor de valores do tipo Tpode escolher qualquer tipo Xe o consumidor não pode saber nada X. Exceto que há um exemplo disso chamado ae que esse valor pode ser transformado em int, dando-o a f. Em outras palavras, um valor do tipo Tsabe como produzir uma intmaneira. Bem, podemos eliminar o tipo intermediário Xe apenas dizer:

T = int

O quantificado universalmente é um pouco diferente.

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

Isso significa: Um valor do tipo Tpode receber qualquer tipo Xe produzirá um valor a:Xe uma função, f:X->int independentemente de qual Xseja . Em outras palavras: um consumidor de valores do tipo Tpode escolher qualquer tipo para X. E um produtor de valores do tipo Tnão pode saber absolutamente nada X, mas deve ser capaz de produzir um valor apara qualquer escolha Xe transformar esse valor em um int.

Obviamente, implementar esse tipo é impossível, porque não existe um programa que possa produzir um valor para todos os tipos imagináveis. A menos que você permita absurdos como nullou fundos.

Como um existencial é um par, um argumento existencial pode ser convertido em universal por meio de currying .

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

é o mesmo que:

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

O primeiro é um existencial de classificação 2 . Isso leva à seguinte propriedade útil:

Todo tipo de classificação existencialmente quantificado n+1é um tipo universalmente quantificado n.

Existe um algoritmo padrão para transformar existenciais em universais, chamado Skolemization .

Apocalisp
fonte
7
Pode ser útil (ou não) de mencionar Skolemization en.wikipedia.org/wiki/Skolem_normal_form
Geoff Reedy
34

Eu acho que faz sentido explicar tipos existenciais junto com tipos universais, já que os dois conceitos são complementares, ou seja, um é o "oposto" do outro.

Não posso responder a todos os detalhes sobre tipos existenciais (como fornecer uma definição exata, listar todos os usos possíveis, sua relação com tipos abstratos de dados etc.) porque simplesmente não tenho conhecimento suficiente para isso. Vou demonstrar apenas (usando Java) o que este artigo do HaskellWiki afirma ser o principal efeito dos tipos existenciais:

Tipos existentes podem ser usados para vários propósitos diferentes. Mas o que eles fazem é "ocultar" uma variável de tipo no lado direito. Normalmente, qualquer variável de tipo que aparece à direita também deve aparecer à esquerda […]

Exemplo de configuração:

O pseudo-código a seguir não é Java totalmente válido, mesmo que fosse fácil o suficiente para corrigir isso. De fato, é exatamente isso que vou fazer nesta resposta!

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

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

Deixe-me explicar brevemente isso para você. Estamos definindo ...

  • um tipo recursivo Tree<α>que representa um nó em uma árvore binária. Cada nó armazena um valuede algum tipo α e possui referências a subárvores lefte opcionais rightdo mesmo tipo.

  • uma função heightque retorna a maior distância de qualquer nó folha ao nó raiz t.

Agora, vamos transformar o pseudocódigo acima heightna sintaxe Java apropriada! (Continuarei omitindo alguns clichês por questões de brevidade, como orientação a objetos e modificadores de acessibilidade.) Vou mostrar duas soluções possíveis.

1. Solução de tipo universal:

A correção mais óbvia é simplesmente tornar heightgenérico introduzindo o parâmetro de tipo α em sua assinatura:

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

Isso permitiria declarar variáveis ​​e criar expressões do tipo α dentro dessa função, se você quisesse. Mas...

2. Solução do tipo existencial:

Se você olhar para o corpo do nosso método, perceberá que não estamos realmente acessando ou trabalhando com algo do tipo α ! Não há expressões com esse tipo, nem nenhuma variável declarada com esse tipo ... então, por que precisamos tornar heightgenéricos? Por que não podemos simplesmente esquecer α ? Como se vê, podemos:

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

Como escrevi no começo desta resposta, os tipos existencial e universal são complementares / de natureza dupla. Assim, se a solução universal de tipos se tornasse height mais genérica, devemos esperar que os tipos existenciais tenham o efeito oposto: torná-los menos genéricos, ou seja, ocultando / removendo o parâmetro de tipo α .

Como conseqüência, você não pode mais se referir ao tipo t.valuedeste método nem manipular nenhuma expressão desse tipo, porque nenhum identificador foi vinculado a ele. (O ?curinga é um token especial, não um identificador que "captura" um tipo.) t.valueTornou-se efetivamente opaco; talvez a única coisa que você ainda possa fazer com ela seja a transmissão do tipo Object.

Resumo:

===========================================================
                     |    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   |                  
=====================+=====================================
stakx - não está mais contribuindo
fonte
3
Boa explicação. Você não precisa converter t.value para Object, apenas pode se referir a ele como Object. Eu diria que o tipo existencial torna o método mais genérico por causa disso. A única coisa que você pode saber sobre t.value é que ele é um Objeto, ao passo que você poderia ter dito algo específico sobre α (como α estende Serializable).
Craig P. Motlin
1
Enquanto isso, acredito que minha resposta realmente não explica o que é existencial, e estou pensando em escrever outro que seja mais parecido com os dois primeiros parágrafos da resposta de Kannan Goudan, que acho que está mais próximo da "verdade". Dito isto, @Craig: comparar genéricos com Objecté bastante interessante: embora ambos sejam semelhantes, pois permitem escrever código estaticamente independente de tipo, o primeiro (genéricos) não apenas joga fora quase todas as informações de tipo disponíveis para alcançar esse objetivo. Nesse sentido particular, os genéricos são um remédio para a ObjectIMO.
stakx - não está mais contribuindo em
1
@stakx, neste código (do Effective 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é um universal typee ?representa um existential type?
Kevin Meredith
Esta resposta não está correta. O ?tipo in int height(Tree<?> t)ainda não é conhecido dentro da função e ainda é determinado pelo responsável pela chamada porque foi o responsável pela escolha da árvore a ser transmitida. Mesmo se as pessoas o chamarem de tipo existencial em Java, não é. O ?espaço reservado pode ser usado para implementar uma forma de existencial em Java, em algumas circunstâncias, mas esse não é um deles.
Peter Hall
15

Todos esses são bons exemplos, mas opto por responder um pouco diferente. Lembre-se de matemática, que ∀x. P (x) significa "para todos os x, posso provar que P (x)". Em outras palavras, é um tipo de função, você me dá um x e eu tenho um método para provar isso para você.

Na teoria dos tipos, não estamos falando de provas, mas de tipos. Portanto, neste espaço, queremos dizer "para qualquer tipo X que você me der, eu lhe darei um tipo específico P". Agora, como não fornecemos a P muitas informações sobre X, além de ser um tipo, P não pode fazer muito com ele, mas existem alguns exemplos. P pode criar o tipo de "todos os pares do mesmo tipo": P<X> = Pair<X, X> = (X, X). Ou podemos criar o tipo de opção P<X> = Option<X> = X | Nil:, onde Nil é o tipo dos ponteiros nulos. Podemos fazer uma lista com isso: List<X> = (X, List<X>) | Nil. Observe que o último é recursivo, os valores de List<X>são pares onde o primeiro elemento é um X e o segundo elemento é um List<X>ou então é um ponteiro nulo.

Agora, em matemática ∃x. P (x) significa "Eu posso provar que existe um x específico que P (x) é verdadeiro". Pode haver muitos desses x's, mas para provar isso, basta um. Outra maneira de pensar é que deve existir um conjunto não vazio de pares de evidências e provas {(x, P (x))}.

Traduzido para a teoria dos tipos: Um tipo na família ∃X.P<X>é um tipo X e um tipo correspondente P<X>. Observe que, antes de darmos X a P (para que soubéssemos tudo sobre X, mas P muito pouco), o oposto é verdadeiro agora. P<X>não promete fornecer nenhuma informação sobre o X, apenas que existe um e que é realmente um tipo.

Como isso é útil? Bem, P pode ser um tipo que exiba seu tipo interno X. Um exemplo seria um objeto que oculta a representação interna de seu estado X. Embora não tenhamos como manipulá-lo diretamente, podemos observar seu efeito cutucando P. Poderia haver muitas implementações desse tipo, mas você poderia usar todos esses tipos, independentemente de qual deles foi escolhido.

Rogon
fonte
2
Hmm, mas o que a função ganha por saber que é uma (e P<X>não uma P(mesma funcionalidade e tipo de contêiner, digamos, mas você não sabe que ela contém X))?
Claudiu
3
Estritamente falando, ∀x. P(x)não significa nada sobre a provabilidade de P(x), apenas a verdade.
R .. GitHub Pare de ajudar o gelo
11

Para responder diretamente à sua pergunta:

Com o tipo universal, os usos de Tdevem incluir o parâmetro type X. Por exemplo T<String>ou T<Integer>. Para o tipo existencial, o uso de Tnão inclui esse parâmetro de tipo porque é desconhecido ou irrelevante - basta usar T(ou em Java T<?>).

Outras informações:

Tipos universais / abstratos e tipos existenciais são uma dualidade de perspectiva entre o consumidor / cliente de um objeto / função e o produtor / implementação dele. Quando um lado vê um tipo universal, o outro vê um tipo existencial.

Em Java, você pode definir uma classe genérica:

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();
  • Da perspectiva de um cliente de MyClass, Té universal porque você pode substituir qualquer tipo Tao usar essa classe e deve conhecer o tipo real de T sempre que usar uma instância deMyClass
  • Da perspectiva dos métodos de instância em MyClasssi, Té existencial porque não sabe o tipo real deT
  • Em Java, ?representa o tipo existencial - portanto, quando você está dentro da classe, Té basicamente ?. Se você deseja manipular uma instância de MyClasscom Texistencial, pode declarar MyClass<?>como no secretMessage()exemplo acima.

Às vezes, tipos existentes são usados ​​para ocultar os detalhes de implementação de algo, conforme discutido em outro lugar. Uma versão Java disso pode se parecer com:

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);

É um pouco complicado capturar isso corretamente, porque estou fingindo estar em algum tipo de linguagem de programação funcional, que Java não é. Mas o ponto aqui é que você está capturando algum tipo de estado mais uma lista de funções que operam nesse estado e você não conhece o tipo real da parte do estado, mas as funções funcionam desde que já foram correspondidas com esse tipo .

Agora, em Java, todos os tipos não primitivos não finais são parcialmente existenciais. Isso pode parecer estranho, mas como uma variável declarada como Objectpotencialmente poderia ser uma subclasse Object, você não pode declarar o tipo específico, apenas "esse tipo ou subclasse". E assim, os objetos são representados como um pouco de estado, além de uma lista de funções que operam nesse estado - exatamente qual função chamar é determinada em tempo de execução pela pesquisa. É muito parecido com o uso de tipos existenciais acima, onde você tem uma parte do estado existencial e uma função que opera nesse estado.

Nas linguagens de programação de tipo estatístico, sem subtipagem e conversão, os tipos existenciais permitem gerenciar listas de objetos de tipos diferentes. Uma lista de T<Int>não pode conter a T<Long>. No entanto, uma lista de T<?>pode conter qualquer variação de T, permitindo colocar muitos tipos diferentes de dados na lista e convertê-los em int (ou executar quaisquer operações fornecidas na estrutura de dados) sob demanda.

Pode-se praticamente sempre converter um registro com um tipo existencial em um registro sem usar fechamentos. Um fechamento também é digitado existencialmente, pois as variáveis ​​livres sobre as quais ele é fechado estão ocultas do chamador. Assim, uma linguagem que suporta fechamentos, mas não tipos existenciais, pode permitir fechamentos que compartilham o mesmo estado oculto que você colocaria na parte existencial de um objeto.

Dobes Vandermeer
fonte
11

Um tipo existencial é um tipo opaco.

Pense em um identificador de arquivo no Unix. Você sabe que seu tipo é int, para poder falsificá-lo facilmente. Você pode, por exemplo, tentar ler do identificador 43. Se acontecer que o programa tenha um arquivo aberto com esse identificador específico, você o lerá. Seu código não precisa ser malicioso, apenas desleixado (por exemplo, o identificador pode ser uma variável não inicializada).

Um tipo existencial está oculto no seu programa. Se fopenum tipo existencial retornasse, tudo o que você poderia fazer seria usá-lo com algumas funções de biblioteca que aceitassem esse tipo existencial. Por exemplo, o seguinte pseudo-código seria compilado:

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

A interface "read" é ​​declarada como:

Existe um tipo T tal que:

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

A variável exfile não é char*um arquivo int, nem um , nem um struct - nada que você possa expressar no sistema de tipos. Você não pode declarar uma variável cujo tipo é desconhecido e não pode converter, por exemplo, um ponteiro para esse tipo desconhecido. O idioma não vai deixar você.

Bartosz Milewski
fonte
9
Isso não vai funcionar. Se a assinatura de readfor ∃T.read(T file, ...), não há nada que você possa transmitir como o primeiro parâmetro. O que iria trabalhar é ter fopendevolver o identificador de arquivo e uma função de leitura escopo pelo mesmo existencial :∃T.(T, read(T file, ...))
Kannan Goundan
2
Parece estar falando apenas de ADTs.
precisa saber é o seguinte
7

Parece que estou chegando um pouco atrasado, mas, de qualquer forma, este documento adiciona outra visão do que são tipos existenciais, embora não especificamente agnósticos, deve ser bastante mais fácil entender os tipos existenciais: http: //www.cs.uu .nl / groups / ST / Projetos / ehc / ehc-book.pdf (capítulo 8)

A diferença entre um tipo quantificado universal e existencialmente pode ser caracterizada pela seguinte observação:

  • O uso de um valor com um tipo quantificado ∀ determina o tipo a ser escolhido para a instanciação da variável do tipo quantificado. Por exemplo, o chamador da função de identidade “id :: ∀aa → a” ​​determina o tipo a ser escolhido para a variável de tipo a para esta aplicação específica de id. Para a aplicação de função "id 3", este tipo é igual a Int.

  • A criação de um valor com um tipo quantificado determin determina e oculta o tipo da variável do tipo quantificado. Por exemplo, um criador de um “∃a. (A, a → Int)” pode ter construído um valor desse tipo a partir de “(3, λx → x)”; outro criador construiu um valor com o mesmo tipo a partir de “('x', λx → ord x)”. Do ponto de vista do usuário, ambos os valores têm o mesmo tipo e, portanto, são intercambiáveis. O valor tem um tipo específico escolhido para a variável de tipo a, mas não sabemos qual tipo, portanto, essas informações não podem mais ser exploradas. Esta informação de tipo específico de valor foi 'esquecida'; só sabemos que existe.

themarketka
fonte
1
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada.
sheilak
1
@sheilak: atualizado a resposta, obrigado pela sugestão
themarketka
5

Existe um tipo universal para todos os valores do (s) parâmetro (s) do tipo. Um tipo existencial existe apenas para valores do (s) parâmetro (s) do tipo que atendem às restrições do tipo existencial.

Por exemplo, no Scala, uma maneira de expressar um tipo existencial é um tipo abstrato que é restrito a alguns limites superiores ou inferiores.

trait Existential {
  type Parameter <: Interface
}

Equivalentemente, um tipo universal restrito é um tipo existencial, como no exemplo a seguir.

trait Existential[Parameter <: Interface]

Qualquer site de uso pode empregar o Interfaceporque qualquer subtipo instável de Existentialdeve definir o type Parameterque deve ser implementado Interface.

Um caso degenerado de um tipo existencial em Scala é um tipo abstrato que nunca é referido e, portanto, não precisa ser definido por nenhum subtipo. Isso efetivamente possui uma notação abreviada List[_] no Scala e List<?>no Java.

Minha resposta foi inspirada na proposta de Martin Odersky de unificar tipos abstratos e existenciais. O slide que acompanha auxilia a compreensão.

Shelby Moore III
fonte
1
Depois de ler alguns dos materiais acima, parece que você resumiu bem meu entendimento: os Tipos Universais ∀x.f(x), são opacos às suas funções de recebimento, enquanto os Tipos Existenciais ∃x.f(x), são limitados a ter certas propriedades. Normalmente, todos os parâmetros são Existenciais, pois sua função os manipulará diretamente; no entanto, parâmetros genéricos podem ter tipos que são universais, pois a função não os gerenciará além das operações universais básicas, como a obtenção de uma referência como em:∀x.∃array.copy(src:array[x] dst:array[x]){...}
George
Conforme descrito aqui, os membros do tipo stackoverflow.com/a/19413755/3195266 podem emular quantificação universal via tipo de identidade. E com certeza existe forSomepara quantificação existencial do parâmetro type.
Netsu 01/01/19
3

Pesquisas sobre tipos de dados abstratos e ocultação de informações trouxeram tipos existenciais para linguagens de programação. Tornar um tipo de dados abstrato oculta informações sobre esse tipo, para que um cliente desse tipo não possa abusar dele. Digamos que você tenha uma referência a um objeto ... alguns idiomas permitem que você faça essa referência a uma referência a bytes e faça o que quiser com esse pedaço de memória. Para garantir o comportamento de um programa, é útil para uma linguagem impor que você só atue na referência ao objeto por meio dos métodos fornecidos pelo designer do objeto. Você sabe que o tipo existe, mas nada mais.

Vejo:

Tipos abstratos têm tipo existencial, MITCHEL & PLOTKIN

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

ja.
fonte
1

Eu criei este diagrama. Não sei se é rigoroso. Mas se ajudar, fico feliz. insira a descrição da imagem aqui

v.oddou
fonte
-6

Pelo que entendi, é uma maneira matemática de descrever interfaces / classe abstrata.

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

Para C #, seria traduzido para um tipo abstrato genérico:

abstract class MyType<T>{
    private T a;

    public abstract int f(T x);
}

"Existencial" significa apenas que existe algum tipo que obedece às regras definidas aqui.

user35910
fonte