O problema de representar variáveis ligadas na sintaxe, e em particular o de substituição para evitar capturas, é bem conhecido e tem várias soluções: variáveis nomeadas com equivalência alfa, índices de Bruijn, localmente sem nome, conjuntos nominais etc.
Mas parece haver outra abordagem bastante óbvia, que eu ainda não vi usada em nenhum lugar. Nomeadamente, na sintaxe básica, temos apenas um termo "variável", escrito digamos , e então, separadamente, fornecemos uma função que mapeia cada variável para um fichário em cujo escopo está. Então, um -term comoλ
seria escrito , e a função mapeará o primeiro para o primeiro e o segundo para o segundo . Portanto, é como os índices de Bruijn, apenas em vez de ter que "contar s" enquanto você sai do termo para encontrar o fichário correspondente, basta avaliar uma função. (Se representar isso como uma estrutura de dados em uma implementação, eu pensaria em equipar cada objeto de termo variável com um simples ponteiro / referência ao objeto de termo vinculativo correspondente.)∙ λ ∙ λ λ
Obviamente, isso não é sensato para escrever sintaxe em uma página para humanos lerem, mas também não são os índices de De Bruijn. Parece-me que faz todo o sentido matematicamente e, em particular, facilita muito a substituição que evita a captura: apenas solte o termo que você está substituindo e adote a união das funções de ligação. É verdade que ele não tem noção de "variável livre", mas então (novamente) nem os índices de Bruijn realmente; nos dois casos, um termo contendo variáveis livres é representado com uma lista de ligantes de "contexto" à frente.
Estou faltando alguma coisa e há algum motivo para essa representação não funcionar? Existem problemas que o tornam muito pior do que os outros que não vale a pena considerar? (O único problema em que posso pensar agora é que o conjunto de termos (junto com suas funções vinculativas) não é definido indutivamente, mas isso não parece intransponível.) Ou, na verdade, existem lugares onde foi usado?
fonte
Respostas:
As respostas de Andrej e Łukasz são boas, mas eu queria acrescentar comentários adicionais.
Para ecoar o que Damiano disse, essa maneira de representar a encadernação usando ponteiros é a sugerida pelas redes de prova, mas o primeiro local em que eu a vi para termos lambda foi em um antigo ensaio de Knuth:
Na página 234, ele desenhou o diagrama a seguir (que ele chamou de "estrutura da informação") representando o termo :(λy.λz.yz)x
Esse tipo de representação gráfica dos termos lambda também foi estudado de forma independente (e mais profunda) em duas teses no início dos anos 1970, tanto por Christopher Wadsworth (1971, Semântica e Pragmática do Lambda-Calculus ) quanto por Richard Statman (1974, Structural Complexity). Provas ). Atualmente, esses diagramas são frequentemente referidos como "gráficos λ" (veja, por exemplo, este artigo ).
Observe que o termo no diagrama de Knuth é linear , no sentido de que toda variável livre ou vinculada ocorre exatamente uma vez - como outros já mencionaram, existem questões e escolhas não triviais a serem feitas na tentativa de estender esse tipo de representação a não termos-lineares.
Por outro lado, para termos lineares, acho ótimo! A linearidade exclui a necessidade de cópia e, portanto, você obtém a equivalência e a substituição "de graça". Essas são as mesmas vantagens do HOAS, e eu realmente concordo com Rodolphe Lepigre de que há uma conexão (se não exatamente uma equivalência) entre as duas formas de representação: há um sentido em que essas estruturas gráficas podem ser naturalmente interpretadas como diagramas de strings , representando endomorfismos de um objeto reflexivo em uma categoria fechada e compacta (dei uma breve explicação sobre isso aqui ).α
fonte
Não tenho certeza de como sua função variável para encadernador seria representada e com qual finalidade você gostaria de usá-lo. Se você estiver usando ponteiros de retorno, como Andrej observou, a complexidade computacional da substituição não é melhor do que a renomeação alfa clássica.
Do seu comentário sobre a resposta de Andrej, deduzo que, até certo ponto, você está interessado em compartilhar. Eu posso fornecer alguma entrada aqui.
Em um cálculo lambda típico, o enfraquecimento e a contração, ao contrário de outras regras, não possuem sintaxe.
Γ , x 1 : A , x 2 : Um ⊢ t : T
Vamos adicionar alguma sintaxe:
Com essa sintaxe, cada variável é usada exatamente duas vezes, uma vez onde está vinculada e uma vez onde é usada. Isso nos permite nos distanciar de uma sintaxe específica e ver o termo como um gráfico em que variáveis e termos são arestas.
Da complexidade algorítmica, agora podemos usar ponteiros não de uma variável para um fichário, mas de um fichário para variável e ter substituições em um tempo constante.
Além disso, essa reformulação nos permite rastrear o apagamento, a cópia e o compartilhamento com mais fidelidade. Pode-se escrever regras que copiam (ou apagam) incrementalmente um termo enquanto compartilham subtermos. Há muitas maneiras de fazer isso. Em alguns ambientes restritos, as vitórias são bastante surpreendentes .
Isso está chegando perto dos tópicos de redes de interação, combinadores de interação, substituição explícita, lógica linear, avaliação ideal de Lamping, compartilhamento de gráficos, lógicas leves e outras.
Todos esses tópicos são muito empolgantes para mim e, com prazer, daria referências mais específicas, mas não tenho certeza se isso é útil para você e quais são seus interesses.
fonte
Sua estrutura de dados funciona, mas não será mais eficiente do que outras abordagens, porque você precisa copiar todos os argumentos de todas as reduções de beta e precisa fazer quantas cópias houver ocorrências da variável vinculada. Dessa forma, você continua destruindo o compartilhamento de memória entre subtermos. Combinado ao fato de você estar propondo uma solução não pura que envolve manipulações de ponteiros e, portanto, é muito propensa a erros, provavelmente não vale a pena.
Mas eu ficaria encantado em ver um experimento! Você pode pegá
lambda
-lo e implementá-lo com sua estrutura de dados (o OCaml tem ponteiros, eles são chamados de referências ). Mais ou menos, você apenas precisa substituirsyntax.ml
enorm.ml
pelas suas versões. São menos de 150 linhas de código.fonte
Outras respostas estão discutindo principalmente questões de implementação. Como você menciona sua principal motivação como fazer provas matemáticas sem muita contabilidade, aqui está o principal problema que vejo com isso.
Quando você diz “uma função que mapeia cada variável para um fichário em cujo escopo ela se encontra”: o tipo de saída dessa função é certamente um pouco mais sutil do que isso soa! Especificamente, a função deve levar valores em algo como "os ligantes do termo em consideração" - ou seja, um conjunto que varia dependendo do termo (e não é obviamente um subconjunto de um conjunto de ambiente maior de qualquer maneira útil). Portanto, na substituição, você não pode simplesmente “assumir a união das funções de ligação”: você também precisa reindexar seus valores, de acordo com algum mapa, de ligantes nos termos originais a ligantes no resultado da substituição.
Essas reindexações certamente deveriam ser "rotineiras", no sentido de poderem ser razoavelmente varridas para debaixo do tapete ou bem embaladas em termos de algum tipo de funcionalidade ou naturalidade. Mas o mesmo se aplica à contabilidade envolvida no trabalho com variáveis nomeadas. De modo geral, parece-me provável que haja pelo menos tanta contabilidade envolvida com essa abordagem quanto com abordagens mais padrão.
Isso, porém, é uma abordagem conceitualmente muito atraente, e eu adoraria vê-la cuidadosamente elaborada - posso imaginar que possa lançar uma luz diferente sobre alguns aspectos da sintaxe do que as abordagens padrão.
fonte
Lazy.t
No geral, acho que é uma representação interessante, mas envolve alguma contabilidade com ponteiros, para evitar a quebra de links vinculativos. Seria possível alterar o código para usar campos mutáveis, eu acho, mas a codificação em Coq seria menos direta. Ainda estou convencido de que isso é muito semelhante ao HOAS, embora a estrutura do ponteiro seja explicitada. No entanto, a presença de
Lazy.t
implica que é possível que algum código seja avaliado no momento errado. Esse não é o caso no meu código, pois apenas a substituição de uma variável por uma variável pode ocorrer noforce
momento (e não a avaliação, por exemplo).fonte