是否可以枚举计算机程序?

时间:2013-05-22 22:43:38

标签: javascript algorithm language-agnostic genetic-programming

假设您必须编写一个程序来测试所有程序以搜索完成特定任务的程序。例如,请考虑以下JavaScript函数:

function find_truth(){
    for(n=0;;++n){
        try {
            var fn = Function(string(n));
            if (fn() == 42)
                return fn;
        } catch() {
            continue;
        }
    }
}

只要string(n)返回第n个字符串(“a”,“b”,“c”,......“aa”,“ab”......),该程序最终会输出一个函数评估为42。此方法的问题在于它枚举的字符串可能是也可能不是有效的程序。我的问题是:是否可以自己枚举程序?怎么样?

5 个答案:

答案 0 :(得分:29)

是的,有可能这样做。几个月前,我做了一点experimental project来做类似的事情,考虑到它正在做的事情,它运作得相当好。你给它一个类型和一些测试来传递,它搜索一个合适的函数来传递测试。我从来没有投入使其成熟,但你可以看到它实际上最终生成了在示例的情况下通过测试的函数。要求这种类型是这种搜索实用性的重要组成部分 - 它大大减少了需要尝试的程序数量。

在断言关于什么是不可能的事情之前,牢牢掌握这个理论是非常重要的 - 有许多人会跳到停止问题并且告诉你这是不可能的,当真相是相当的时候比那更微妙。

  • 您可以轻松生成给定语言的所有语法上有效的程序。天真地,您可以生成所有字符串并过滤掉解析/类型检查的字符串;但有better ways
  • 如果语言不完整 - 例如简单类型的lambda演算,或者甚至像Agda这样非常强大的东西,你可以枚举生成42的所有程序,并且给定任何程序你可以决定“这生成42”或“这不生成42 ”。 (我在实验项目中使用的语言在本课程中)。这里的张力是,在任何这样的语言中,都会有一些你无法编写的可计算函数。
  • 即使语言完成,您仍然可以枚举最终生成42的所有程序(通过并行运行它们来完成此操作,并在完成时报告成功)。

你不能用图灵完整的语言来决定一个程序肯定不会产生42 - 它可以永远地运行,并且你无法判断它是否会最终成功直到它完成。有一些一些程序,你可以告诉它们是无限循环,但是,并不是全部。因此,如果你有一个程序表,你可能希望你的枚举器程序在非图灵完整语言的情况下是这样的:

Program |  P1  |  P2  |  P3  |  P4  |  P5  |  P6  |  P7  | ...
42?     | No   | No   | No   | Yes  | No   | No   | No   | ...

在图灵完整语言中,您的输出(在给定时间)将是这样的:

Program |  P1  |  P2  |  P3  |  P4  |  P5  |  P6    |  P7  | ...
42?     | No   | No   | Loop | Yes  | No   | Dunno  | No   | ...

后来Dunno可能变成了Yes或No,或者它可能会永远留下dunno - 你只是不知道。

这一切都是非常理论化的,实际上用图灵完整的语言生成程序来搜索那些做某件事情的程序非常困难而且需要很长时间。当然你不想一个接一个地做,因为空间太大了;您可能想要使用遗传搜索算法或其他东西。

理论中另一个微妙的观点:谈论“生成42”的程序缺少一些重要的细节。通常当我们谈论生成程序时,我们希望它生成某个函数,而不仅仅是输出特定的东西。这就是你想要做的事情变得更加不可能的时候。如果你有无限的可能输入空间 - 比如输入一个数字,那么

  • 您通常无法判断程序是否计算给定的函数。您无法手动检查每个输入,因为无穷大太多而无法检查。你不能搜索你的函数做正确的事情的证明,因为对于任何可计算的函数 f ,对于任何公理系统 A ,那里是计算 f 的程序,使 A 无法证明他们计算 f
  • 你不知道一个程序是否会为每个可能的输入提供任何答案。它可能适用于前400,000,000个,但在400,000,001个上无限循环。
  • 同样,你无法判断两个程序是否计算相同的功能(即相同的输入 - >相同的输出)。

你有它,每日剂量的可判定性理论现象学。

如果您有兴趣真正做到这一点,请查看遗传算法,并对您尝试的功能和/或使用可判定的(非图灵完备)语言进行超时。

答案 1 :(得分:4)

当然可以枚举给定语言中语法有效的所有程序(以及静态类型语言,甚至只有那些类型检查的程序):你可以简单地枚举你提出的所有字符串,尝试将每一个字符串输入到语言的解析器,然后拒绝那些无法解析的语言。所以如果这是你对有效程序的定义,那么是的,这是可能的。

