【问题标题】:Is it possible to enumerate computer programs?是否可以枚举计算机程序?
【发布时间】:2013-05-18 03:44:33
【问题描述】:

假设您必须编写一个程序来测试所有程序以寻找完成特定任务的程序。例如,考虑这个 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。这种方法的问题在于它枚举了可能是或不可能是有效程序的字符串。我的问题是:是否可以自己枚举程序?怎么样?

【问题讨论】:

  • 如果您的程序有一个抽象语法树,并列举了 that 的变体,也许它会更接近您的设想?不过,这只会帮助解决语法错误;语义问题可能仍然存在,具体取决于您制作随机发生器的全面程度。
  • 我认为你必须迭代几百万年才能遇到一个有效的程序。也许如果它是由Improbability Drive
  • @Dokkat:我不知道有什么具体的作品,但我的理解是这种自动程序空间搜索是一个相当大的研究领域。它通常在“自动错误修复”的上下文中完成。这是我通过快速 google 找到的一篇论文:cs.unm.edu/~pdevineni/papers/Strawn.pdf
  • @Dokkat:这是一个试图实现这一点的实际研究项目的另一个资源:dijkstra.cs.virginia.edu/genprog
  • 您有兴趣阅读Chaitin's Omega

标签: javascript algorithm language-agnostic genetic-programming


【解决方案1】:

当然可以枚举给定语言中语法上有效的所有程序(在静态类型语言中,即使只有那些类型检查的程序):您可以像您建议的那样简单地枚举所有字符串,尝试将每个字符串输入语言的解析器,然后拒绝那些无法解析的。因此,如果这是您对有效程序的定义,那么是的,这是可能的。

但是,您的程序不一定会输出一个最终返回 42 的函数,即使您将 string 替换为仅返回语法有效程序的函数,这也是不正确的。如果返回的函数包含无限循环,它将永远运行,因此您的程序将永远无法尝试另一个函数。因此,您可能永远无法找到返回 42 的函数。

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

【讨论】:

  • 不过,它可以很容易地用“如果 N 秒过去了就停止”守卫来纠正。
  • @Dokkat 如果您知道在给定系统上可以在不到 N 秒的时间内执行给定任务,它可以。对于只返回 42 的函数来说,这没问题,但我认为这只是一个示例。如果您希望能够生成解决任意问题的函数,那么您将很难想出一个足够大以涵盖所有问题的 N。
【解决方案2】:

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

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

编辑:

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

【讨论】:

  • +1 我试图记住术语“停机问题”,我知道这是相关的。
  • 好的,这一切都是有效的,但我们并没有说到重点:我正在寻找一种低熵的方法来枚举计算机程序。为了更清楚,假设我们想列举短语。我们可以使用我的方法,测试每一个可能的字符串,或者我们可以只测试字典单词的组合,因为短语是由单词组成的,而不是由字母组成的。那会好很多。那么,计算机程序的等价物是什么?
  • @Dokkat:计算机程序的等价物是枚举ASTs
  • 给定程序是否停止对于枚举它们的业务无关紧要。鉴于符号空间是有限的(例如 ascii 字符)并且有效的程序是有限长度的,因此很容易枚举它们。
  • @Boris 不是我的反对意见,而是:5 个答案,其中 4 个回答“是”,您可以枚举一个程序,而 1 个回答说“不可能”。我怀疑你投反对票是因为人们认为你错了。
【解决方案3】:

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

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

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

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


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

但是仍然实际上可以做到这一点!您只需要选择一个顺序来检查所有可能性,以便最终在某个有限的时间内完成所有事情。您有两个无限的“维度”可供搜索;所有程序的枚举,以及每个程序的评估深度。您可以通过执行以下策略来确保涵盖所有基础:

  1. 运行长度不超过 1 的所有程序,最多 1 步
  2. 运行长度不超过 2 的所有程序,最多 2 步
  3. 运行长度不超过 3 的所有程序,最多 3 步
  4. ...

等等。这保证了无论程序的长度和所需的“步骤”数量如何,您最终都会完成它们而无需“首先”完成无限量的工作(只要实际存在具有您想要的结果的程序)。

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

