Codificação de igreja - Church encoding

Em matemática , a codificação Church é um meio de representar dados e operadores no cálculo lambda . Os numerais da Igreja são uma representação dos números naturais usando a notação lambda. O método tem o nome de Alonzo Church , que primeiro codificou os dados no cálculo lambda desta forma.

Os termos que geralmente são considerados primitivos em outras notações (como inteiros, booleanos, pares, listas e uniões marcadas) são mapeados para funções de ordem superior na codificação Church. A tese de Church-Turing afirma que qualquer operador computável (e seus operandos) pode ser representado pela codificação Church. No cálculo lambda sem tipo, o único tipo de dado primitivo é a função.

A codificação Church não se destina a ser uma implementação prática de tipos de dados primitivos. Seu uso é para mostrar que outros tipos de dados primitivos não são necessários para representar qualquer cálculo. A completude é representacional. Funções adicionais são necessárias para traduzir a representação em tipos de dados comuns, para exibição às pessoas. Em geral, não é possível decidir se duas funções são extensionalmente iguais devido à indecidibilidade de equivalência do teorema de Church . A tradução pode aplicar a função de alguma forma para recuperar o valor que ela representa ou procurar seu valor como um termo lambda literal.

O cálculo lambda é geralmente interpretado como usando igualdade intensional . Existem problemas potenciais com a interpretação dos resultados devido à diferença entre a definição intensional e extensional de igualdade.

Numerais de igreja

Os numerais da igreja são as representações dos números naturais sob a codificação da igreja. A função de ordem superior que representa o número natural n é uma função que mapeia qualquer função para sua composição n- vezes . Em termos mais simples, o "valor" do numeral é equivalente ao número de vezes que a função encapsula seu argumento.

Todos os numerais de igreja são funções que aceitam dois parâmetros. Os numerais da igreja 0 , 1 , 2 , ..., são definidos como segue no cálculo lambda .

Começando com 0 sem aplicar a função de forma alguma, prossiga com 1 aplicando a função uma vez, 2 aplicando a função duas vezes, 3 aplicando a função três vezes, etc .:

O numeral da Igreja 3 representa a ação de aplicar qualquer função dada três vezes a um valor. A função fornecida é aplicada primeiro a um parâmetro fornecido e, em seguida, sucessivamente ao seu próprio resultado. O resultado final não é o numeral 3 (a menos que o parâmetro fornecido seja 0 e a função seja uma função sucessora ). A função em si, e não seu resultado final, é o numeral da Igreja 3 . O número 3 da Igreja significa simplesmente fazer qualquer coisa três vezes. É uma demonstração ostensiva do que se entende por "três vezes".

Cálculo com numerais de igreja

As operações aritméticas em números podem ser representadas por funções em numerais de Igreja. Essas funções podem ser definidas no cálculo lambda ou implementadas na maioria das linguagens de programação funcionais (consulte conversão de expressões lambda em funções ).

A função de adição usa a identidade .

A função sucessora é β-equivalente a .

A função de multiplicação usa a identidade .

A função de exponenciação é dada pela definição dos numerais da Igreja ,. Na definição, substitua por obter e,

que dá a expressão lambda,

A função é mais difícil de entender.

Um numeral de Igreja aplica uma função n vezes. A função predecessora deve retornar uma função que aplica seu parâmetro n - 1 vezes. Isso é obtido construindo um contêiner em torno de f e x , que é inicializado de uma maneira que omite a aplicação da função na primeira vez. Veja predecessor para uma explicação mais detalhada.

A função de subtração pode ser escrita com base na função predecessora.

Tabela de funções em numerais de igreja

Função Álgebra Identidade Definição de função Expressões lambda
Sucessor ...
Adição
Multiplicação
Exponenciação
Predecessor *

Subtração * ...

* Observe que na codificação da Igreja,

Derivação da função predecessora

A função predecessora usada na codificação Church é,

.

Para construir o predecessor, precisamos de uma maneira de aplicar a função 1 menos tempo. Um numeral n aplica a função f n vezes a x . A função predecessora deve usar o numeral n para aplicar a função n -1 vezes.

Antes de implementar a função predecessora, aqui está um esquema que envolve o valor em uma função de contêiner. Definiremos novas funções para usar no lugar de f e x , chamadas inc e init . A função do contêiner é chamada de valor . O lado esquerdo da tabela mostra um numeral n aplicado a inc e init .

