Os tipos universais são um subtipo ou caso especial de tipos existenciais?


20

Gostaria de saber se um tipo universalmente quantificado : é um subtipo ou caso especial, de um tipo quantificado existencialmente com a mesma assinatura:T a = X : { a X , f : X { T , F } } T e T e = X : { a X , f : X { T , F } }Ta

Tuma=X:{umaX,f:X{T,F}}
Te
Te=X:{umaX,f:X{T,F}}

Eu diria "sim": se algo é verdadeiro "para todos os X" ( ), também deve ser verdadeiro "para alguns X" ( ). Ou seja, uma declaração com ' ' é simplesmente uma versão mais restrita da mesma declaração com ' ':X X , P ( X ) ?XX

X,P(X)?X,P(X).

Estou errado em algum lugar?

Antecedentes: Por que estou perguntando isso?

Estou estudando tipos existenciais para entender por que e como "Tipos abstratos de [dados] têm tipo existencial" . Não consigo entender bem esse conceito apenas com a teoria; Também preciso de exemplos concretos.

Infelizmente, é difícil encontrar bons exemplos de código, porque a maioria das linguagens de programação tem suporte limitado apenas para tipos existenciais. (Por exemplo, curingas de Haskellforall ou Java? .) Por outro lado, tipos quantificados universalmente são suportados por muitas linguagens recentes via "genéricos".

O que é pior, os genéricos parecem facilmente se misturar com os tipos existenciais , tornando ainda mais difícil diferenciar tipos existenciais de universais. Estou curioso para saber por que essa confusão ocorre com tanta facilidade. Uma resposta para essa pergunta pode explicar: se tipos universais são de fato apenas um caso especial de tipos existenciais, não é de admirar que tipos genéricos, como Java List<T>, possam ser interpretados de qualquer maneira.


1
Qual é a diferença entre universal e existencial?

Matematicamente falando, você está certo: se forall x. P(x)sim exists x. P(x). Se os sistemas de tipos levam isso em consideração ao verificar os tipos ... Não faço ideia. +1 para uma pergunta interessante.

1
@deinan: Se P (x) não se aplica a nenhum x , certamente ∀xP (x) não se aplica. O que você provavelmente quis dizer é que quando não há x , ou seja, ∀x∈XP (x) não implica ∃x∈XP (x) se X = ∅ .

1
... E observe que, se elas forem reescritas sem a notação de conjunto, parecerão diferentes: ∀xx∈X⇒P (x) vs. ∃xx∈X & P (x) e que ∃xx∈X⇒P (x) será trivialmente satisfeita por qualquer x não de X .

1
Pergunta legal. Em Haskell, certamente é verdade que um valor do tipo (forall b. Show b => b) pode ser passado para uma função que recebe a (forall b. B), mas não vice-versa, implicando a substituibilidade que você esperaria de um relacionamento de subtipagem. Mas é claro que quando você fala sobre os tipos que você deve mencionar o tipo de sistema que você está olhando, especialmente se você tiver um tipo de álgebra formal em mente para a sua semântica ...

Respostas:


10

Primeiro, do ponto de vista matemático, não implica . A implicação vale se e somente se não estiver vazio. No entanto, nas linguagens de programação, é incomum lidar com tipos vazios (embora isso aconteça).x:T,P(x)x:T,P(x)T

Ainda do ponto de vista matemático, mesmo quando , os dois não são os mesmos. Se você der aos tipos uma semântica definida, será um superconjunto de , não o mesmo tipo. (Na verdade, não é um superconjunto; é quase isomórfico a um superconjunto.)(x:T,P(x))(x:T,P(x))TaTe

Vamos nos aproximar da teoria da linguagem de programação e ver o que esses tipos realmente significam. é um tipo universal: se você tiver um valor desse tipo, poderá construir para qualquer . Em particular, se você tem e ambos do tipo , você pode construir e . Em essência (e possivelmente em efeito, dependendo do idioma) é uma função dos tipos aos termos. Como tal, o tipo universal fornece parametrização de tipo : um valor trabalhando para todos os tipos. Os tipos universais estão no coração deTa=X.{a:X,f:Xbool}AA(M)M:XM1M2XA(M1)A(M2)Tapolimorfismo .

