【发布时间】:2015-10-20 20:33:59
【问题描述】:
教会数字是自然数作为函数的编码。
(\ 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
随着我的“错误警报”闪烁,我去谷歌验证,10^10%13 == 3 确实如此。 但是 λ 计算器不应该找到那个结果,它几乎不能存储 10^10。我开始强调它,为了科学。它立即回复了我20^20%13 == 3、50^50%13 == 4、60^60%3 == 0。我不得不使用external tools 来验证这些结果,因为Haskell 本身无法计算它(由于整数溢出)(如果你使用整数而不是整数,当然!)。将其推向极限,这是200^200%31的答案:
5
{ iterations: 10351327, applications: 5175644, used_memory: 23754870 }
real 0m4.025s
user 0m3.686s
sys 0m0.341s
如果我们为宇宙中的每个原子拥有一个宇宙副本,并且我们为每个原子拥有一台计算机,我们就无法存储教会编号200^200。这促使我质疑我的 Mac 是否真的那么强大。也许最佳评估器能够跳过不必要的分支并以与 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 上方抛出任何东西都会卡住,从而消除假设。
optimal evaluator I used 是一个 160 行长、未优化的 JavaScript 程序,它不包含任何类型的指数模数学 - 而我使用的 lambda 演算模函数同样简单:
(λ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))))))
我没有使用特定的模运算算法或公式。 那么,最佳评估者如何能够得出正确的答案?
【问题讨论】:
-
您能告诉我们更多关于您使用的最佳评估类型的信息吗?也许是论文引用?谢谢!
-
我正在使用 Lamping 的抽象算法,正如 The Optimal Implementation of Functional Programming Languages 书中所解释的那样。请注意,我没有使用“oracle”(没有羊角面包/括号),因为该术语是 EAL 类型的。此外,我不是在并行随机减少粉丝,而是按顺序遍历图表以不减少无法访问的节点,但恐怕这不是文献 AFAIK...
-
好的,如果有人好奇,我已经设置了一个GitHub repository,其中包含我的最佳评估器的源代码。它有许多 cmets,您可以运行
node test.js对其进行测试。如果您有任何问题,请告诉我。 -
很好找!我对最优评估知之甚少,但我可以说这让我想起了费马小定理/欧拉定理。如果您不知道,这可能是一个很好的起点。
-
这是我第一次完全不知道这个问题是关于什么的,但仍然支持这个问题,尤其是出色的首发回答。
标签: algorithm haskell functional-programming lambda-calculus modular-arithmetic