看似不可能的功能程序
- 2007年9月28日
- 马丁·埃斯卡多
-
计算,嘉宾帖子,辅导的
安德烈邀请我写一些令人惊讶的功能程序。第一个程序,由于乌尔里希·伯杰(1990),执行详尽搜索“康托尔空间“二进制无限序列数字。我在末尾加入了参考资料。弱形式的穷举搜索相当于检查是否存在总谓词包含康托空间的所有元素。因此,这相当于康托空间上的通用量化。这可能是吗在有限时间内完成算法?
更强的相当于找到一个谓词成立的示例,如果是这样的示例存在,并说没有其他的。
我会使用语言哈斯克尔,但有可能快速将程序转换为ML或OCaml公司。此处显示的源代码附加为似乎不太可能。hs.
我们可以使用布尔值来表示二进制数字,甚至可以使用整数,但我更喜欢使用不同的类型以避免混淆:
>data位=零|一>推导(等式)
这个衍生子句告诉Haskell找出如何自动决定位的相等性。
对于无限序列的类型,我们可以使用内置类型这里考虑的大多数算法的惰性列表。但是,为了举例说明某些观点,我将采用数学观点把序列看作是定义在自然数上的函数。下一个Haskell定义的版本将有一个内置类型自然数。目前,我将其实现为整数:
>type Natural=整数>类型Cantor=自然->位
操作员(#)需要一点时间x个和a序列一并生成一个新序列x#a(x#a)具有x个作为头部和一作为尾巴(很像内置操作(:)列表):
>(#)::位->康托->康托>x#a=\i->如果i==0,则x其他a(i-1)
注意,符号\i->。。。代表$\lambda i.\dots$。
接下来,我们讨论问题的核心,即执行的功能对康托空间进行穷尽搜索。的规格功能找到这是总数吗第页,其中一个应该有那个吗找到p始终是康托的全部要素空间,而且,如果有一在康托空间中p a=正确,然后a=求p就是这样一个例子一.
>对于某些,forevery::(Cantor->Bool)->Bool>查找::(Cantor->Bool)->Cantor
因为我将有几个找到,我必须选择一个才能编译和运行程序。A类规范选择是第一选择,
>find=find_i
但你被邀请去试验其他的。对于以下内容定义查找_i为了理解,你必须做出上述选择。
功能找到在康托上取一个谓词空格,因此它通常有一个$\lambda$-表达式参数。在以下定义中,这是不必要的,因为(a->pa)=p根据$\eta$规则。但我有为了清晰起见,我们采用了它,这样我们就可以阅读了”查找(\a->p a)“朗读为”查找一这样的话第(a)页“:
>对于某些p=p(查找(\a->pa))>forevery p=not(对于某些(\a->not(pa)))
请注意,函数外汇(通用量化)从函数中获得福瑟姆(存在量化)通过德摩根法律.工作人员对某人来说和查找_i由相互递归定义:
>find_i::(康托->布尔)->康托>find_i p=if for some(\a->p(零#a))>然后是零#find_i(\a->p(零#a))>else一个#find_i(\a->p(一个#a))
算法的直观思想查找(_i)清楚:如果有一个以零开始的例子,然后得出结果从零开始,否则必须从一开始。然后我们使用相同的思想递归地构建尾部。什么可能不是很明显,递归是否最终生成一个数字,因为通过调用间接递归调用对某人来说.数学证明通过归纳进行一致连续的模第页,定义如下。
只有在以下情况下才返回示例可能更自然一个,否则就说没有:
>搜索::(Cantor->Bool)->可能是Cantor>search p=如果要查找某些(\a->pa),那么只需(find(\a->pa))else Nothing
这个也许 吧类型构造函数由Haskell预定义为
data可能a=只是a |没有
类型理论备注:类型也许是一个对应于总和类型$A+1$,其中只调用$1$的元素没有什么以及在哪里只是是插入$A\toA+1美元。
练习:显示两者对某人来说和找到可以是直接从定义搜索假设我们已经定义搜索第一。
常识告诉我们,函数类型没有可判定的平等。实际上,例如函数类型整数->整数由于暂停问题众所周知。然而,常识并非如此总是正确的,事实上,其他一些函数类型也有可判定的等式,例如类型康托->y对于任何类型年有可判定的平等,没有反驳图灵:
>相等::方程y=>(康托尔->y)->(康托尔->y)->布尔>等于f g=forevery(a->f a==g a)
这看起来很奇怪,甚至有点可疑,因为康托空间在有些比整数大。在后续帖子中,我会解释这与Cantor空间是拓扑紧的,但整数不是。
让我们运行一个示例:
>强制::Bit->自然>强制零=0>强制一=1>f,g,h::康托->整数>f a=矫顽力>g a=胁迫(a(胁迫(a4)+11*(胁迫(a 7)))>h a=如果a 7==零>那么如果a 4==0,那么强制(a 4),否则强制(a 11)>else如果a4==One,则强制(a15)else强制(a8)
现在我们调用全球温室气体排放指数口译员:
$ghci似乎不太可能。lhs___ ___ _/ _ / // __(_)//_///_//|GHC Interactive,6.6版,适用于Haskell 98。/ /_/ __ / /___| | http://www.haskell.org/ghc/____///__/___/|_|类型:?寻求帮助。正在加载包基。。。正在链接。。。完成。[1 of 1]编译主目录(似乎很难理解)好的,加载的模块:主。*主菜单>
此时,我们可以在解释器的提示下计算表达式。首先,我要求它在每次评估后打印时间和空间使用情况:
*主>:集合+秒
在运行1.73GHz的Dell 410笔记本电脑上,我测试了以下内容表达:
*主>等于f gFalse(错误)(0.10秒,3490296字节)*主>等于f h真的(0.87秒,36048844字节)*主>等于g hFalse(错误)(0.09秒,3494064字节)*主>等于f f真的(0.91秒,38642544字节)*主要>等于g g真的(0.15秒,6127796字节)*主>等于h h真的(0.83秒,32787372字节)
通过改变找到,我来做这个速度更快,也可以运行更大的示例。但让我们暂时继续当前的实施。
以下是伯杰考虑上述结构:
>模数::(康托->整数)->自然>模量f=最小值(\n->forevery(a->forevely(b->eq n a b-->(f a==f b)))
这有时被称为风扇功能正常然后回到Brouwer(20世纪20年代),这在更高类型的可计算性理论中是众所周知的社区(见下文诺曼(2006))。它会找到均匀连续模,定义为最不自然编号$n$,这样
$\对于所有\alpha,\beta(\alpha=_n\beta\ to f(\alfa)=f(\beta)$
哪里
对于所有i,$\alpha=_n\beta\iff\。\alpha_i=\beta_i$
这里发生的是可计算函数是连续的,这相当于说,有限数量的输出只取决于有限数量的输入。但康托空间很紧凑在分析和拓扑学中,有一个定理说在紧空间上定义的函数是均匀地连续。在这种情况下,这相当于存在一个$n$,这样对于所有输入,只需查看深度$n$即可获得答案(在这种情况下总是有限的,因为它是一个整数)。我会在另一篇文章中解释这一切。我会的通过运行一些示例中的程序来说明这一点。
注意,Haskell定义与数学定义相同第一,只要我们定义所有其他需要的成分:
>最小值:(自然->布尔)->自然>最小p=如果p为0,则0为1+最小值(\n->p(n+1))>(-->)::布尔->布尔->布尔>p-->q=非p||q>eq::自然->康托尔->康托尔->布尔>eq 0 a b=真>eq(n+1)a b=a n==b n&&eq n a b
为了理解实际中的模量函数,定义投影如下:
>项目::自然->(康托->整数)>项目i=\a->胁迫(ai)
然后我们得到:
*主>模量(a->45000)0(0.00秒,0字节)*主要>模数(项目1)2(0.00秒,0字节)*主要>模数(项目2)三(0.01秒,0字节)*主>模量(项目3)4(0.05秒,820144字节)*主要>模数(项目4)5(0.30秒,5173540字节)*主要>模数(项目5)6(1.69秒,31112400字节)*主要>模数(项目6)7(9.24秒,171456820字节)
因此,直观地说,模量是输入的最后一个指数函数使用加号。对于像上面这样的常量函数模量为零,因为没有使用指数。
技术备注.均匀模量的概念证明终止所需的连续性查找_i与上述内容不完全相同,只是略有不同(有时称为紧张的均匀模量连续性,而我们的被称为伸展的一)。但我不会去研究这样的数学这里有一些微妙之处。主要思想是,当模数为$0$时递归终止于查找_i然后开始一个新的递归生成示例的下一个数字。当第页是$n+1$,谓词的模\a->p(零#a)小于等于$n$,因此总是进行递归调用模量较小,因此最终终止。的结尾备注。
现在我将尝试更快地实现找到.我将分几个阶段修改原始实现。首先,我将通过扩展函数的定义对某人来说在定义中函数查找_i:
>find_ii p=如果p(零#find_ii(\a->p(零#a)))>然后是零#find_ii(\a->p(零#a))>else一个#find_ii(\a->p(一个#a))
这应该有基本相同的速度。现在注意,如果我们使零和一成为参数小时因此,可以“考虑out”条件如下:
>find_iii::(康托尔->布尔)->康托尔>find_iii p=h#find_iid(\a->p(h#a))>其中h=如果p(Zero#find_iii(\a->p(Zero#a))),则其他为零
对于某些示例,这(以指数形式)更快。一条线索这就是那个小时只有当它是“用过”(我们的语言很懒)。让我们运行一个示例,替换上述定义找到通过find=查找iii:
*主>等于f h真的(0.00秒,522668字节)*主>相等(项目1000)真的(0.00秒,0字节)*主>相等(项目1000)(项目4000)False(错误)(0.03秒,1422680字节)*主>相等(项目1000)(项目(2^20))False(错误)(7.02秒,336290704字节)
正如你所见,我们尝试的投影函数越大,比较时间越长。要查看第一个算法有多糟糕,让我们切换回find=find_i:
*主>相等(项目10)真的(0.05秒,1529036字节)*主要>相等(项目10)(项目15)False(错误)(1.61秒,72659036字节)*主>相等(项目10)(项目20)False(错误)(60.62秒,2780497676字节)
除非我们有可用比特数超过可观测宇宙中的原子数我们愿意等待数十亿年,因为算法的连续模是指数的。
你可能注意到还有一个明显的改进从开始图dii:
>find_iv::(康托->布尔)->康托>find_iv p=let leftbranch=零#find_if(\a->p(零#a))>in if p(左分支)>然后左分支>else一个#find_iv(\a->p(一个#a))
实际上,我从来没有想过这个算法的性能或进行了试验。让我们看看我们得到了什么(您需要替换find=查找_iv):
*主>相等(项目10)(项目20)False(错误)(0.00秒,522120字节)*主>相等(项目10)(项目200)False(错误)(0.04秒,1550824字节)*主要>相等(项目10)(项目2000)False(错误)(3.71秒,146039744字节)*主要>相等(项目10)(项目20000)打断。
比查找_i,但比图iii! 我在上一个例子中放弃了,因为大约一分钟后,我开始放慢这篇文章的速度。
但有一个更好的算法,我现在介绍。我不会的尝试在本文中解释此算法的工作原理(请参阅如果你真的感兴趣的话,我的LICS 2007论文如下),但我包括下面是几句话:
>find_v::(康托->布尔)->康托>find_v p=\n->如果q n(find_v(q n)),则其他为零>其中q n a=p(\i->如果i<n,则find_v p i else如果i==n,则归零else a(i-n-1))
除此之外,上述所有算法都可以很容易地重写为使用惰性列表,而不是在自然数字。此算法利用了以下事实:表示为函数的序列的元素,它不是必需的扫描前面的所有元素。这可能是一种神秘的方式算法隐式计算出其参数的条目第页使用,并且只显式构造这些。您可以访问如果你愿意的话,其他的,但是算法查找_v不强制他们进行评估。有一种方法可以看出这一点查找_v是正确的就是通过归纳n个,那个findipn=findvpn,虽然计算量很大,但并不太难在某些阶段,如果不小心引入合适的辅助设备符号。更好的方法是直接理解这一点,就像在上面的文件(您需要查找产品功能概括这一点)。
现在这真的很快find=查找_v):
*主>相等(项目(2^300))(项目(2%300))真的(0.00秒,522148字节)*主>相等(项目(2^300))(项目(4^400))False(错误)(0.00秒,525064字节)
但如果函数使用了多个参数,而不仅仅是一个参数(请参见下面的示例),这不再那么好了。要解决这个问题,首先按如下所示重写上述程序,引入一个辅助程序变量b命名结果,并替换其中一个要使用的递归调用(有两个)b而是:
>find_vi::(康托->布尔)->康托>find_vi p=b>其中b=\n->如果q n(find_vi(q n)),则其他为零>q n a=p(如果i<n,则b i else如果i==n,则0 else a(i-n-1))
懒惰的评估在这里没有帮助,因为b是一个函数,实际上这会使程序稍微慢一些。现在,为了显著加快速度,我们将标识函数应用于定义b或者更确切地说是精心制作的标识函数的实现,该函数存储b以宽度第一的方式生成无限二叉树,然后将其取回(此技巧使用对数开销):
>find_vii::(康托->布尔)->康托>find_vii p=b>其中b=id’(\n->如果q n(find_vii(q n)),则归零其他1)>q n a=p(\i->如果i<n,则b i else如果i==n,则零else a(i-n-1))>数据T x=B x(T x)>代码:(天然->x)->T x>代码f=B(f 0)(代码(\n->f(2*n+1)))>(代码(\n->f(2*n+2))>解码::T x->(自然->x)>解码(B x l r)n | n==0=x>|奇数n=解码l((n-1)`div`2)>|否则=解码r((n-2)`div`2)>id’::(自然->x)->(自然->x)>id’=解码代码
现在接受find=查找_vii,并测试:
>f',g',h'::康托->整数>f'a=a'(10*a'(3^80)+100*a'>g'a=a'(10*a'(3^80)+100*a'(4^80)+1000*a'(6^80))其中a'i=胁迫(ai)>h’a=a’k>其中i=如果a'(5^80)==0,则0,否则1000>j=如果a'(3^80)==1,则10+i其他i>k=如果a'(4^80)==0,则j为100+j>a'i=矫顽力(ai)*主>等于f“g”False(错误)(6.75秒,814435692字节)*主>等于f“h”真的(3.20秒,383266912字节)*主>等于g'h'False(错误)(6.79秒,813083216字节)*主>等于f“f”真的(3.20秒,383384908字节)*主>等于g‘g’真的(3.31秒,400711084字节)*主>等于h'h'真的(3.22秒,383274252字节)
在所有上述算法中,只有发现_vii能应付用上面的例子。一个更有趣的例子是。两个自然数的有限序列$s$和$t$具有相同的设置元素的iff为两个函数
$\bigwedge_{i<|s|}\mathrm{项目}_{si}$和$\bigwedge_{i<|t|}\马特姆{项目}_{ti}$
相等,其中上面的符号表示逐点投影的逻辑和(连接),其中$|s|$是长度为$s$。下面是它的一个实现想法:
>pointwiseand::[自然]->(Cantor->Bool)>逐点和[]=\b->真>逐点与(n:a)=\b->(b n==一)&&逐点与a b>相同元素::[自然]->[自然]->布尔>相同的元素a b=相等(点和a)(点和b)*主>相同元素[6^60, 5^50, 1, 5, 6, 6, 8, 9, 3, 4, 6, 2, 4,6, 1, 6^60, 7^700, 1, 1, 1, 3, 3^30][1, 2, 3, 4, 5, 6, 7, 8, 9, 3^30, 5^50, 6^60, 7^70]False(错误)(0.14秒,21716248字节)*主>相同元素[6^60, 5^50, 1, 5, 6, 6, 8, 9, 3, 4, 6, 2, 4,6, 1, 6^60, 7^70, 1, 1, 1, 3, 3^30][1, 2, 3, 4, 5, 6, 7, 8, 9, 3^30, 5^50, 6^60, 7^70]False(错误)(0.10秒,14093520字节)*主要>相同元素[6^60, 5^50, 1, 5, 6, 6, 8, 9, 3, 4, 6, 2, 4,6, 1, 6^60, 7^70, 1, 1, 1, 3, 3^30][1, 2, 3, 4, 5, 6, 8, 9, 3^30, 5^50, 6^60, 7^70]真的(0.10秒,12610056字节)*主>相同元素[6^60, 5^50, 1, 5, 6, 6, 8, 9, 3, 4, 6, 2, 4,6, 1, 6^60, 7^70, 1, 1, 1, 3, 3^30][1, 2, 3, 4, 5, 6, 8, 9, 3^30, 5^50, 6^60, 7^700]False(错误)(0.12秒,17130684字节)*主>相同元素[6^60, 5^50, 1, 5, 6, 6, 8, 9, 3, 4, 6, 2, 4,6, 1, 6^60, 7^700, 1, 1, 1, 3, 3^30][1, 2, 3, 4, 5, 6, 8, 9, 3^30, 5^50, 6^60, 7^700]真的(0.12秒,17604776字节)
询问是否有要编程的应用程序是很自然的验证。我不知道,但是丹·吉卡我推测有,我们正计划对此进行调查。
下面的第一条评论提供了一种更快的搜索算法。
- 乌尔里希·伯杰.Bereichtheorie中的总目标和门根.博士论文,慕尼黑LMU,1990年。
- M.H.埃斯卡多.允许快速穷举的无限集搜索2007年LICS,波兰,弗罗茨瓦夫,7月。下载配套Haskell程序本文研究了哪些类型的无限集允许穷举搜索。它给出了系统构建新的旧的可搜索集合。它还表明,对于丰富的类型,任何允许使用量词的子集也允许使用搜索者。这个从量词构造搜索器的算法很慢,因此,目前这一结果只具有理论意义。但是其他算法速度很快。
- M.H.埃斯卡多。数据类型和经典空间ENTCS,Elsevier,第87卷,第21-156页,11月2004年。我现在将此称为程序类型的算法拓扑,而不是数据类型的合成拓扑,以及在未来的写作中这就是我将要提到的。它展示了一般拓扑可以直接映射到编程语言(I再次使用Haskell)。但它也显示了如何将其映射回经典拓扑。详尽的搜索集功能如下紧集的计算表现。
- M.H.Escardo和W.K.Ho。操作域理论顺序编程语言的拓扑结构.2005年6月,第20届IEEE计算机科学逻辑研讨会(LICS)论文集,427-436页。如果你想了解域理论和拓扑的使用对于程序推理来说,这是一个可能的起点。而不是使用领域理论和拓扑,我们直接从的操作语义中提取它们语言(PCF,可以被视为Haskell),并列指称语义。关于领域理论和指称语义学在这里作为定理出现。那里是伯杰计划的证明,包括证明所需的一致连续模。
- 达格·诺曼.泛函计算——可计算性理论还是计算机科学?《符号逻辑公报》,12(1):43-592006。这是一篇关于更高类型可计算性的简短历史和调查理论(该学科是在20世纪50年代末由Kleene和Kreisel,但有一些前体)。这个理论不太好在函数式程序员中是众所周知的,但可能应该是至少在那些有理论倾向的人中。总有一天会有人应该给计算机科学家写一份解释性的报告。
- 亚历克斯·辛普森.精确实泛函的惰性泛函算法.英寸1998年计算机科学数学基础,Springer LNCS 1450,第456-4641998页。人们可以使用无限序列来准确地表示实数。使用通用量化函数,Alex Simpson开发了黎曼积分的一种算法。算法结果是效率低下,但这不是通用量词的错:Haskell分析器告诉我,集成程序执行关于每秒10000次通用量化,其中3000次返回真的.