O tipo existencial é um animal bem diferente. Dado um valor do tipo , existe apenas um termo tal que e, para este termo . O tipo existencial fornece uma maneira de ocultar a natureza de ; está dizendo: "Existe um tipo! Mas eu não vou te dizer qual! ”. Como tal, o tipo existencial fornece abstração de tipo : um valor oculto específico. Tipos existentes estão no coração dos sistemas de módulos .B T e N : X π 1 ( B ) = N pi 2 ( B ) = { um : N , f : N b o o l } NTe=X.{a:X,f:Xbool}BTeN:Xπ1(B)=Nπ2(B)={uma:N,f:Nbooeu}N

Não se deixe enganar por Haskell forall: apesar do nome, é uma forma de quantificador existencial.

Como pano de fundo, recomendo fortemente Tipos e Linguagens de Programação (os capítulos 23 e 24 discutem tipos universais e tipos existenciais, respectivamente). Ele fornecerá informações úteis para entender artigos de pesquisa.


1
Uma queixa menor e um tanto tardia - a de Haskell forallé de fato um quantificador universal no contexto original da quantificação implícita que torna explícita, a saber: a visualização de tipos polimórficos "de fora" para definições de nível superior. No "interior" dessa definição, ao manipular argumentos, os tipos polimórficos são efetivamente existenciais; cada variável de tipo está vinculada a algum tipo, mas não sabemos (e não podemos) saber qual é esse tipo. Que eu saiba, nenhuma implementação Haskell suporta tipos existenciais verdadeiros (brutos, de nível superior), e não está claro para mim qual objetivo isso serviria.
CA McCann

1
Os tipos existenciais suportados pelo GHC são tipos que (direta ou indiretamente) possuem quantificadores universais que, vistos de "fora", ocorrem em posição contravariante. Isso usa aproximadamente a mesma dualidade da negação lógica; portanto, para ter esses tipos existenciais no nível superior, eles devem ser duplamente contravariantes, usando uma codificação do tipo CPS (essa é a equivalência que Uday Reddy dá). Observe que quantificadores existenciais em intuicionistas apresentam inconvenientes semelhantes por razões semelhantes.
CA McCann

5

Sua intuição de que devem ser incorporados em geralmente está correta. Só falha se toda a coleção de tipos estiver vazia, o que raramente é o caso.X.P(X)X.P(X)

No entanto, o exemplo específico que você forneceu não é bom. Um valor desse tipo deve ser capaz de produzir um elemento de cada tipo (para o primeiro componente do par). Se você está pensando em tipos como conjuntos, isso não é possível. Então, esse tipo está vazio. Ele seria incluído em todos os tipos, em particular o tipo existencial , mas isso não é muito interessante. De qualquer forma, como exercício teórico, eis um termo que constrói uma coisa dessas:X X .X.(X×(XBooeu))XX.(X×(XBooeu))

 f (p: \forall X. (X * (X -> Bool))) = PACK X = Bool WITH p[Bool]

O artigo que você menciona para tipos existenciais é um pouco teórico. Um artigo mais tutorial é o artigo de Cardelli e Wegner: Sobre a compreensão de tipos, abstração de dados e polimorfismo . Os livros de texto mais avançados sobre linguagens de programação também teriam alguma discussão sobre tipos existenciais. Um bom livro para procurar seria o livro Foundations of Programming Languages, de Mitchell .

Você está certo que a maioria das linguagens de programação não possui tipos existenciais explicitamente. No entanto, muitos têm tipos abstratos (ou por outro nome, como "pacotes" ou "módulos"). Portanto, eles são capazes de expressar valores de tipos existenciais, mesmo que não tratem esses valores como entidades de primeira classe.

Mais importante ainda, é equivalente a . Assim, qualquer linguagem que trata funções polimórficas (de tipos) como entidades de primeira classe também tem a capacidade de tratar tipos abstratos como entidades de primeira classe. Y .X.P(X)Y.(X.P(X)Y)Y

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.