【讨论】:

    【解决方案4】:

    是的,有很多方法可以做到这一点。几个月前,我做了一点experimental project 来做类似的事情,考虑到它在做什么,它的效果相当不错。你给它一个类型和一些要通过的测试,它会搜索一个合适的函数来通过测试。我从来没有投入让它成熟的工作,但你可以看到它确实最终生成了在示例的情况下通过测试的函数。要求类型是此搜索实用性的重要组成部分 - 它大大减少了需要尝试的程序数量。

    在断言什么是可能的和不可能的之前,牢牢掌握这里的理论是很重要的——有很多人会跳到停顿的问题上,告诉你这是不可能的,而事实是相当复杂的比这更细微。

    • 您可以轻松地以给定语言生成所有语法有效的程序。天真地,您可以生成所有字符串并过滤掉解析/类型检查的字符串;但是有better ways
    • 如果语言不完整——例如简单类型的 lambda 演算,甚至像 Agda 这样非常强大的东西,你可以枚举所有生成 42 的程序,给定 any 程序你可以决定“这会生成 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,或者它可能永远不知道 - 你只是不知道。

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

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

    • 您通常无法判断程序是否计算给定函数。您无法手动检查每个输入,因为无穷大无法检查。您无法搜索您的函数做正确事情的证据,因为对于任何可计算函数 f,对于 any 公理系统 A,有是计算 f 的程序,因此 A 没有证据证明它们计算了 f
    • 您无法判断程序是否会针对每个可能的输入给出任何类型的答案。它可能对前 400,000,000 个有效,但在第 400,000,001 个无限循环。
    • 同样,您无法判断两个程序是否计算相同的函数(即相同的输入 -> 相同的输出)。

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

    如果您有兴趣真正做到这一点,请研究遗传算法,并为您尝试的函数设置超时时间和/或使用可判定(非图灵完备)语言。

    【讨论】:

    • 很棒的答案。我花了一些时间阅读它,因为您提供了我也必须阅读的很棒的链接。一些考虑:如果我们正在寻找一种算法来计算某个函数(例如,QuickSort),那么最好的解决方案肯定会运行得很快。所以长时间运行的函数可以被丢弃而不会造成任何损害——事实上,我们并不关心它是否会在一段时间后真正提供正确的结果。所以我认为整个停止问题在这里无关紧要。然后...(继续)
    • 我也有一种感觉,如果我们找到通过某些测试的算法,很可能我们找到了正确的算法来做我们想做的事。也就是说,看看函数式快速排序的描述有多短: qsort = (x)->(h=head(x); concat(qsort(filter(h,x) ))。现在,有多少短字符串可以提供一个正确排序 100 个测试但不通用的程序?
    • @Dokkat,使用字符串长度作为启发式的问题在于它不一定与您的其他要求相关(它是有效的)。
    • @Dokkat,我的计划是使用具有类型系统的语言(可以表达parametericity),以丢弃大量无意义的垃圾程序,并从用户那里获得更多关于约束的指导所需的功能。我计划的另一个组成部分是一些关于如何解构问题的人工指导。例如“对于排序,您可能需要比较和列表连接”(反过来可以从测试中自动生成,或直接编写)
    • 我用线性堆栈机器做了类似的事情,因为这似乎允许用最短的程序进行最复杂的行为。 github.com/gurgeh/CodeSpace
    【解决方案5】:

    假设我正确解释了您的短语“是否可以枚举程序本身?”然后Yes就可以枚举程序了。这本质上是一个遗传编程问题。见:

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

    在这里,程序本身被创建、运行和评估(使用生成的适应度值,其中最佳值 = 42),就像遗传算法一样,所以是的,这将提供您的枚举。此外,经过几代人,您可以让它发展您的程序以产生42

    【讨论】:

      【解决方案6】:

      我建议从javascript的语法开始,例如ANTLR。

      https://github.com/antlr/grammars-v4/blob/master/javascript/javascript/JavaScriptParser.g4

      语法定义了一个类似这个的有向图:

      grammar Exp;
      
      /* This is the entry point of our parser. */
      eval
          :    additionExp
          ;
      
      /* Addition and subtraction have the lowest precedence. */
      additionExp
          :    multiplyExp 
               ( '+' multiplyExp 
               | '-' multiplyExp
               )* 
          ;
      
      /* Multiplication and division have a higher precedence. */
      multiplyExp
          :    atomExp
               ( '*' atomExp 
               | '/' atomExp
               )* 
          ;
      

      使用这种结构,您可以创建一个程序,该程序可以创建所有语法上有效的程序,它们具有不同的深度 1、2、3、4...,并在模拟器中运行这些程序。这些将是语法上有效的程序,尽管许多程序会返回错误(想想被零除或访问未定义的变量)。还有一些你无法证明他们是否完成。但是使用正确定义的语法(如 ANTLR 提供的语法)可以生成尽可能多的语法正确的程序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-04
        • 1970-01-01
        • 2011-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-03
        相关资源
        最近更新 更多