Los números de iglesia son una codificación de números naturales como funciones.
(\ 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
Claramente, puede exponer 2 números de iglesia simplemente aplicándolos. Es decir, si aplica 4 a 2, obtiene el número de la iglesia 16
, o 2^4
. Obviamente, eso es completamente poco práctico. Los números de la iglesia necesitan una cantidad lineal de memoria y son muy, muy lentos. 10^10
Calcular algo como , que GHCI responde rápidamente correctamente, llevaría años y, de todos modos, no podría caber en la memoria de su computadora.
Últimamente he estado experimentando con evaluadores λ óptimos. En mis pruebas, accidentalmente escribí lo siguiente en mi calculadora λ óptima:
10 ^ 10 % 13
Se suponía que era multiplicación, no exponenciación. Antes de que pudiera mover mis dedos para abortar el programa que se ejecuta para siempre con desesperación, respondió a mi solicitud:
3
{ iterations: 11523, applications: 5748, used_memory: 27729 }
real 0m0.104s
user 0m0.086s
sys 0m0.019s
Con mi "alerta de error" parpadeando, fui a Google y verifiqué, de 10^10%13 == 3
hecho. Pero no se suponía que la calculadora λ encontrara ese resultado, apenas puede almacenar 10 ^ 10. Empecé a enfatizarlo, por la ciencia. En el acto se me respondió 20^20%13 == 3
, 50^50%13 == 4
, 60^60%3 == 0
. Tuve que usar herramientas externas para verificar esos resultados, ya que el propio Haskell no pudo calcularlo (debido al desbordamiento de enteros) (por supuesto, si usa Integers not Ints). Llevándolo al límite, esta fue la respuesta a 200^200%31
:
5
{ iterations: 10351327, applications: 5175644, used_memory: 23754870 }
real 0m4.025s
user 0m3.686s
sys 0m0.341s
Si tuviéramos una copia del universo para cada átomo en el universo, y tuviéramos una computadora para cada átomo que teníamos en total, no podríamos almacenar el número de la iglesia 200^200
. Esto me llevó a preguntarme si mi Mac era realmente tan poderoso. Quizás el evaluador óptimo pudo saltarse las ramas innecesarias y llegar directamente a la respuesta de la misma manera que Haskell lo hace con la evaluación perezosa. Para probar esto, compilé el 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)
Esto genera correctamente 1
( 5 ^ 5 % 4
), pero arroja cualquier cosa por encima 10^10
y se atascará, eliminando la hipótesis.
El evaluador óptimo que utilicé es un programa JavaScript no optimizado de 160 líneas de largo que no incluía ningún tipo de matemática de módulo exponencial, y la función de módulo de cálculo lambda que utilicé fue igualmente simple:
(λ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))))))
No utilicé ningún algoritmo o fórmula aritmética modular específica. Entonces, ¿cómo puede el evaluador óptimo llegar a las respuestas correctas?
node test.js
. Hazme saber si tienes alguna pregunta.