A regra geral de recorrência é,

Se também houver uma função para recuperar o valor do contêiner (chamada de extração ),

Em seguida, extrair pode ser usado para definir a função samenum como,

A função samenum não é intrinsecamente útil. No entanto, como inc delega a chamada de f para seu argumento de contêiner, podemos fazer com que no primeiro aplicativo inc receba um contêiner especial que ignora seu argumento, permitindo pular a primeira aplicação de f . Chame esse novo contêiner inicial de const . O lado direito da tabela acima mostra as expansões de n inc const . Então, substituindo init por const na expressão para a mesma função, obtemos a função predecessora,

Conforme explicado abaixo, as funções inc , init , const , value e extract podem ser definidas como,

O que dá a expressão lambda para pred as,

Contentor de valor

O contêiner de valor aplica uma função a seu valor. É definido por,

tão,

Inc

A função inc deve receber um valor contendo v e retornar um novo valor contendo fv .

Deixando g ser o contêiner de valor,

então,

tão,

Extrair

O valor pode ser extraído aplicando a função de identidade,

Usando I ,

tão,

Const

Para implementar pred, a função init é substituída pela const que não se aplica f . Precisamos const para satisfazer,

Que fica satisfeito se,

Ou como uma expressão lambda,

Outra maneira de definir pred

Pred também pode ser definido usando pares:

Esta é uma definição mais simples, mas leva a uma expressão mais complexa para pred. A expansão para :

Divisão

A divisão de números naturais pode ser implementada por,

O cálculo requer muitas reduções beta. A menos que faça a redução manualmente, isso não importa muito, mas é preferível não ter que fazer esse cálculo duas vezes. O predicado mais simples para testar números é IsZero, portanto, considere a condição.

Mas essa condição é equivalente a , não . Se esta expressão for usada, então a definição matemática da divisão dada acima é traduzida em função em numerais de Igreja como,

Conforme desejado, essa definição tem uma única chamada para . No entanto, o resultado é que essa fórmula fornece o valor de .

Este problema pode ser corrigido adicionando 1 an antes de chamar divide . A definição de divisão é, então,

divide1 é uma definição recursiva. O combinador Y pode ser usado para implementar a recursão. Crie uma nova função chamada div por;

  • No lado esquerdo
  • No lado direito

para obter,

Então,

Onde,

Dá,

Ou como texto, usando \ para λ ,

divide = (\n.((\f.(\x.x x) (\x.f (x x))) (\c.\n.\m.\f.\x.(\d.(\n.n (\x.(\a.\b.b)) (\a.\b.a)) d ((\f.\x.x) f x) (f (c d m f x))) ((\m.\n.n (\n.\f.\x.n (\g.\h.h (g f)) (\u.x) (\u.u)) m) n m))) ((\n.\f.\x. f (n f x)) n))

Por exemplo, 9/3 é representado por

divide (\f.\x.f (f (f (f (f (f (f (f (f x))))))))) (\f.\x.f (f (f x)))

Usando uma calculadora de cálculo lambda, a expressão acima se reduz a 3, usando a ordem normal.

\f.\x.f (f (f (x)))

Números assinados

Uma abordagem simples para estender Números de Igreja a números com sinais é usar um par de Igreja, contendo numerais de Igreja que representam um valor positivo e um valor negativo. O valor inteiro é a diferença entre os dois números da Igreja.

Um número natural é convertido em um número assinado por,

A negação é realizada trocando os valores.

O valor inteiro é mais naturalmente representado se um do par for zero. A função OneZero atinge essa condição,

A recursão pode ser implementada usando o combinador Y,

Mais e menos

A adição é definida matematicamente no par por,

A última expressão é traduzida em cálculo lambda como,

Da mesma forma a subtração é definida,

dando,

Multiplique e divida

A multiplicação pode ser definida por,

A última expressão é traduzida em cálculo lambda como,

Uma definição semelhante é dada aqui para divisão, exceto nesta definição, um valor em cada par deve ser zero (consulte OneZero acima). A função divZ nos permite ignorar o valor que tem um componente zero.

divZ é então usado na seguinte fórmula, que é a mesma da multiplicação, mas com mult substituído por divZ .

Números racionais e reais

