Церковные числа - это кодирование натуральных чисел как функций.
(\ 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
Аккуратно, вы можете увеличить 2 номера церкви, просто применяя их. То есть, если вы применяете 4 к 2, вы получаете номер церкви 16
или 2^4
. Очевидно, что это совершенно непрактично. Церковные числа нуждаются в линейном объеме памяти и действительно, очень медленны. Вычисляя что-то вроде 10^10
, которое GHCI быстро отвечает правильно, потребует возраста и не сможет поместиться в памяти на вашем компьютере.
В последнее время я экспериментировал с оптимальными оценщиками λ. В моих тестах я случайно набрал следующее на моем оптимальном λ-калькуляторе:
10 ^ 10 % 13
Предполагалось, что это будет умножение, а не возведение в степень. Прежде чем я смог перенести мои пальцы, чтобы отменить бесконечную программу в отчаянии, она ответила на мою просьбу:
3
{ iterations: 11523, applications: 5748, used_memory: 27729 }
real 0m0.104s
user 0m0.086s
sys 0m0.019s
Когда мое "предупреждение об ошибке" мигает, я отправился в Google и подтвердил, 10^10%13 == 3
действительно. Но λ-калькулятор не должен был найти этот результат, он едва может хранить 10 ^ 10. Я начал подчеркивать это, для науки. Он мгновенно ответил мне 20^20%13 == 3
, 50^50%13 == 4
, 60^60%3 == 0
. Мне пришлось использовать внешние инструменты, чтобы проверить эти результаты, так как сам Haskell не смог вычислить его (из-за переполнения целого) (это если вы используете целые числа, а не инты, конечно!). Подталкивая его к своим пределам, это был ответ на 200^200%31
:
5
{ iterations: 10351327, applications: 5175644, used_memory: 23754870 }
real 0m4.025s
user 0m3.686s
sys 0m0.341s
Если бы у нас была одна копия Вселенной для каждого атома на вселенной, и у нас был компьютер для каждого атома, который у нас был в общей сложности, мы не смогли сохранить номер церкви 200^200
. Это побудило меня задать вопрос, действительно ли мой мак был настолько сильным. Может быть, оптимальный оценщик смог пропустить ненужные ветки и прийти прямо к ответу тем же способом, который Haskell делает с ленивой оценкой. Чтобы проверить это, я скомпилировал λ-программу для 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)
Это правильно выводит 1
(5 ^ 5 % 4
) - но выбросьте что-нибудь выше 10^10
, и оно застрянет, исключив гипотезу.
Оптимизатор который я использовал, представляет собой неоптимизированную программу JavaScript на 160 строк, которая не включала в себя какой-либо математический показатель экспоненциального модуля используемая нами функция модуля лямбда-исчисления была одинаковой:
(λ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))))))
Я не использовал никакого специального модульного арифметического алгоритма или формулы. Итак, как оптимальный оценщик может прийти к правильным ответам?