Lógica combinatória

Origem: Wikipédia, a enciclopédia livre.
Ir para: navegação, pesquisa

Lógica combinatória é uma notação introduzida por Moses Schönfinkel e Haskell Curry para eliminar a necessidade de variáveis em lógica matemática. Vem sendo mais usada recentemente na ciência da computação como um modelo de computação e como base para o desenvolvimento de linguagens de programação funcionais. Ela é baseada em combinadores, funções de ordem superior somente usam aplicações de funções e outros combinadores para definir um resultado a partir de seus parâmetros.

Matemática[editar | editar código-fonte]

A lógica combinatória visava originalmente servir como uma "pré-lógica" que clarificasse o papel das variáveis quantificadas na lógica, eliminando-as do processo. Inventor original da lógica combinatória, Moses Schönfinkel não publicou nada mais sobre o assunto após seu artigo original de 1924, e parou de publicar após a consolidação do poder de Stalin em 1929. Haskell Curry redescobriu os combinadores enquanto trabalhava como instrutor na Universidade de Princeton no final de 1927. No final da década de 1930, Alonzo Church e seus estudantes da Princeton inventaram um formalismo rival para a abstração funcional, o cálculo lambda, que se tornou mais popular que a lógica combinatória. Até que a ciência da computação tomasse interesse no assunto entre as décadas de 1960 e 1970, quase todo o trabalho no assunto foi publicado por Curry e seus estudantes, ou por Robert Feys na Bélgica. Curry e Feys (1958) e Curry et al. (1972) traçaram os primórdios dessa notação.

Ciência da computação[editar | editar código-fonte]

Na ciência da computação, a lógica combinatória é usada como um modelo simplificado de computação, usado na teoria da computabilidade e na teoria da demonstração. Apesar de sua simplicidade, ela captura diversos elementos essenciais da computação.

A lógica combinatória pode ser vista como uma variação do cálculo lambda, em que as expressões lambda (representando a abstração funcional) são substituídas por um conjunto limitado de combinadores, funções primitivas sem a presença de variáveis livres. É trivial transformar expressões lambda em expressões de combinadores, e a redução de combinadores é muito mais simples que a de lambda. Portanto, a lógica combinatória tem sido usada para modelar algumas linguagens de programação funcionais não rígidas e hardware. A visão mais pura dessa notação é a linguagem de programação Unlambda, cujas únicas primitivas são os combinadores S e K. Apesar da carência de praticidade, a linguagem promove certo interesse teórico.

Cálculo combinatório[editar | editar código-fonte]

Como a abstração é a única forma de montagem de funções em lambda cálculo, deve-se criar um substituto no cálculo combinatório. Ao invés de abstração, o cálculo combinatório prove um conjunto limitado de funções a partir das quais outras podem ser construídas.

Termos[editar | editar código-fonte]

Um termo combinatório possui uma das seguintes formas:

  • x
  • P
  • (E_1 E_2)

em que x é uma variável, P é uma das funções primitivas e (E_1 E_2) é a aplicação dos termos combinatórios E_1 e E_2. As funções primitivas são combinadores, ou funções que, quando vistas como termos lambda, não possuem variáveis livres. Para facilitar a notação, convenciona-se que (E_1 E_2 E_3 \ldots E_n) ou mesmo E_1 E_2 E_3 \ldots E_n denotam o termo (\ldots ((E_1 E_2) E_3) \ldots E_n). A mesma convenção é usada na aplicação múltipla do cálculo lambda.

Redução[editar | editar código-fonte]

Cada combinador primitivo possui uma regra de redução da forma:

(P x_1 \ldots x_n) = E

em que E é um termo mencionando somente variáveis do conjunto x_1 \ldots x_n. É assim que os combinadores primitivos atuam como funções.

Exemplos de combinadores[editar | editar código-fonte]

O exemplo mais simples de combinador é I, o combinador identidade, definido por (I x) = x para todos os termos x.

