Cálculo lambdaNa lógica matemática e na ciência da computação, o cálculo lambda, também escrito como cálculo-λ é um sistema formal que estuda funções recursivas computáveis, no que se refere à teoria da computabilidade, e fenômenos relacionados, como variáveis ligadas e substituição. Sua principal característica são as entidades que podem ser utilizadas como argumentos e retornadas como valores de outras funções. A parte relevante do cálculo lambda para computação ficou conhecida como cálculo lambda não tipado. O cálculo lambda tipado e o não tipado tem suas ideias aplicadas nos campos da lógica, teoria da recursão (computabilidade) e linguística, e tem tido um grande papel no desenvolvimento da teoria de linguagens de programação (com a versão não tipada sendo a inspiração original para programação funcional, em particular Lisp, e a versão tipada contribuindo para fundamentar modernos sistemas de tipos e linguagens de programação). HistóriaO cálculo lambda foi apresentada por Alonzo Church na década de 1930 como parte da investigação dos fundamentos da matemática.[1][2] O sistema original foi demonstrado ser logicamente inconsistente em 1935 quando Stephen Kleene e J. Barkley Rosser desenvolveram o paradoxo Kleene-Rosser. Em seguida, em 1936, Church isolou e publicou apenas a computação e que depois ficou conhecida como cálculo lambda não tipado.[3] Em 1940, ele também apresentou uma versão computacionalmente mais fraca, mas com um sistema lógico consistente, conhecido como cálculo lambda simplesmente tipado.[4] Alonzo Church foi orientador de Alan Turing. De certa forma, o trabalho de Church está para o conceito de software assim como o de Turing relaciona-se muito diretamente ao conceito de Hardware. Descrição informalMotivaçãoFunções recursivas são um conceito fundamental dentro da ciência da computação e da matemática. O cálculo-λ provê uma semântica simples para computações, permitindo que propriedades da computação fossem estudadas formalmente. Considere os dois exemplos a seguir. A função identidade
recebe uma única entrada,
recebe um par de entradas, A primeira observação é que funções não precisam ser nomeadas explicitamente. Isto é, a função
pode ser reescrita na forma anônima como
(leia-se “a tupla
pode ser reescrita em sua forma anônima para A segunda observação é que a escolha do nome para os argumentos de uma função é totalmente irrelevante. Isto é,
expressam a mesma função: a identidade. De forma similar,
também expressam a mesma função. Finalmente, qualquer função que recebe duas entradas, como a função
pode ser reelaborada para
Esta transformação é chamada currying, e pode ser generalizada para funções que aceitam um número arbitrário de argumentos. Currying pode ser entendido de forma mais clara através de um exemplo. Compare a função
com sua forma "curryficada",
Dado dois argumentos, temos:
No entanto, usando currying, temos:
e assim vemos que as versões com ou sem currying computam o mesmo resultado. Perceba que x*x se transformou numa constante. O Cálculo LambdaO cálculo lambda consiste de uma linguagem de termos lambda junto com uma teoria equacional (que pode também ser entendida operacionalmente). Como os nomes de funções são uma mera conveniência, o cálculo lambda não tem interesse em nomear uma função. Já que todas as funções esperando mais de um argumento podem ser transformadas em funções equivalentes recebendo uma única entrada (via currying), o cálculo lambda não tem interesse em criar funções que aceitam mais de um argumento. E como os nomes dos argumentos são irrelevantes, a noção nativa de igualdade entre termos lambda se chama alfa-equivalência e que demonstra este princípio. Termos lambdaA sintaxe de termos lambda é particularmente simples. Existem três maneiras de obtê-las:
Nada mais é termo lambda, apesar de que parenteses podem ser usados para tirar ambiguidades entre termos. Uma abstração lambda é uma definição de uma função não nomeada que é capaz de receber uma entrada e substitui-la em sua expressão . Define, assim, uma função anônima que recebe e retorna . Por exemplo, é uma abstração lambda para a função usando o termo para . A definição de uma função com a abstração lambda meramente "configura" a função, mas não a invoca. A abstração liga a variável no termo . Uma aplicação representa a aplicação de uma função com um argumento , ou seja, ela representa o ato de chamar a função com entrada para produzir . Em cálculo lambda não existe o conceito de declaração de variáveis. Em uma definição tal qual (i.e. ), cálculo lambda trata como uma variável que ainda não foi definida.A abstração lambda é sintaticamente valida, e representa uma função que soma a entrada com o valor ainda não conhecido . Parenteses podem ser usados para distinguir termos. Por exemplo, e representam diferentes termos (embora eles coincidentemente possam ser reduzidos para o mesmo termo normal). Aqui o primeiro exemplo define uma função que define outra função e retorna o resultado de aplicar x para a função-filha (aplicar a função e então retornar), enquanto o segundo exemplo define uma função que retorna uma função para qualquer entrada e então retorna a aplicação dela a x (retorna uma função e então aplica). Funções que operam em funçõesEm cálculo lambda, funções são consideradas como valores, então elas podem servir de entrada para outras funções, e funções podem retornar funções como saída. Por exemplo, O que torna os termos ainda mais interessantes são as várias noções de equivalência e redução que podem ser definidas sobre eles. Alfa-equivalênciaUma forma básica de equivalência, definida para termos lambda, é chamada de alfa-equivalência. Ela determina que a escolha da variável ligada, na abstração lambda, não importa (normalmente). Por exemplo, Perceba que os termos Em muitos casos, é fácil de identificar termos lambda equivalentes. As próximas definições serão necessárias para que a definição de beta-redução seja possível. Variáveis livresAs variáveis livres de um termo são aquelas variáveis que não são ligadas por uma abstração lambda. Isto é, as variáveis livres de Por exemplo, o termo lambda representando a função identidade Substituição de variáveisSuponha que , e são lambda termos e e são variáveis. A notação indica a substituição de por em . Isto é definido de modo que:
Exemplos:
Redução BetaA regra de redução beta afirma que uma aplicação da forma reduz para o termo . A notação é usada para indicar que beta reduz para . Por exemplo, para todo , . Isto demostra que realmente é a função identidade. De forma semelhante, , Que demonstra que e uma função constante. O cálculo lambda pode ser visto como uma linguagem de programação funcional idealizada. Como Haskell ou Standard ML. Sob este ponto de vista beta redução corresponde a um passo de computação. Este passo pode ser repetido por conversões beta adicionais até que não haja mais reduções beta. Em cálculo lambda não tipado, como este apresentado aqui, este processo pode não terminar em alguns casos. Um exemplo é o seguinte, considere o termo . Assim temos . Isto é, o termo reduz para ele mesmo em um passo da beta redução, e portanto o processo de redução nunca termina. Outro aspecto do cálculo lambda não tipado é que ele não faz distinção entre diferentes tipos de dados. Por exemplo, ele pode não ter interesse em escrever uma função que apenas opera com números. Contudo, em um cálculo lambda não tipado, não há nenhuma maneira de impedir uma função de ser aplicadas a valores de verdade, strings ou outros objetos não numéricos. Definição formalDefiniçãoExpressões lambda são compostas por
Considerando um número infinito de identificadores: {a, b, c, …, x, y, z, x1, x2, …}, o conjunto de todas as expressões do lambda pode então ser descrito pela seguinte gramática livre de contexto na forma normal de Backus:
O conjunto das expressões lambda, Λ, podem ser definidas indutivamente:
Instâncias da regra 2 são conhecidas como abstrações e instâncias de regra 3 são conhecidas como aplicações. NotaçãoPara manter a notação das expressões lambda organizada, as seguintes convenções são geralmente aplicados.
Variáveis livres e ligadasO operador de abstração λ, é dito para ligar suas variáveis sempre que elas ocorrem no corpo da abstração. Variáveis que caem dentro do âmbito de uma abstração são ditos ser ligado. Todas as outras variáveis são chamadas de livres. Por exemplo, na seguinte expressão y é uma variável ligada e x é uma variável livre: O conjunto das variáveis livres de uma expressão lambda M é denotado como FV(M) e é definido por recursão na estrutura de termos. Como se segue:
Uma expressão que não contém nenhuma variável livre é dita ser fechada. Expressão lambda fechada são também conhecidas como combinadores e são equivalentes para termos em combinatory logic. ReduçãoO significado de expressões lambda é definido por como expressões podem ser reduzidas.[9] Existem três tipos de reduções.
Nós também falamos das equivalências resultantes: duas expressões são β-equivalente, se elas podem ser β-convertidos em uma mesma expressão, e α/η-equivalência são definidas de forma semelhante. O termo redex, significa expressão redutível. refere-se a sub-termos que podem ser reduzidos por uma das regras de redução. Por exemplo, α-conversãoAlpha-conversão, às vezes é conhecida como alfa-renomeação,[10] permite alterar nomes de variáveis ligadas. Por exemplo, alpha-conversão de As regras para a alfa-conversão não são completamente triviais. Primeiro, quando alfa-conversão atua em uma abstração, as únicas ocorrências de variáveis que podem ser renomeados são aqueles que são vinculados a esta mesma abstração. Por exemplo, um alfa-conversão de Em segundo lugar, alfa-conversão não é possível se isto iria resultar em uma variável sendo capturada por uma abstração diferente. Por exemplo, se substituirmos Em linguagens de programação com escopo estático, alfa-conversão pode ser usado para fazer name resolution garantindo que nenhum nome de variável masks um nome em um determinado escopo. Na notação De Bruijn index, quaisquer dois termos alfa-equivalente são literalmente idênticos. SubstituiçãoSubstituição, escrito E[V := R], é o processo de substituição de todas as ocorrências da variável livre V na expressão E pela expressão R. Substituição de termos em λ-cálculo é definido por recursão sobre a estrutura de termos, do seguinte modo (note: x e y são apenas as variáveis enquanto M e N são alguma λ expressão). x[x := N] ≡ N y[x := N] ≡ y, se x ≠ y (M1 M2)[x := N] ≡ (M1[x := N]) (M2[x := N]) (λx.M)[x := N] ≡ λx.M (λy.M)[x := N] ≡ λy.(M[x := N]), se x ≠ y, e y ∉ FV(N) Para substituir em uma abstração lambda, às vezes é necessário algo converter α-expressão. Por exemplo, não é correto para (λx.y) [y = x:] para resultar em (λx.x), porque o x substituído era suposto ser livre, mas acabou sendo ligado. A substituição correta neste caso é (λz.x), pela α-equivalência. Observe que a substituição é definida exclusivamente até a α-equivalência. β-reduçãoBeta-redução capta a ideia de aplicação de função. Beta-redução é definida em termos de substituição: a beta-redução de Por exemplo, assumindo alguns codificação de η-conversãoEta-conversão expressa a ideia de extensionalidade, que neste contexto é que duas funções são as mesmas se e somente se eles dão o mesmo resultado para todos os argumentos. Eta-conversão converte entre Forma normal e confluênciaPara o cálculo lambda n/tipado, β-redução como uma rewriting rule não é nem fortemente normalizada nem fracamente normalizada. No entanto, se puder ser demonstrado que a β-redução é confluente. (É claro que estamos a trabalhar livre α-conversão, ou seja, nós consideramos duas formas normais para ser igual, se é possível α-converter um para o outro.) Portanto, ambos os termos fortemente normalizados e termos fracamente normalizados tem uma forma normal única. Para termos fortemente normalizáveis, qualquer estratégia de redução é garantida para alcançar a forma normal, enquanto que para termos fracamente normalizáveis, algumas estratégias de redução pode não conseguir encontrá-lo. Codificando tipos de dadosO cálculo lambda básico pode ser usado para modelar booleano, aritmética, estruturas de dados e a recursividade, como ilustrado nas subsecções seguintes. Aritmética no cálculo lambdaExistem várias formas possíveis para definir os números naturais no cálculo lambda, mas, de longe, os mais comuns são os numerais de Church, que podem ser definidos da seguinte forma:
e assim por diante. Ou usando a sintaxe alternativa representada anteriormente na Notação:
Um numeral de Church é uma função de ordem superior. Ela toma um função de um único argumento Podemos definir uma função sucessor, o que leva um número
Porque o
e
são expressões lambda β-equivalentes, desde que acrescentando
De forma simular, multiplicação pode ser definida da seguinte forma:
Alternativamente:
desde multiplicando
A função predecessora definida por
pode ser validado, mostrando indutivamente que se T representa
Lógica e predicadosPor convenção, as duas definições seguintes (conhecidas como booleanos de Church) serão utilizadas para os valores booleanos
Então, com esses dois termos λ, podemos definir alguns operadores lógicos (estes são apenas possíveis formulações; outras expressões são igualmente correto):
Estamos agora em condições de calcular algumas funções lógicas, por exemplo:
e vemos que Um predicado é uma função que retorna um valor booleano. O predicado mais fundamental é
Os seguintes predicados testa se o primeiro argumento é menor que ou igual a segundo:
e desde Da disponibilidade de predicados e na definição acima de
que pode ser verificada, mostrando indutivamente que ParesUm par (2-tupla) pode ser definida em termos de
Uma lista ligada pode ser definida como qualquer Como um exemplo da utilização de
O que nos permite dar talvez a versão mais transparente da função predecessor :
Recursão e pontos fixosRecursão é a definição de uma função que invoca a própria função; em face do que, cálculo lambda não permitir isto (nós não podemos nos referir a um valor que ainda está para ser definido, dentro do termo lambda que define esse mesmo valor, como todas as funções são anônimas em cálculo lambda). Contudo, esta impressão é enganosa: dentro de Considere, por exemplo, a função fatorial
Na expressão lambda que é representar esta função, um parâmetro (tipicamente o primeiro) será presumido para receber em si a expressão lambda como o seu valor, de modo que é chamando – aplicando-o a um argumento – será equivalente à recursão. Assim, para alcançar a recursividade, o argumento destinado-como-auto-referência (chamado
A auto-aplicação alcança replicação aqui, passando a função como expressão lambda para a próxima invocação como um valor de argumento, tornando-o disponível para ser referenciado e invocou ali. Isto resolve, mas necessita de reescrever cada chamada recursiva como auto-aplicação. Gostaríamos de ter uma solução genérica, sem necessidade de quaisquer reescrita:
Dado um termo lambda com o primeiro argumento representando chamada recursiva (i.e. Na verdade, há muitas definições possíveis para este operador
Em cálculo lambda,
Agora, para executar nossa chamada recursiva para a função fatorial, teríamos simplesmente que chamar
Cada função definida recursivamente pode ser vista como um ponto fixo de alguma função adequadamente definida através da chamada recursiva com um argumento extra, e, por conseguinte, utilizando Termos padrãoAlguns termos têm nomes comumente aceitos:
Ver tambémReferências
|