Teoria do tipo dependente e funções do tipo 'arbitrárias'
Minha primeira resposta a essa pergunta foi alta em conceitos e baixa em detalhes e refletida na subquestão "o que está acontecendo?"; esta resposta será a mesma, mas focada na subquestão, 'podemos obter funções de tipo arbitrárias?'.
Uma extensão das operações algébricas de soma e produto são os chamados "grandes operadores", que representam a soma e o produto de uma sequência (ou, mais geralmente, a soma e o produto de uma função sobre um domínio) geralmente escritos Σ
e Π
respectivamente. Veja Notação Sigma .
Então a soma
a₀ + a₁X + a₂X² + ...
pode ser escrito
Σ[i ∈ ℕ]aᵢXⁱ
onde a
está uma sequência de números reais, por exemplo. O produto seria representado da mesma forma com em Π
vez de Σ
.
Quando você olha à distância, esse tipo de expressão se parece muito com uma função 'arbitrária' X
; é claro que estamos limitados a séries expressáveis e suas funções analíticas associadas. Este é um candidato a uma representação em uma teoria de tipos? Definitivamente!
A classe das teorias de tipos que têm representações imediatas dessas expressões é a classe das teorias de tipos 'dependentes': teorias com tipos dependentes. Naturalmente, temos termos dependentes de termos e em idiomas como Haskell com funções de tipo e quantificação de tipos, termos e tipos dependendo de tipos. Em uma configuração dependente, também temos tipos, dependendo dos termos. Haskell não é uma linguagem de tipo dependente, embora muitos recursos de tipos dependentes possam ser simulados torturando um pouco a linguagem .
Curry-Howard e tipos dependentes
O 'isomorfismo de Curry-Howard' começou a vida como uma observação de que os termos e regras de julgamento de tipo do cálculo lambda de tipagem simples correspondem exatamente à dedução natural (conforme formulada por Gentzen) aplicada à lógica proposicional intuicionista, com os tipos substituindo as proposições , e termos substituindo as provas, apesar de as duas serem inventadas / descobertas independentemente. Desde então, tem sido uma enorme fonte de inspiração para os teóricos do tipo. Uma das coisas mais óbvias a considerar é se, e como, essa correspondência para a lógica proposicional pode ser estendida para predicar ou lógicas de ordem superior. As teorias de tipos dependentes surgiram inicialmente dessa avenida de exploração.
Para uma introdução ao isomorfismo de Curry-Howard para o cálculo lambda de tipo simples, veja aqui . Como exemplo, se queremos provar A ∧ B
, devemos provar A
e provar B
; uma prova combinada é simplesmente um par de provas: uma para cada conjunto.
Em dedução natural:
Γ ⊢ A Γ ⊢ B
Γ ⊢ A ∧ B
e no cálculo lambda de tipo simples:
Γ ⊢ a : A Γ ⊢ b : B
Γ ⊢ (a, b) : A × B
Existem correspondências semelhantes para ∨
e tipos de soma, →
e tipos de função, e as várias regras de eliminação.
Uma proposição improvável (intuitionisticamente falsa) corresponde a um tipo desabitado.
Com a analogia dos tipos como proposições lógicas em mente, podemos começar a considerar como modelar predicados no mundo dos tipos. Há muitas maneiras pelas quais isso foi formalizado (consulte esta introdução à Teoria dos Tipos Intuicionistas de Martin-Löf para obter um padrão amplamente utilizado), mas a abordagem abstrata geralmente observa que um predicado é como uma proposição com variáveis de termo livre ou, alternativamente, uma função que leva termos a proposições. Se permitirmos que expressões de tipo contenham termos, um tratamento no estilo de cálculo lambda se apresentará imediatamente como uma possibilidade!
Considerando apenas provas construtivas, do que constitui uma prova ∀x ∈ X.P(x)
? Podemos pensar nisso como uma função de prova, levando termos ( x
) a provas de suas proposições correspondentes ( P(x)
). Assim, os membros (provas) do tipo (proposição) ∀x : X.P(x)
são funções dependentes '', o que para cada x
em X
dar um termo do tipo P(x)
.
Que tal ∃x ∈ X.P(x)
? Precisamos de qualquer membro X
, x
juntamente com uma prova de P(x)
. Portanto, membros (provas) do tipo (proposição) ∃x : X.P(x)
são 'pares dependentes': um termo distinto x
em X
, juntamente com um termo do tipo P(x)
.
Notação: vou usar
∀x ∈ X...
para declarações reais sobre os membros da classe X
e
∀x : X...
para expressões de tipo correspondentes à quantificação universal sobre o tipo X
. Da mesma forma para ∃
.
Considerações combinatórias: produtos e somas
Assim como a correspondência de Curry-Howard de tipos com proposições, temos a correspondência combinatória de tipos algébricos com números e funções, que é o ponto principal desta questão. Felizmente, isso pode ser estendido aos tipos dependentes descritos acima!
Vou usar a notação de módulo
|A|
representar o 'tamanho' de um tipo A
, tornar explícita a correspondência descrita na pergunta, entre tipos e números. Note que este é um conceito fora da teoria; Não afirmo que exista um operador desse tipo no idioma.
Vamos contar os possíveis membros (totalmente reduzidos, canônicos) do tipo
∀x : X.P(x)
que é o tipo de funções dependentes que levam termos x
de tipo X
a termos de tipo P(x)
. Cada uma dessas funções deve ter uma saída para cada termo de X
, e essa saída deve ser de um tipo específico. Para cada x
no X
, então, isso dá |P(x)|
'escolhas' de saída.
O punchline é
|∀x : X.P(x)| = Π[x : X]|P(x)|
o que, obviamente, não faz muito sentido se X
for IO ()
, mas é aplicável a tipos algébricos.
Da mesma forma, um termo do tipo
∃x : X.P(x)
é o tipo de pares (x, p)
com p : P(x)
, portanto, dado que qualquer um x
em X
podemos construir um par apropriado com qualquer membro de P(x)
, dando |P(x)|
'escolhas'.
Conseqüentemente,
|∃x : X.P(x)| = Σ[x : X]|P(x)|
com as mesmas ressalvas.
Isso justifica a notação comum para tipos dependentes em teorias usando os símbolos Π
e Σ
, de fato, muitas teorias obscurecem a distinção entre 'para todos' e 'produto' e entre 'existe' e 'soma', devido às correspondências mencionadas acima.
Estamos chegando perto!
Vetores: representando tuplas dependentes
Podemos agora codificar expressões numéricas como
Σ[n ∈ ℕ]Xⁿ
como expressões de tipo?
Nem tanto. Embora possamos considerar informalmente o significado de expressões como Xⁿ
em Haskell, onde X
é um tipo e n
um número natural, é um abuso de notação; esta é uma expressão de tipo que contém um número: distintamente não é uma expressão válida.
Por outro lado, com tipos dependentes na imagem, tipos contendo números é precisamente o ponto; de fato, tuplas dependentes ou 'vetores' são um exemplo muito citado de como os tipos dependentes podem fornecer segurança pragmática no nível de tipo para operações como o acesso à lista . Um vetor é apenas uma lista, juntamente com informações em nível de tipo sobre seu tamanho: exatamente o que buscamos para expressões de tipo Xⁿ
.
Durante a duração desta resposta, deixe
Vec X n
seja o tipo de comprimento de n
vetores de X
valores de tipo.
Tecnicamente, n
aqui está, em vez de um número natural real , uma representação no sistema de um número natural. Podemos representar números naturais ( Nat
) no estilo Peano como zero ( 0
) ou o sucessor ( S
) de outro número natural, e para n ∈ ℕ
eu escrever, ˻n˼
quero dizer o termo em Nat
que representa n
. Por exemplo, ˻3˼
é S (S (S 0))
.
Então nós temos
|Vec X ˻n˼| = |X|ⁿ
para qualquer n ∈ ℕ
.
Tipos Nat: promovendo ℕ termos para tipos
Agora podemos codificar expressões como
Σ[n ∈ ℕ]Xⁿ
como tipos. Esta expressão em particular daria origem a um tipo que é obviamente isomórfico ao tipo de lista de X
, conforme identificado na pergunta. (Não apenas isso, mas do ponto de vista teórico da categoria, a função de tipo - que é um functor - levar X
para o tipo acima é naturalmente isomórfica para o functor List.)
Uma peça final do quebra-cabeça para funções 'arbitrárias' é como codificar, por
f : ℕ → ℕ
expressões como
Σ[n ∈ ℕ]f(n)Xⁿ
para que possamos aplicar coeficientes arbitrários a uma série de potências.
Já entendemos a correspondência de tipos algébricos com números, permitindo mapear de tipos para números e funções de tipo para funções numéricas. Nós também podemos ir para o outro lado! - tomando um número natural, existe obviamente um tipo algébrico definível com tantos membros do termo, independentemente de termos ou não tipos dependentes. Podemos facilmente provar isso fora da teoria dos tipos por indução. O que precisamos é de uma maneira de mapear de números naturais para tipos, dentro do sistema.
Uma percepção agradável é que, uma vez que temos tipos dependentes, a prova por indução e a construção por recursão tornam-se intimamente semelhantes - na verdade, são a mesma coisa em muitas teorias. Como podemos provar por indução que existem tipos que atendem às nossas necessidades, não deveríamos ser capazes de construí-los?
Existem várias maneiras de representar tipos no nível do termo. Usarei aqui uma notação Haskellish imaginária *
para o universo de tipos, geralmente considerado um tipo em um ambiente dependente. 1
Da mesma forma, também existem pelo menos tantas maneiras de ℕ
anotar 'eliminação' quanto as teorias de tipos dependentes. Usarei uma notação de correspondência de padrões haskellish.
Precisamos de um mapeamento, α
de Nat
para *
, com a propriedade
∀n ∈ ℕ.|α ˻n˼| = n.
A seguinte pseudo-definição é suficiente.
data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe
α : Nat -> *
α 0 = Zero
α (S n) = Successor (α n)
Então, vemos que a ação de α
espelha o comportamento do sucessor S
, tornando-o um tipo de homomorfismo. Successor
é uma função de tipo que 'adiciona um' ao número de membros de um tipo; isto é, |Successor a| = 1 + |a|
para qualquer a
um com um tamanho definido.
Por exemplo α ˻4˼
(que é α (S (S (S (S 0))))
), é
Successor (Successor (Successor (Successor Zero)))
e os termos deste tipo são
Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))
dando-nos exatamente quatro elementos: |α ˻4˼| = 4
.
Da mesma forma, para qualquer um n ∈ ℕ
, temos
|α ˻n˼| = n
como requerido.
- Muitas teorias exigem que os membros de
*
sejam meros representantes de tipos, e uma operação é fornecida como um mapeamento explícito dos termos do tipo *
para os tipos associados. Outras teorias permitem que os tipos literais sejam entidades de nível de termo.
Funções 'arbitrárias'?
Agora temos o aparato para expressar uma série de potências totalmente geral como um tipo!
As séries
Σ[n ∈ ℕ]f(n)Xⁿ
torna-se o tipo
∃n : Nat.α (˻f˼ n) × (Vec X n)
onde ˻f˼ : Nat → Nat
há alguma representação adequada no idioma da função f
. Podemos ver isso da seguinte maneira.
|∃n : Nat.α (˻f˼ n) × (Vec X n)|
= Σ[n : Nat]|α (˻f˼ n) × (Vec X n)| (property of ∃ types)
= Σ[n ∈ ℕ]|α (˻f˼ ˻n˼) × (Vec X ˻n˼)| (switching Nat for ℕ)
= Σ[n ∈ ℕ]|α ˻f(n)˼ × (Vec X ˻n˼)| (applying ˻f˼ to ˻n˼)
= Σ[n ∈ ℕ]|α ˻f(n)˼||Vec X ˻n˼| (splitting product)
= Σ[n ∈ ℕ]f(n)|X|ⁿ (properties of α and Vec)
Quão arbitrário é isso? Estamos limitados não apenas a coeficientes inteiros por esse método, mas a números naturais. Além disso, f
pode ser qualquer coisa, dada a linguagem Turing Complete com tipos dependentes, podemos representar qualquer função analítica com coeficientes de números naturais.
Não investiguei a interação disso com, por exemplo, o caso fornecido na questão List X ≅ 1/(1 - X)
ou que sentido esses 'tipos' negativos e não inteiros podem ter nesse contexto.
Esperamos que esta resposta explique até onde podemos ir com funções de tipo arbitrário.