Os números das igrejas são uma codificação dos números naturais como funções.
(\ f x → (f x)) -- church number 1
(\ f x → (f (f (f x)))) -- church number 3
(\ f x → (f (f (f (f x))))) -- church number 4
Ordenadamente, você pode exponenciar dois números de igrejas apenas aplicando-os. Ou seja, se você aplicar 4 a 2, obterá o número da igreja 16
ou 2^4
. Obviamente, isso é totalmente impraticável. Os números das igrejas precisam de uma quantidade linear de memória e são muito, muito lentos. A computação de algo como 10^10
- que o GHCI responde rapidamente corretamente - levaria séculos e não caberia na memória do seu computador.
Ultimamente tenho experimentado avaliadores λ ideais. Nos meus testes, digitei acidentalmente o seguinte na minha λ-calculadora ideal:
10 ^ 10 % 13
Era para ser multiplicação, não exponenciação. Antes que eu pudesse mover meus dedos para abortar o programa em execução eterna em desespero, ele respondeu ao meu pedido:
3
{ iterations: 11523, applications: 5748, used_memory: 27729 }
real 0m0.104s
user 0m0.086s
sys 0m0.019s
Com o meu "alerta de bug" piscando, fui ao Google e verifiquei, de 10^10%13 == 3
fato. Mas a calculadora λ não deveria encontrar esse resultado, mal pode armazenar 10 ^ 10. Comecei a enfatizar isso, pela ciência. Ele imediatamente me respondeu 20^20%13 == 3
, 50^50%13 == 4
, 60^60%3 == 0
. Eu tive que usar ferramentas externas para verificar esses resultados, já que o próprio Haskell não foi capaz de computá-lo (devido ao estouro de número inteiro) (é se você usar Inteiros, não Ints, é claro!). Levando-o ao limite, esta foi a resposta para 200^200%31
:
5
{ iterations: 10351327, applications: 5175644, used_memory: 23754870 }
real 0m4.025s
user 0m3.686s
sys 0m0.341s
Se tivéssemos uma cópia do universo para cada átomo no universo e tivéssemos um computador para cada átomo no total, não poderíamos armazenar o número da igreja 200^200
. Isso me levou a questionar se meu Mac era realmente tão poderoso. Talvez o avaliador ideal tenha conseguido pular os ramos desnecessários e chegar diretamente à resposta da mesma maneira que Haskell faz na avaliação preguiçosa. Para testar isso, compilei o programa λ para Haskell:
data Term = F !(Term -> Term) | N !Double
instance Show Term where {
show (N x) = "(N "++(if fromIntegral (floor x) == x then show (floor x) else show x)++")";
show (F _) = "(λ...)"}
infixl 0 #
(F f) # x = f x
churchNum = F(\(N n)->F(\f->F(\x->if n<=0 then x else (f#(churchNum#(N(n-1))#f#x)))))
expMod = (F(\v0->(F(\v1->(F(\v2->((((((churchNum # v2) # (F(\v3->(F(\v4->(v3 # (F(\v5->((v4 # (F(\v6->(F(\v7->(v6 # ((v5 # v6) # v7))))))) # v5))))))))) # (F(\v3->(v3 # (F(\v4->(F(\v5->v5)))))))) # (F(\v3->((((churchNum # v1) # (churchNum # v0)) # ((((churchNum # v2) # (F(\v4->(F(\v5->(F(\v6->(v4 # (F(\v7->((v5 # v7) # v6))))))))))) # (F(\v4->v4))) # (F(\v4->(F(\v5->(v5 # v4))))))) # ((((churchNum # v2) # (F(\v4->(F(\v5->v4))))) # (F(\v4->v4))) # (F(\v4->v4))))))) # (F(\v3->(((F(\(N x)->F(\(N y)->N(x+y)))) # v3) # (N 1))))) # (N 0))))))))
main = print $ (expMod # N 5 # N 5 # N 4)
Isso gera corretamente 1
( 5 ^ 5 % 4
) - mas atire qualquer coisa acima 10^10
e ele ficará preso, eliminando a hipótese.
O avaliador ideal que usei é um programa JavaScript não otimizado de 160 linhas de comprimento que não inclui nenhum tipo de módulo de matemática exponencial - e a função do módulo lambda-calculus que usei era igualmente simples:
(λab.(b(λcd.(c(λe.(d(λfg.(f(efg)))e))))(λc.(c(λde.e)))(λc.(a(b(λdef.(d(λg.(egf))))(λd.d)(λde.(ed)))(b(λde.d)(λd.d)(λd.d))))))
Não usei nenhum algoritmo ou fórmula aritmética modular específica. Então, como o avaliador ideal é capaz de chegar às respostas certas?
node test.js
. Deixe-me saber se você tiver alguma dúvida.