然而,您的程序必然会输出最终返回42的函数 - 即使您使用仅返回语法上有效的程序的函数替换string也是如此。如果返回的函数包含无限循环,它将永远运行,因此您的程序将永远不会尝试另一个函数。因此,您可能永远不会到达返回42的函数。

为了避免这个问题,您可能会说string(n)函数应该只生成语法上有效的代码并且不包含无限循环(同时仍然能够返回所有这样的代码)功能)。然而,这是不可能的,因为这将导致决定停机问题(当然,这是不可判定的)。

答案 2 :(得分:4)

正如已经指出的那样,通过插入语言X的解析器/编译器,您可以轻而易举地将“生成所有字符串”程序转换为“生成语言X中的所有有效程序”。通常,如果您可以编写一个程序, text作为输入并返回true / false,指示文本是否代表有效程序,然后您可以将其用作“生成所有字符串”程序的过滤器。

您还可以设计一种编程语言,其中每个字符串都是有效的程序(perl会让人想起)。

可能更有趣的是,给定语言的正式语法,您可以使用它来语言生成程序而不是解析它们。你只需要对语法进行广度优先遍历,以确保在某个有限的时间内达到每个有限长度的程序(如果你进行深度优先遍历,你将会探索所有仅由一个程序组成的程序。变量,其名称是'A'或其他东西的更长序列。

实际上用于解析编程语言的大多数语法都不能直接用于此;他们通常略微过度宽容。例如,语法可以将标识符定义为与正则表达式[_A-Za-z][0-9_A-Za-z]*匹配的任何内容,它允许无限长度的变量名称,但是许多语言实现实际上会阻塞具有千兆字节长度的变量名称的程序。但是你原则上可以找到所有这些问题并编写一个可枚举的语法,它完全覆盖了某些感兴趣的语言中的所有有效程序。


这样可以枚举所有程序。这实际上不足以让你运行你的find_truth程序并找到一个返回42的程序,因为它会无限期地评估碰巧包含无限循环的第一个程序。

仍然实际上可以做到这一点!您只需要选择一个订单来检查所有可能性,以便最终在某个有限的时间内完成所有操作。你有两个无限的“维度”可供搜索;所有计划的列举,以及每个计划的评估深度。您可以通过执行以下策略来确保覆盖所有基础:

  1. 运行长度最多为1的所有程序,最多1步
  2. 运行长度最多为2的所有程序,最多2步
  3. 运行长度最多为3的所有程序,最多3个步骤
  4. ...
  5. 等等。这保证了无论程序的长度和所需的“步骤”的数量,你最终都会击中它们而不需要“先”完成无限量的工作(只要具有所需结果的程序实际存在)。

    如果你有无限存储可用,你可以避免在这些阶段之间重复工作(你存储所有长度为N但没有完成N步骤的程序,以及它们的状态,然后在下一轮你运行程序,最多N + 1步,并运行所有“待处理”程序,每个步骤再一步)。 “步骤”的定义并不重要,只要它不承认无限循环。一些有限数量的字节码,或CPU指令,甚至是秒;当然,你需要一种可以暂停的语言实现。

答案 3 :(得分:1)

假设我正确地解释了你的短语“是否有可能自己枚举程序?”然后您可以枚举程序。这基本上是遗传编程问题。见:

http://en.wikipedia.org/wiki/Genetic_programming

这里的程序本身是创建,运行和评估的(带有最终的适应值,最佳值= 42),就像遗传算法一样,所以是的,这将提供你的枚举。此外,经过几代人的发展,您可以让它改进您的程序以生成42

答案 4 :(得分:0)

这是不可能的。问题是某些程序可能需要很长时间才能完成计算。有些程序可能陷入无限循环。理想情况下,您希望中​​止运行那些陷入无限循环的程序,并且只保留长时间运行的程序。你可以实现一个计时器,但是如果你的程序运行时间比计时器长,但仍会返回正确的答案呢?

通常,查看计算机程序是否将终止的唯一方法是运行它并查看,并有进入无限循环的风险。当然,你可以实现一些启发式方法来识别无限循环的常见形式,但总的来说它是不可能的。这称为halting problem

编辑:

我意识到我只能部分回答你的问题。你问是否有可能自己枚举程序。这当然是可能的。您已经有了按顺序生成所有可能字符串的方法。然后你可以看到哪些字符串正确解析为一个javascript程序,并保留它们。