Outro combinador simples é K, que monta funções constantes. (K x) é uma função que, para qualquer parâmetro, retorna x, de forma que ((K x) y) = x para todos os termos x e y. Ou, a seguinte convenção para múltipla aplicação: (K x y) = x.

Mais um combinador é S, uma forma generalizada de aplicação, de forma que (S x y z) = (x z (y z)). S aplica x a y após substituir z em ambos.

Uma característica interessante é que, dados S e K, I pode ser construído da seguinte forma:


\begin{align}
 ((S K K) x) & = (S K K x) \\
      & = (K x (K x)) \\
      & = x \\
\end{align}

para qualquer termo x. Notar que apesar de ((S K K) x) = (I x) para qualquer x, (S K K) \neq I. Diz-se que os termos são estencionalimente iguais. Esse conceito captura a noção matemática de igualidade de funções, em que duas funções são iguais se elas produzem a mesma saída para a mesma entrada. Em contraste, os termos em si, juntos com a redução dos combinadoers primitivos, capturam a noção de igualidade intencional, isto é, duas funções são iguais se e somente se elas possuem implementações idênticas até a expansão dos combinadores primitivos, quando estes são aplicados aos parâmetros. Há várias formas de implementação da função identidade, e entre elas estão (S K K), I e (S K S). Um combinador mais interessante é o combinador de ponto fixo, ou Y, que pode ser usado para implementar recursividade.

Completude da base S-K[editar | editar código-fonte]

S e K podem ser compostas para produzir combinadores equivalentes a qualquer termo lambda, e portanto, segundo a tese de Church, para qualquer função computável. A demonstração é apresentar uma transformação T[] que converte um termo lambda qualquer em um combinador equivalente. Pode-se definir T[] da seguinte forma:

  1. T[x] \Rightarrow x
  2. T[(E_1 E_2)] \Rightarrow (T[E_1] T[E_2])
  3. T[\lambda x.E] \Rightarrow (K T[E])
  4. T[\lambda x.x] \Rightarrow I
  5. T[\lambda x.\lambda y.E] \Rightarrow T[\lambda x.T[\lambda y.E]]
  6. T[\lambda x.(E_1 E_2)] \Rightarrow (S T[\lambda x.E_1] T[\lambda x.E_2])

Esse processo é também conhecido como eliminação de abstração.

Exemplo de conversão[editar | editar código-fonte]

Por exemplo, pode-se converter o termo lambda \lambda x.\lambda y.(y x) em um combinador:


\begin{align}
 T[\lambda x.\lambda y.(y x)] & = T[\lambda x.T[\lambda y.(y x)]] \\
      & = T[\lambda x.(S T[\lambda y.y] T[\lambda y.x])] \\
      & = T[\lambda x.(S I T[\lambda y.x])] \\
      & = T[\lambda x.(S I (K x))] \\
      & = (S T[\lambda x.(S I)] T[\lambda x.(K x)]) \\
      & = (S (K (S I)) T[\lambda x.(K x)]) \\
      & = (S (K (S I)) (S T[\lambda x.K] T[\lambda x.x)]) \\
      & = (S (K (S I)) (S (K K) T[\lambda x.x)]) \\
      & = (S (K (S I)) (S (K K) I)) \\
\end{align}

Se aplicarmos esse combinador a quaisquer termos x e y, reduz-se da seguinte forma:


\begin{align}
 (S (K (S I)) (S (K K) I) x y) & = (K (S I) x (S (K K) I x) y) \\
      & = (S I (S (K K) I x) y) \\
      & = (I y (S (K K) I x y)) \\
      & = (y (S (K K) I x y)) \\
      & = (y (K K x (I x) y)) \\
      & = (y (K (I x) y)) \\
      & = (y (I x)) \\
      & = (y x) \\
\end{align}

Ver também[editar | editar código-fonte]