Os números reais racionais e computáveis ​​também podem ser codificados no cálculo lambda. Os números racionais podem ser codificados como um par de números com sinal. Os números reais computáveis ​​podem ser codificados por um processo de limitação que garante que a diferença do valor real difere em um número que pode ser tão pequeno quanto necessário. As referências fornecidas descrevem software que poderia, em teoria, ser traduzido em cálculo lambda. Uma vez que os números reais são definidos, os números complexos são codificados naturalmente como um par de números reais.

Os tipos de dados e funções descritos acima demonstram que qualquer tipo de dados ou cálculo pode ser codificado no cálculo lambda. Esta é a tese de Church-Turing .

Tradução com outras representações

A maioria das linguagens do mundo real tem suporte para inteiros nativos da máquina; as funções igreja e não igreja são convertidas entre inteiros não negativos e seus números de igreja correspondentes. As funções são fornecidas aqui em Haskell , onde o \corresponde ao λ do cálculo Lambda. As implementações em outras linguagens são semelhantes.

type Church a = (a -> a) -> a -> a

church :: Integer -> Church Integer
church 0 = \f -> \x -> x
church n = \f -> \x -> f (church (n-1) f x)

unchurch :: Church Integer -> Integer
unchurch cn = cn (+ 1) 0

Igreja Booleana

Os booleanos da Igreja são a codificação da Igreja dos valores booleanos verdadeiro e falso. Algumas linguagens de programação usam isso como um modelo de implementação para aritmética booleana; exemplos são Smalltalk e Pico .

A lógica booleana pode ser considerada uma escolha. A codificação da Igreja de verdadeiro e falso são funções de dois parâmetros:

  • true escolhe o primeiro parâmetro.
  • false escolhe o segundo parâmetro.

As duas definições são conhecidas como Church Booleans:

Esta definição permite que predicados (ou seja, funções que retornam valores lógicos ) atuem diretamente como cláusulas if. Uma função que retorna um booleano, que é então aplicado a dois parâmetros, retorna o primeiro ou o segundo parâmetro:

avalia a cláusula then se o predicado-x for avaliado como verdadeiro , e a cláusula else se o predicado-x for avaliado como falso .

Como verdadeiro e falso escolhem o primeiro ou o segundo parâmetro, eles podem ser combinados para fornecer operadores lógicos. Observe que existem várias implementações possíveis de não .

Alguns exemplos:

Predicados

Um predicado é uma função que retorna um valor booleano. O predicado mais fundamental é , que retorna se seu argumento for o numeral da Igreja e se seu argumento for qualquer outro numeral da Igreja:

O seguinte predicado testa se o primeiro argumento é menor ou igual ao segundo:

,

Por causa da identidade,

O teste de igualdade pode ser implementado como,

Pares de igreja

Os pares de igrejas são a codificação de igrejas do tipo par (duas tuplas). O par é representado como uma função que recebe um argumento de função. Quando receber seu argumento, ele o aplicará aos dois componentes do par. A definição em cálculo lambda é,

Por exemplo,

Codificações de lista

Uma lista ( imutável ) é construída a partir de nós de lista. As operações básicas da lista são;

Função Descrição
nada Construa uma lista vazia.
isnil Teste se a lista está vazia.
contras Anexe um determinado valor a uma lista (possivelmente vazia).
cabeça Obtenha o primeiro elemento da lista.
cauda Obtenha o resto da lista.

Fornecemos quatro representações diferentes de listas abaixo:

  • Construa cada nó da lista a partir de dois pares (para permitir listas vazias).
  • Construa cada nó da lista a partir de um par.
  • Represente a lista usando a função de dobra à direita .
  • Representa a lista usando a codificação de Scott que recebe casos de expressão de correspondência como argumentos

Dois pares como um nó de lista

Uma lista não vazia pode ser implementada por um par de Church;

  • Primeiro contém a cabeça.
  • Em segundo lugar, contém a cauda.

No entanto, isso não dá uma representação da lista vazia, porque não há um ponteiro "nulo". Para representar nulo, o par pode ser agrupado em outro par, fornecendo valores livres,

  • Primeiro - é o ponteiro nulo (lista vazia).
  • Second.First contém a cabeça.
  • Second.Second contém a cauda.

Usando essa ideia, as operações básicas da lista podem ser definidas assim:

