为什么λ微积分的最佳评估器无需公式就可以计算较大的模幂?


135

教堂数字是自然数作为函数的编码。

(\ 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

整整齐齐地,您只需应用两个教堂数字即可对其求幂。也就是说,如果您将4应用于2,则会得到教堂编号162^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 == 350^50%13 == 460^60%3 == 0。我不得不使用外部工具来验证这些结果,因为Haskell本身无法计算(由于整数溢出)(当然,这是使用Integers而不是Ints的结果!)。将其推到极限,这是对以下问题的答案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)

这样可以正确输出15 ^ 5 % 4)-但将任何内容扔到上面10^10,就会卡住,从而消除了假设。

我使用最佳评估器是一个160行,未经优化的JavaScript程序,其中不包含任何指数模数数学-而且我使用的lambda-calculus模数函数同样简单:

ab.(bcd.(ce.(dfg.(f(efg)))e))))(λc.(cde.e)))(λc.(a(bdef.(dg.(egf))))(λd.d)(λde.(ed)))(bde.d)(λd.d)(λd.d))))))

我没有使用特定的模块化算术算法或公式。那么,最佳评估者如何才能得出正确的答案?


2
您能告诉我们更多关于您使用的最佳评估类型的信息吗?也许是论文引文?谢谢!
杰森·达吉特

11
我正在使用Lamping的抽象算法,如《函数式编程语言的最佳实现》一书中所述。注意,我没有使用“ oracle”(没有羊角面包/括号),因为该术语是EAL型的。另外,我没有顺序并行地减少风扇,而是依次遍历该图以不减少不可达的节点,但是恐怕这不在文献AFAIK上了
。– MaiaVictor

7
好吧,万一有人好奇,我已经为我的最佳评估者建立了一个带有源代码的GitHub存储库。它有很多注释,您可以对其进行测试node test.js。如果您有任何问题,请告诉我。
MaiaVictor

1
干净利落!我对最优评估还不够了解,但是我可以说这让我想起了费马的小定理/欧拉定理。如果您不知道,可能是一个很好的起点。
luqui 2015年

5
这是我第一次完全不知道问题的全部内容,但是仍然支持该问题,尤其是出色的第一答案。
Marco13

Answers:


124

这种现象来自共享的减少beta的步骤数量,这在Haskell风格的惰性评估(或通常的按值致电,在这方面相差不大)和Vuillemin-Lévy-Lamping- Kathail-Asperti-Guerrini(等)“最佳”评估。这是一个常规功能,它完全独立于此特定示例中可以使用的算术公式。

共享意味着代表您的lambda项,其中一个“节点”可以描述您代表的实际lambda项的几个相似部分。例如,您可以代表该词

\x. x ((\y.y)a) ((\y.y)a)

使用(有向无环)图,其中只有一个子图表示(\y.y)a,并且有两个针对该子图的边。用Haskell的术语来说,您有一个thunk,您只评估一次,还有两个指向该thunk的指针。

Haskell风格的备忘录可实现完整子项的共享。共享水平可以通过有向无环图表示。最佳共享没有此限制:它还可以共享“部分”子项,这可能意味着图形表示中存在循环。

要了解这两个共享级别之间的区别,请考虑一下

\x. (\z.z) ((\z.z) x)

如果您的共享仅限于完整的子项(如Haskell中的情况),则您可能只有一次出现\z.z,但是此处的两个beta-redex将是不同的:一个是(\z.z) x,另一个是(\z.z) ((\z.z) x),并且因为它们不是相等的术语它们无法共享。如果允许共享部分项,那么就可以共享部分项(\z.z) [](不仅是函数\z.z,而且是“ \z.z应用于某物的函数” ),无论此参数是什么,它都会一步估算出某物。您可以有一个图,其中只有一个节点代表以下两个应用\z.z到两个不同的参数,并且只需一步就可以减少这两个应用程序。请注意,此节点上存在一个循环,因为“第一次出现”的参数正好是“第二次出现”。最后,通过最佳共享,您可以在减少beta的一个步骤(加上一些簿记)中从(表示一个图)\x. (\z.z) ((\z.z) x))到(表示一个图)结果\x.x。这基本上就是您的最佳评估器中发生的事情(图形表示也可以防止空间爆炸)。

对于稍微扩展的解释,您可以查看论文弱最优性和共享的含义(您感兴趣的是简介和第4.1节,以及结尾处的一些书目指针)。

回到您的示例,对Church整数进行运算的算术函数编码是示例中的“知名”地雷,其中最佳评估程序的性能要优于主流语言(在这句话中,众所周知实际上意味着专家都知道这些示例)。有关更多此类示例,请参阅Asperti和Chroboczek撰写的论文《安全操作员:括号永远闭合》(顺便说一下,您会在这里发现有趣的Lambda术语,这些术语不是EAL类型的;因此,我鼓励您采用从这本Asperti / Chroboczek论文开始,看看甲骨文。

就像您自己说的那样,这种编码完全不切实际,但是它们仍然代表理解情况的好方法。最后,让我提出一个进一步研究的挑战:您是否能够找到一个示例,说明对这些所谓不良编码的最佳评估实际上与对合理数据表示的传统评估相提并论?(据我所知,这是一个真正的开放性问题)。


34
那是最不寻常的彻底的第一篇文章。欢迎来到StackOverflow!
dfeuer

2
无所不及。谢谢,欢迎来到社区!
MaiaVictor,2015年

7

这不是一个烦恼,但这是关于您可能会开始寻找的建议。

有一种简单的方法可以在很小的空间内计算模幂,特别是通过重写

(a * x ^ y) % z

(((a * x) % z) * x ^ (y - 1)) % z

如果评估者这样评估,并将累加参数保持为a正常形式,那么您将避免使用过多的空间。如果确实您的评估员最佳的,那么大概不能做更多的工作,因此特别是不能使用比该评估员花费的时间更多的空间。

我不确定真正的最佳评估者是什么,因此恐怕我不能使其更加严格。


4
@Tom所说的@Viclib Fibonacci是一个很好的例子。fib需要天真的指数时间,可以通过简单的记忆/动态编程将其减少为线性。通过计算的第n个矩阵幂,甚至可以是对数(!)时间[[0,1],[1,1]](只要您对每个乘法计数都具有恒定的成本)。

1
如果您敢于近似,那么即使是恒定的时间,也是如此:)
J. Abrahamson

5
@TomEllis为什么只知道如何减少任意Lambda微积分表达式的东西会有什么想法(a * b) % n = ((a % n) * b) % n呢?那肯定是神秘的部分。
雷德·巴顿

2
@ReidBarton当然可以尝试!结果相同。
MaiaVictor

2
@TomEllis和Chi,不过,只是一句话。所有这些都假定传统的递归函数是“天真”的fib实现,但是IMO有另一种更自然的表达方式。这种新表示形式的标准形式只有传统形式的一半大小,而Optlam设法线性地计算了该形式!因此,我认为,就λ微积分而言,这是fib的“天真”定义。我会写一篇博客文章,但我不确定这是否值得...
MaiaVictor 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.