A expressão 'algébrica' para tipos de dados algébricos parece muito sugestiva para alguém com experiência em matemática. Deixe-me tentar explicar o que quero dizer.
Tendo definido os tipos básicos
- produtos
•
- União
+
- Singleton
X
- Unidade
1
e usando a abreviação X²
de X•X
e 2X
para X+X
et cetera, podemos definir expressões algébricas para, por exemplo, listas vinculadas
data List a = Nil | Cons a (List a)
↔ L = 1 + X • L
e árvores binárias:
data Tree a = Nil | Branch a (Tree a) (Tree a)
↔ T = 1 + X • T²
Agora, meu primeiro instinto como matemático é enlouquecer com essas expressões e tentar resolver por L
e T
. Eu poderia fazer isso através de substituições repetidas, mas parece muito mais fácil abusar da notação horrivelmente e fingir que posso reorganizá-la à vontade. Por exemplo, para uma lista vinculada:
L = 1 + X • L
(1 - X) • L = 1
L = 1 / (1 - X) = 1 + X + X² + X³ + ...
onde eu usei a expansão de séries de potência de 1 / (1 - X)
uma maneira totalmente injustificada para obter um resultado interessante, ou seja, que um L
tipo é Nil
ou contém 1 elemento ou contém 2 elementos ou 3 etc.
Fica mais interessante se o fizermos para árvores binárias:
T = 1 + X • T²
X • T² - T + 1 = 0
T = (1 - √(1 - 4 • X)) / (2 • X)
T = 1 + X + 2 • X² + 5 • X³ + 14 • X⁴ + ...
novamente, usando a expansão da série Power (feita com Wolfram Alpha ). Isso expressa o fato não óbvio (para mim) de que existe apenas uma árvore binária com 1 elemento, 2 árvores binárias com dois elementos (o segundo elemento pode estar no ramo esquerdo ou direito), 5 árvores binárias com três elementos etc. .
Então, minha pergunta é - o que estou fazendo aqui? Essas operações parecem injustificadas (qual é exatamente a raiz quadrada de um tipo de dados algébrico?), Mas levam a resultados sensatos. o quociente de dois tipos de dados algébricos tem algum significado na ciência da computação ou é apenas truque notacional?
E, talvez mais interessante, é possível estender essas idéias? Existe uma teoria da álgebra de tipos que permita, por exemplo, funções arbitrárias em tipos, ou os tipos requerem uma representação de séries de poder? Se você pode definir uma classe de funções, a composição de funções tem algum significado?
fonte
Branch x (Branch y Nil Nil) Nil
ou pareceBranch x Nil (Branch y Nil Nil)
.undefined
,throw
etc. Devemos usá-lo.Respostas:
Isenção de responsabilidade: Muito disso realmente não funciona muito bem quando você responde por so, então eu vou desconsiderar descaradamente isso por uma questão de simplicidade.
Alguns pontos iniciais:
Observe que "união" provavelmente não é o melhor termo para A + B aqui - isso é especificamente uma união disjunta dos dois tipos, porque os dois lados são distintos mesmo que seus tipos sejam os mesmos. Pelo que vale, o termo mais comum é simplesmente "tipo de soma".
Tipos Singleton são, efetivamente, todos os tipos de unidades. Eles se comportam de forma idêntica sob manipulações algébricas e, mais importante, a quantidade de informações presentes ainda é preservada.
Você provavelmente quer um tipo zero também. Haskell fornece isso como
Void
. Não há valores cujo tipo seja zero, assim como há um valor cujo tipo é um.Ainda falta uma operação importante aqui, mas voltarei a isso daqui a pouco.
Como você já deve ter notado, Haskell tende a emprestar conceitos da Teoria das categorias, e todas as opções acima têm uma interpretação muito direta como essa:
Dados os objetos A e B em Hask , seu produto A × B é o tipo único (até o isomorfismo) que permite duas projeções fst : A × B → A e snd : A × B → B, onde é dado qualquer tipo C e funções f : C → A, g : C → B, você pode definir o emparelhamento f &&& g : C → A × B de modo que fst ∘ (f &&& g) = f e da mesma forma para g . A parametridade garante as propriedades universais automaticamente e minha escolha menos que sutil de nomes deve lhe dar a idéia. O
(&&&)
operador é definidoControl.Arrow
, a propósito.A dupla acima é o coproduto A + B com injeções inl : A → A + B e inr : B → A + B, onde é dado qualquer tipo C e funções f : A → C, g : B → C, você pode definir o copairing f ||| g : A + B → C tal que as equivalências óbvias se mantêm. Novamente, a parametridade garante a maioria das partes complicadas automaticamente. Neste caso, as injecções de padrão são simplesmente
Left
eRight
e o copairing representa a funçãoeither
.Muitas das propriedades dos tipos de produto e soma podem ser derivadas do exposto acima. Observe que qualquer tipo singleton é um objeto terminal do Hask e qualquer tipo vazio é um objeto inicial.
Voltando à operação ausente mencionada acima, em uma categoria fechada cartesiana, você possui objetos exponenciais que correspondem às setas da categoria. Nossas setas são funções, nossos objetos são tipos com tipos
*
, e o tipoA -> B
realmente se comporta como B A no contexto da manipulação algébrica de tipos. Se não for óbvio por que isso deve ocorrer, considere o tipoBool -> A
. Com apenas duas entradas possíveis, uma função desse tipo é isomórfica para dois valores do tipoA
, ie(A, A)
. PoisMaybe Bool -> A
temos três entradas possíveis, e assim por diante. Observe também que, se reformularmos a definição de copairing acima para usar notação algébrica, obtemos a identidade C A × C B = CA + B .Quanto ao motivo pelo qual tudo isso faz sentido - e, em particular, por que o uso da expansão da série de potência é justificado - observe que grande parte dos itens acima se refere aos "habitantes" de um tipo (ou seja, valores distintos tendo esse tipo) em ordem para demonstrar o comportamento algébrico. Para tornar essa perspectiva explícita:
O tipo de produto
(A, B)
representa um valor para cada um delesA
eB
obtido independentemente. Portanto, para qualquer valor fixoa :: A
, há um valor de tipo(A, B)
para cada habitante deB
. É claro que esse é o produto cartesiano, e o número de habitantes do tipo de produto é o produto do número de habitantes dos fatores.O tipo de soma
Either A B
representa um valor de umA
ouB
, com os ramos esquerdo e direito distintos. Como mencionado anteriormente, esta é uma união disjunta, e o número de habitantes do tipo soma é a soma do número de habitantes dos somandos.O tipo exponencial
B -> A
representa um mapeamento de valores do tipoB
para valores do tipoA
. Para qualquer argumento fixob :: B
, qualquer valor deA
pode ser atribuído a ele; um valor do tipoB -> A
seleciona um desses mapeamentos para cada entrada, o que equivale a um produto com tantas cópiasA
quantoB
habitantes, daí a exponenciação.Embora, no início, seja tentador tratar tipos como conjuntos, isso não funciona muito bem nesse contexto - temos união desunida em vez da união padrão de conjuntos, não há interpretação óbvia de interseção ou muitas outras operações de conjunto, e nós geralmente não se preocupam com a associação de conjuntos (deixando isso para o verificador de tipos).
Por outro lado, as construções acima passam muito tempo conversando sobre a contagem de habitantes, e enumerar os possíveis valores de um tipo é um conceito útil aqui. Isso rapidamente nos leva a combinações enumerativas e, se você consultar o artigo vinculado da Wikipedia, descobrirá que uma das primeiras coisas a fazer é definir "pares" e "uniões" exatamente no mesmo sentido que os tipos de produto e soma por meio de gerando funções , então faz o mesmo para "sequências" idênticas às listas de Haskell, usando exatamente a mesma técnica que você fez.
Edit: Ah, e aqui está um bônus rápido que eu acho que demonstra o ponto de forma impressionante. Você mencionou em um comentário que, para um tipo de árvore,
T = 1 + T^2
você pode derivar a identidadeT^6 = 1
, o que está claramente errado. No entanto,T^7 = T
é válido, e uma bijeção entre árvores e sete tuplas de árvores pode ser construída diretamente, cf. "Sete Árvores em Uma", de Andreas Blass .Editar × 2: Sobre o assunto da construção "derivada de um tipo" mencionada em outras respostas, você também pode apreciar este artigo do mesmo autor, que se baseia mais na idéia, incluindo noções de divisão e outros enfeites interessantes.
fonte
Árvores binárias são definidas pela equação
T=1+XT^2
na semirrada de tipos. Por construção,T=(1-sqrt(1-4X))/(2X)
é definido pela mesma equação na semiring de números complexos. Portanto, dado que estamos resolvendo a mesma equação na mesma classe de estrutura algébrica, na verdade não deve surpreender que vejamos algumas semelhanças.O problema é que, quando raciocinamos sobre polinômios na semirrigação de números complexos, normalmente usamos o fato de que os números complexos formam um anel ou mesmo um campo, então nos encontramos usando operações como subtração que não se aplicam a semirreclamações. Mas muitas vezes podemos eliminar subtrações de nossos argumentos se tivermos uma regra que nos permita cancelar de ambos os lados de uma equação. Esse é o tipo de coisa comprovada por Fiore e Leinster, mostrando que muitos argumentos sobre anéis podem ser transferidos para semirriscos.
Isso significa que muito do seu conhecimento matemático sobre anéis pode ser transferido com segurança para tipos. Como resultado, alguns argumentos que envolvem números complexos ou séries de potências (no círculo das séries formais de potência) podem ser transferidos para tipos de maneira completamente rigorosa.
No entanto, há mais na história do que isso. Uma coisa é provar que dois tipos são iguais (digamos), mostrando duas séries de potência iguais. Mas você também pode deduzir informações sobre tipos inspecionando os termos da série de potências. Não tenho certeza de qual deveria ser a declaração formal aqui. (Eu recomendo o artigo de Brent Yorgey sobre espécies combinatórias para alguns trabalhos que estão intimamente relacionados, mas espécies não são iguais aos tipos.)
O que acho absolutamente surpreendente é que o que você descobriu pode ser estendido ao cálculo. Teoremas sobre cálculo podem ser transferidos para a semirrada de tipos. De fato, mesmo argumentos sobre diferenças finitas podem ser transferidos e você descobre que os teoremas clássicos da análise numérica têm interpretações na teoria dos tipos.
Diverta-se!
fonte
P = X^2
, tem derivadadP = X + X
, assimEither
como o contexto de um buraco do par. Isso é bem legal. Poderíamos 'integrar'Either
para obter um par também. Mas se tentarmos 'integrar'Maybe
(com o tipoM = 1 + X
), precisamos ter o\int M = X + X^2 / 2
que não faz sentido (o que é meio tipo?) Isso significa que esseMaybe
não é o contexto de um buraco de outro tipo?(A,A)
com um orifício éA
um pouco e um pouco informando de que lado o orifício está. UmA
sozinho não tem um buraco distinto para preencher, e é por isso que você não pode "integrá-lo". O tipo de informação que falta neste caso é, obviamente2
,.X^2/2
blog.sigfpe.com/2007/09/type-of-distinct-pairs.htmlParece que tudo o que você está fazendo é expandir a relação de recorrência.
E como as regras para as operações nos tipos funcionam como as regras para operações aritméticas, você pode usar meios algébricos para ajudá-lo a descobrir como expandir a relação de recorrência (já que isso não é óbvio).
fonte
X
é um elemento dos números reais para uma afirmação verdadeira sobre tipos e, além disso, onde faz a correspondência (coeficiente don
termo do grau) <=> (número de tipos que contêmn
elementos)?T = 1 + T^2
) eu posso derivarT^6 = 1
(ou seja, soluçõesx^2 - x + 1 = 0
são a sexta raiz da unidade), mas claramente não é verdade que um tipo de produto que consiste em seis árvores binárias é equivalente à unidade()
.T^7
eT
. cf. arxiv.org/abs/math/9405205L = 1 + X * L
, teve ser melhor a mesma que você começa quando você série expandir, por consistência. Caso contrário, você poderá executar o resultado para trás para obter algo falso sobre os reais.Não tenho uma resposta completa, mas essas manipulações tendem a "apenas funcionar". Um artigo relevante pode ser Objetos de categorias como números complexos de Fiore e Leinster - me deparei com esse enquanto lia o blog de sigfpe sobre um assunto relacionado ; o restante desse blog é uma mina de ouro para idéias semelhantes e vale a pena conferir!
Você também pode diferenciar tipos de dados, a propósito - que fornecerão o Zipper apropriado para o tipo de dados!
fonte
A Álgebra de Processos de Comunicação (ACP) lida com tipos semelhantes de expressões para processos. Oferece adição e multiplicação como operadores para escolha e sequência, com elementos neutros associados. Com base nisso, existem operadores para outras construções, como paralelismo e interrupção. Veja http://en.wikipedia.org/wiki/Algebra_of_Communicating_Processes . Há também um artigo on-line chamado "Uma Breve História da Álgebra de Processos".
Estou trabalhando na extensão de linguagens de programação com o ACP. Em abril passado, apresentei um trabalho de pesquisa no Scala Days 2012, disponível em http://code.google.com/p/subscript/
Na conferência, demonstrei um depurador executando uma especificação recursiva paralela de uma bolsa:
Bag = A; (Bag & a)
onde A e um representam ações de entrada e saída; o ponto e vírgula e o e comercial representam sequência e paralelismo. Veja o vídeo em SkillsMatter, acessível a partir do link anterior.
Uma especificação de bolsa mais comparável à
L = 1 + X • L
seria
B = 1 + X&B
ACP define paralelismo em termos de escolha e sequência usando axiomas; veja o artigo da Wikipedia. Gostaria de saber qual seria a analogia da bolsa.
L = 1 / (1-X)
A programação no estilo ACP é útil para analisadores de texto e controladores GUI. Especificações como
searchCommand = clicado (botão de pesquisa) + tecla (Enter)
cancelCommand = clicado (cancelButton) + tecla (Escape)
pode ser anotado de forma mais concisa, tornando implícitos os dois refinamentos "clicados" e "chave" (como o que Scala permite com funções). Por isso, podemos escrever:
searchCommand = searchButton + Enter
cancelCommand = cancelButton + Escape
O lado direito agora contém operandos que são dados, e não processos. Nesse nível, não é necessário saber quais refinamentos implícitos transformarão esses operandos em processos; eles não necessariamente se refinariam em ações de entrada; as ações de saída também se aplicariam, por exemplo, na especificação de um robô de teste.
Os processos obtêm os dados dessa maneira como complementares; assim, cunho o termo "álgebra de itens".
fonte
Séries de cálculo e Maclaurin com tipos
Aqui está outra pequena adição - uma visão combinatória sobre por que os coeficientes de uma expansão em série devem 'funcionar', focando especialmente as séries que podem ser derivadas usando a abordagem de Taylor-Maclaurin do cálculo. Nota: o exemplo de expansão de série que você fornece do tipo de lista manipulada é uma série de Maclaurin.
Como outras respostas e comentários lidam com o comportamento das expressões algébricas do tipo (somas, produtos e expoentes), essa resposta elimina esse detalhe e se concentra no tipo 'cálculo'.
Você pode notar vírgulas invertidas fazendo algum trabalho pesado nesta resposta. Existem dois motivos:
Definição da série Maclaurin
A série Maclaurin de uma função
f : ℝ → ℝ
é definida comoonde
f⁽ⁿ⁾
significa an
th derivada def
.Para entender a série Maclaurin como interpretada com tipos, precisamos entender como podemos interpretar três coisas em um contexto de tipo:
0
(1/n!)
e acontece que esses conceitos da análise têm contrapartes adequadas no mundo dos tipos.
O que quero dizer com "contraparte adequada"? Deveria ter o sabor de um isomorfismo - se podemos preservar a verdade em ambas as direções, fatos deriváveis em um contexto podem ser transferidos para o outro.
Cálculo com tipos
Então, o que a derivada de uma expressão de tipo significa? Acontece que, para uma classe grande e bem-comportada ('diferenciável') de expressões e functores de tipo, há uma operação natural que se comporta de maneira semelhante o suficiente para ser uma interpretação adequada!
Para estragar o argumento, a operação análoga à diferenciação é a de criar "contextos de um buraco". Este é um excelente local para expandir ainda mais esse ponto específico, mas o conceito básico de um contexto de um buraco (
da/dx
) é que ele representa o resultado da extração de um único subitem de um tipo específico (x
) de um termo (do tipoa
), preservando todas as outras informações, incluindo as necessárias para determinar a localização original do subitem. Por exemplo, uma maneira de representar um contexto de um buraco para uma lista é com duas listas: uma para itens que vieram antes do extraído e outra para itens que vieram depois.A motivação para identificar esta operação com diferenciação vem das seguintes observações. Escrevemos
da/dx
para significar o tipo de contextos de um furo para o tipoa
com furo do tipox
.Aqui,
1
e0
representam tipos com exatamente um e exatamente zero habitantes, respectivamente,+
e×
representam os tipos de soma e produto, como de costume.f
eg
são usados para representar funções de tipo ou formadores de expressão de tipo e[f(x)/a]
significa a operação de substituirf(x)
todosa
os itens na expressão anterior.Isso pode ser escrito em um estilo sem ponto, escrevendo
f'
para significar a função derivada da função typef
, assim:o que pode ser preferível.
Nota: as igualidades podem ser rigorosas e exatas se definirmos derivadas usando classes de isomorfismo de tipos e functores.
Agora, notamos em particular que as regras em cálculo referentes às operações algébricas de adição, multiplicação e composição (geralmente chamadas de regras de soma, produto e cadeia) são refletidas exatamente pela operação de "fazer um buraco". Além disso, os casos básicos de 'fazer um buraco' em uma expressão constante ou o
x
próprio termo também se comportam como diferenciação; portanto, por indução, obtemos um comportamento semelhante à diferenciação para todas as expressões do tipo algébrico.Agora podemos interpretar diferenciação, o que significa a
n
"derivada" de uma expressão de tipodⁿe/dxⁿ
? É um tipo que representan
contextos -place: termos que, quando 'preenchidos' comn
termos do tipo,x
produzem ume
. Há outra observação importante relacionada a '(1/n!)
' vir mais tarde.A parte invariável de um tipo functor: aplicando uma função a 0
Já temos uma interpretação para
0
o mundo dos tipos: um tipo vazio sem membros. O que significa, do ponto de vista combinatório, aplicar uma função de tipo a ela? Em termos mais concretos, supondo quef
seja uma função de tipo, como éf(0)
? Bem, certamente não temos acesso a nada do tipo0
, portanto, quaisquer construçõesf(x)
que requeiram umx
estão indisponíveis. O que resta são aqueles termos acessíveis na ausência deles, que podemos chamar de parte 'invariável' ou 'constante' do tipo.Para um exemplo explícito,
Maybe
use o functor, que pode ser representado algebricamente comox ↦ 1 + x
. Quando aplicamos isso a0
, obtemos1 + 0
- é como1
: o único valor possível é oNone
valor. Para uma lista, da mesma forma, obtemos apenas o termo correspondente à lista vazia.Quando o devolvemos e interpretamos o tipo
f(0)
como um número, ele pode ser considerado a contagem de quantos termos do tipof(x)
(para qualquerx
) podem ser obtidos sem acesso a umx
: ou seja, o número de termos "vazios" .Juntando tudo: interpretação completa de uma série Maclaurin
Receio não poder pensar em uma interpretação direta apropriada
(1/n!)
como um tipo.Se considerarmos, no entanto, o tipo
f⁽ⁿ⁾(0)
de luz do exposto, vemos que ele pode ser interpretado como o tipo den
contextos -Coloque para um mandato do tipof(x)
que já não contêm umx
- isto é, quando nós 'integrar' elesn
vezes , o termo resultante tem exatamenten
x
s, nem mais, nem menos. Então, a interpretação do tipof⁽ⁿ⁾(0)
como um número (como nos coeficientes da série Maclaurin def
) é simplesmente uma contagem de quantosn
contextos vazios existem. Estamos quase lá!Mas onde
(1/n!)
acaba? Examinar o processo do tipo 'diferenciação' mostra que, quando aplicado várias vezes, preserva a 'ordem' na qual os subtermos são extraídos. Por exemplo, considere o termo(x₀, x₁)
do tipox × x
e a operação de "fazer um buraco" nele duas vezes. Temos as duas sequênciasmesmo que ambos venham do mesmo termo, porque existem
2! = 2
maneiras de obter dois elementos de dois, preservando a ordem. Em geral, existemn!
maneiras de extrairn
elementosn
. Portanto, para obter uma contagem do número de configurações de um tipo de functor que possuemn
elementos, precisamos contar o tipof⁽ⁿ⁾(0)
e dividir porn!
, exatamente como nos coeficientes da série Maclaurin.Então, dividir por
n!
acaba sendo interpretável simplesmente como ele mesmo.Considerações finais: definições "recursivas" e analiticidade
Primeiro, algumas observações:
Como temos a regra da cadeia, podemos usar diferenciação implícita , se formalizarmos derivadas de tipo como classes de isomorfismo. Mas a diferenciação implícita não requer nenhuma manobra alienígena, como subtração ou divisão! Portanto, podemos usá-lo para analisar definições de tipo recursivo. Para dar um exemplo de lista, temos
e então podemos avaliar
para obter o coeficiente da
X¹
série Maclaurin.Mas, como estamos confiantes de que essas expressões são realmente estritamente 'diferenciáveis', mesmo que implícitas, e como temos a correspondência com as funções ℝ → ℝ, onde derivadas são certamente únicas, podemos ter certeza de que, mesmo se obtivermos os valores usando ' ilegais ', o resultado é válido.
Agora, da mesma forma, para usar a segunda observação, devido à correspondência (é um homomorfismo?) Com funções ℝ → ℝ, sabemos que, desde que estejamos satisfeitos que uma função tenha uma série de Maclaurin, se pudermos encontrar alguma série em todos , os princípios descritos acima podem ser aplicados para torná-lo rigoroso.
Quanto à sua pergunta sobre composição de funções, suponho que a regra da cadeia forneça uma resposta parcial.
Não sei ao certo quantos ADTs ao estilo Haskell se aplicam, mas suspeito que sejam muitos, se não todos. Descobri uma prova realmente maravilhosa desse fato, mas essa margem é muito pequena para contê-lo ...
Agora, certamente, essa é apenas uma maneira de descobrir o que está acontecendo aqui e provavelmente existem muitas outras maneiras.
Resumo: TL; DR
0
nos fornece os termos "vazios" para esse functor.fonte
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
pode ser escrito
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 provarA
e provarB
; uma prova combinada é simplesmente um par de provas: uma para cada conjunto.Em dedução natural:
e no cálculo lambda de tipo simples:
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 cadax
emX
dar um termo do tipoP(x)
.Que tal
∃x ∈ X.P(x)
? Precisamos de qualquer membroX
,x
juntamente com uma prova deP(x)
. Portanto, membros (provas) do tipo (proposição)∃x : X.P(x)
são 'pares dependentes': um termo distintox
emX
, juntamente com um termo do tipoP(x)
.Notação: vou usar
para declarações reais sobre os membros da classe
X
epara 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
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
que é o tipo de funções dependentes que levam termos
x
de tipoX
a termos de tipoP(x)
. Cada uma dessas funções deve ter uma saída para cada termo deX
, e essa saída deve ser de um tipo específico. Para cadax
noX
, então, isso dá|P(x)|
'escolhas' de saída.O punchline é
o que, obviamente, não faz muito sentido se
X
forIO ()
, mas é aplicável a tipos algébricos.Da mesma forma, um termo do tipo
é o tipo de pares
(x, p)
comp : P(x)
, portanto, dado que qualquer umx
emX
podemos construir um par apropriado com qualquer membro deP(x)
, dando|P(x)|
'escolhas'.Conseqüentemente,
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
como expressões de tipo?
Nem tanto. Embora possamos considerar informalmente o significado de expressões como
Xⁿ
em Haskell, ondeX
é um tipo en
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
seja o tipo de comprimento de
n
vetores deX
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 paran ∈ ℕ
eu escrever,˻n˼
quero dizer o termo emNat
que representan
. Por exemplo,˻3˼
éS (S (S 0))
.Então nós temos
para qualquer
n ∈ ℕ
.Tipos Nat: promovendo ℕ termos para tipos
Agora podemos codificar expressões como
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 - levarX
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
expressões como
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. 1Da 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,
α
deNat
para*
, com a propriedadeA seguinte pseudo-definição é suficiente.
Então, vemos que a ação de
α
espelha o comportamento do sucessorS
, 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 qualquera
um com um tamanho definido.Por exemplo
α ˻4˼
(que éα (S (S (S (S 0))))
), ée os termos deste tipo são
dando-nos exatamente quatro elementos:
|α ˻4˼| = 4
.Da mesma forma, para qualquer um
n ∈ ℕ
, temoscomo requerido.
*
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
torna-se o tipo
onde
˻f˼ : Nat → Nat
há alguma representação adequada no idioma da funçãof
. Podemos ver isso da seguinte maneira.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.
fonte