Expressão Descrição
O primeiro elemento do par é verdadeiro, o que significa que a lista é nula.
Recupere o indicador nulo (ou lista vazia).
Crie um nó de lista, que não seja nulo, e atribua a ele um cabeçalho h e um tail t .
em segundo lugar, o primeiro é a cabeça.
o segundo.segundo é a cauda.

Em um nulosegundo nunca é acessado, desde que a cabeça e cauda são aplicadas apenas a listas não vazios.

Um par como um nó de lista

Alternativamente, defina

onde a última definição é um caso especial do geral

Represente a lista usando a dobra direita

Como alternativa à codificação usando pares de Church, uma lista pode ser codificada identificando-a com sua função de dobra à direita . Por exemplo, uma lista de três elementos x, y e z pode ser codificada por uma função de ordem superior que, quando aplicada a um combinador ce um valor n retorna cx (cy (czn)).

Esta representação lista pode ser determinado tipo no Sistema F .

Represente a lista usando a codificação Scott

Uma representação alternativa é a codificação Scott, que usa a ideia de continuações e pode levar a um código mais simples. (veja também a codificação Mogensen – Scott ).

Nesta abordagem, usamos o fato de que as listas podem ser observadas usando a expressão de correspondência de padrões. Por exemplo, usando a notação Scala , se listdenota um valor do tipo Listcom lista Nile construtor vazios Cons(h, t), podemos inspecionar a lista e calcular nilCodecaso a lista esteja vazia e consCode(h, t)quando a lista não estiver vazia:

list match {
  case Nil        => nilCode
  case Cons(h, t) => consCode(h,t)
}

A 'lista' é dada pela forma como atua sobre 'nilCode' e 'consCode'. Portanto, definimos uma lista como uma função que aceita 'nilCode' e 'consCode' como argumentos, de modo que, em vez da correspondência de padrão acima, possamos simplesmente escrever:

Vamos denotar por 'n' o parâmetro correspondente a 'nilCode' e por 'c' o parâmetro correspondente a 'consCode'. A lista vazia é aquela que retorna o argumento nulo:

A lista não vazia com cabeça 'h' e cauda 't' é dada por

Mais geralmente, um tipo de dados algébrico com alternativas torna-se uma função com parâmetros. Quando o enésimo construtor tem argumentos, o parâmetro correspondente da codificação também aceita argumentos.

A codificação de Scott pode ser feita em cálculo lambda sem tipo, enquanto seu uso com tipos requer um sistema de tipos com recursão e polimorfismo de tipo. Uma lista com o tipo de elemento E nesta representação que é usada para calcular valores do tipo C teria a seguinte definição de tipo recursiva, onde '=>' denota o tipo de função:

type List = 
  C =>                    // nil argument
  (E => List => C) =>     // cons argument
  C                       // result of pattern matching

Uma lista que pode ser usada para calcular tipos arbitrários teria um tipo que é quantificado C. Uma lista genérica em Etambém tomaria Ecomo argumento de tipo.

Veja também

Notas

  1. ^ Esta fórmula é a definição de um numeral de Igreja n com f -> m, x -> f.
  2. ^ Allison, Lloyd. "Lambda Calculus Integers" .
  3. ^ Bauer, Andrej. "Resposta de Andrej a uma pergunta;" Representando números negativos e complexos usando cálculo lambda " " .
  4. ^ "Aritmética real exata" . Haskell .
  5. ^ Bauer, Andrej. "Software computacional de número real" .
  6. ^ Pierce, Benjamin C. (2002). Tipos e linguagens de programação . MIT Press . p. 500. ISBN 978-0-262-16209-8.
  7. ^ Tromp, John (2007). "14. Cálculo Lambda Binário e Lógica Combinatória". Em Calude, Cristian S (ed.). Aleatoriedade e complexidade, de Leibniz a Chaitin . World Scientific. pp. 237–262. ISBN 978-981-4474-39-9.
    Como PDF: Tromp, John (14 de maio de 2014). "Binary Lambda Calculus and Combinatory Logic" (PDF) . Página visitada em 2017-11-24 .
  8. ^ Jansen, Jan Martin (2013). "Programação no λ-Calculus: Da Igreja a Scott e Voltar". LNCS . 8106 : 168-180. doi : 10.1007 / 978-3-642-40355-2_12 .

Referências