【问题标题】:Homoiconic and "unrestricted" self modifying code + Is lisp really self modifying?Homoiconic 和“不受限制”的自我修改代码 + lisp 真的是自我修改吗?
【发布时间】:2012-01-19 09:49:09
【问题描述】:

我承认我对 Lisp 的了解非常少。但是我对这门语言非常感兴趣,并计划在不久的将来开始认真学习它。我对这些问题的理解无疑是有缺陷的,所以如果我说了什么明显错误的话,请评论和纠正我,而不是投反对票。

真正的同音和自修改语言

我正在寻找同时支持 Homoiconicity(代码与数据具有相同的表示)和无限制自我修改(无限制意味着您可以更改正在运行的代码的各个方面的编程语言示例,不仅仅是发出新代码或更改函数指针/委托。)

到目前为止,我只找到了三个符合此标准的示例:

  1. 机器代码。 Homoiconic 因为一切都是数字。可不受限制地修改,因为它包含指针,可用于操作任何内存地址,无论该地址是保存代码还是数据。
  2. 马尔博格。与机器代码相同的推理。每条指令在执行后都会自我修改
  3. DNA。不是编程语言,但仍然很有趣。它不像机器代码那样自我修改;实际指令+数据被修改到位的地方。然而,它是自我复制的,可以根据之前的状态变异/进化(辐射等副作用时不时地把它搞砸)。无论如何,这只是一种间接的自我修改方式。简而言之,DNA可以自我修改,但它是通过在其整体中复制自身以及相关突变来实现的。物理 DNA 串是“不可变的”。

为什么 Lisp 不在此列表中

Lisp 不在该列表中,因为在我看来 Lisp 只是几乎同音字,并且只支持受限的自我修改。你可以做类似的事情

(+ 1 2 3)

它会做同样的事情

(eval '(+ 1 2 3))

在第一个版本中(+ 1 2 3) 是原始代码,而在第二个版本中它是数据。通过假设这个陈述的真实性,可以说 Lisp 甚至不是 homiconic。代码具有与数据相同的表示形式,因为它们都是列表/树/S 表达式。但是,您必须明确标记这些列表/树/S 表达式中的哪些是代码,哪些是对我来说是数据,这一事实似乎表明 Lisp 毕竟不是 homiconic。这些表示非常相似,但它们的细微差别在于您必须实际说明您是在处理代码还是数据。这绝不是一件坏事(事实上其他任何事情都是疯狂的),但它突出了 Lisp 和机器代码之间的区别。在机器代码中,您不必明确标记哪些数字是指令,哪些是指针,哪些是数据。在实际需要解释之前,一切都只是一个数字,此时它可能是这些东西中的任何一个。

这是一个反对不受限制的自我修改的更强有力的案例。当然,您可以获取代表一些代码的列表并对其进行操作。比如改变

'(+ 1 2 3)

'(+ 1 4 3)

然后你通过eval 运行它。但是当你这样做时,你只是在编译一些代码并运行它。您不是在修改现有代码,您只是在发布和运行新代码。 C# 可以使用表达式树做完全相同的事情,即使格式不太方便(这是由于 C# 代码对其 AST 具有不同的表示形式,而不是 Lisp,它 它自己的 AST )。你真的可以获取一个完整的源文件并在它运行时开始修改整个源文件,对源文件所做的更改会对程序行为产生实时影响吗?

除非有某种方法可以做到这一点,否则 Lisp 既不是 homiconic 也不是自我修改的。 (为了推迟对定义的争论,Lisp 不像机器代码那样同音或自我修改

使 Lisp Homoiconic/无限制地自我修改的方法

我可以看到 3 种潜在的方法来使 Lisp 像机器代码一样具有同义性/可自我修改。

  1. 非冯诺依曼架构。 如果有人可以发明一些惊人的假设机器,其中程序的最低级别表示是可以直接执行的 AST(无需进一步编译)。在这样的机器上,AST 既代表可执行指令,也代表数据。不幸的是,问题还没有解决,因为 AST 仍然必须是代码或数据。 eval 函数的出现不会改变这一点。在机器代码中,您可以根据需要在代码和数据之间来回切换。而使用 eval 和 Lisp 后,一旦您将某个列表从数据“评估”到代码并执行它,就无法再次将该列表作为数据取回。实际上,该列表已永远消失,并已被其价值所取代。我们会遗漏一些重要的东西,而这恰好是指针。
  2. 列表标签。如果要求每个列表也有一个唯一的标签,则可以通过对具有给定标签的列表运行函数来进行间接的自我修改。结合延续,这最终将允许在与机器代码相同的意义上自我修改代码。标签等同于机器代码内存地址。例如,考虑一个 Lisp 程序,其中 AST 的顶部节点具有标签“main”。然后,在 main 中,您可以执行一个函数,该函数接受一个标签、一个整数、一个原子,并将原子复制到列表中,其标签与提供给函数的标签相匹配,位于整数指定的索引处。然后只需在 main 上调用当前的继续。好了,自我修改代码。
  3. Lisp 宏。我没有花时间去了解 Lisp 宏,实际上它们可能完全符合我的想法。

第 1 点与第 2 点相结合将产生一个完全自我修改的 Lisp。前提是可以生产所描述的神奇的 Lisp 机器。 2. 单独可以产生一个自我修改的 Lisp,但是在冯诺依曼架构上的实现可能非常低效。

问题

  1. 除了机器码、dna 和 malbolge 之外,还有什么语言可以进行完全自我修改并且是同形的?
  2. (如果您对上述文字做了 tl;dr,请不要费心回答)。 lisp 真的是谐音+自我修改吗?如果您这么说,您能否准确引用我在论证中误入歧途的地方?

附录

具有不受限制的自我修饰但没有同源性的语言

  1. 组装。该代码使用单词而不是数字,因此失去了同义性,但它仍然具有指针,它保留了对内存的完全控制并允许不受限制的自我修改。
  2. 任何使用原始指针的语言。例如 C/C++/Objective C。与 Assembly 相同的参数
  3. 包含虚拟指针的 JIT 语言。例如在不安全的上下文中运行的 C#/.net。与 Assembly 的参数相同。

其他可能相关/有趣的概念和语言: Lisp、Ruby、Snobol、Forth 和它的编译时元编程、Smalltalk 和它的反射、无类型的 lambda 演算,它的属性是一切都是函数(这意味着假设我们可以发明一台直接执行 lambda 演算的机器,lambda 演算将是同形的,而冯诺依曼机器代码在所述机器上运行时不会。[并且哥德尔定理将是可执行的。哈哈,可怕的想法:P])

【问题讨论】:

  • 参见stackoverflow.com/a/6480923/271324 以获取 Emacs lisp 中的示例。
  • 我不知道这是否符合您的期望,但您可能会发现 Kernel 是一种非常有趣的编程语言。
  • quote 形式没什么大不了的,因为它可以通过一些不同且更简单的评估规则派生出来,即所谓的“显式”评估样式(内核语言的基础之一)上面提到的),请参阅[Shu10]。自修改属性也无关紧要,但一个简单的事实是eval(显式或隐式样式)不区分要评估的操作数的身份,因此就地修改和复制+之间应该没有明显的区别逐个擦除。

标签: assembly lisp machine-code self-modifying homoiconicity


【解决方案1】:

我在这里遇到了一个粉丝,我之前说过,但请阅读 Paul Graham 的 On Lisp 如果你想了解 Lisp 宏。在启用原本不可行的修改方面,它们是一件大事。另外,我认为这里区分 Lisp 语言家族、给定的 Lisp 和给定的 Lisp 实现是很重要的。

我想我对你的论点的主要问题出现在“为什么 Lisp 不在此列表中”之后的第一段中,并且与 Lisp 中的 REPL 有关。当您在解释器中输入 exp (+ 1 2 3) 时,实际上是在调用列表 (+ 1 2 3) 上的函数 EVAL。您描述的“原始代码”实际上是“数据”,被提供给其他 lisp 代码,它只是特定上下文中的数据。

【讨论】:

    【解决方案2】:

    宏可以避免引用,如果这是您所追求的:

    > (defmacro foo (x) (cdr x))
    > (foo (+ - 5 2))
    3
    

    (+ - 5 2) 是代码还是数据?在写入时,它看起来像数据。在宏扩展时间之后,它看起来像代码。如果foo 的定义在其他地方,那么它很容易(错误地)被认为是一个函数,在这种情况下,(+ - 5 2) 将被认为是行为类似于数据的代码,其行为类似于代码。

    【讨论】:

      【解决方案3】:

      Lisp 是一个编程语言家族。这个家族的成员在他们的能力和实现技术上有很大的不同。对此有两种不同的解释:

      • Lisp 是一个语言家族,它们共享一些重叠的功能集。这包括从第一个 Lisp 到 Maclisp、Scheme、Logo、Common Lisp、Clojure 以及数百种不同语言及其实现的所有内容。

      • Lisp 也有一个主要的语言分支,其名称中也大多包含“Lisp”:Lisp、MacLisp、Common Lisp、Emacs Lisp、...

      随着时间的推移,已经投入了大量研究来改进语言(使其更“功能化”或使其更面向对象,或两者兼而有之)并改进实现技术。

      例如,Common Lisp 支持编译、各种优化等,以允许开发人员在需要在灵活性和功能之间取得某种平衡的大型项目中使用它。编译后的函数是机器代码,不再是由列表、符号、字符串和数字组成的数据结构。

      Common Lisp 允许实现创建静态代码。同时,它为受控的运行时修改留出了一些空间(例如,通过使用运行时编译器、加载代码、评估代码、替换代码……)。

      OTOH 如果您有一个带有解释器的 Lisp 实现,并且解释器可能使用 Lisp 数据结构来表示源代码,那么您可以更改正在运行的程序,例如通过更改列表结构。有一些 Lisp 方言的实现允许这样做。一个典型的事情是使用宏,它可以由这样的系统在运行时计算。其他一些 Lisp 有所谓的 Fexprs,这是一种类似的机制(但通常无法有效编译)。

      在基于 Common Lisp 的应用程序(例如,用 Lisp 编写的 CAD 系统)中,大部分源信息已被交付工具删除,这是不可能的。有一个机器代码可执行文件,其中大部分运行时灵活性被删除。

      同音性也不是一个很好定义的概念。对于 Lisp,我更喜欢我们说源代码可以变成数据,因为源代码使用了 s-expressions 的外部表示,这是一种数据序列化格式。但并不是每个 s-expression 都是有效的 Lisp 程序。此外,作为 Lisp 数据的源代码的内部表示无论如何都不是“标志性的”。 Lisp 具有作为外部 s 表达式的源代码,在阅读源代码后,它具有作为 Lisp 数据的内部表示。 READ 读取它,PRINT 打印它,然后 EVAL 评估它。

      在 Lisp 中还有其他方法可以提供对运行程序的 Lisp 和程序的访问:

      • CLOS(Common Lisp Object System)中的元对象协议就是这样一个例子

      • 3Lisp 提供了无限的解释器塔。有一个 Lisp 解释器运行你的程序。这个 Lisp 解释器由另一个 Lisp 解释器运行,该解释器又在另一个解释器中运行,而且那个解释器也...

      【讨论】:

      • 第二个 3Lisp。如果您想看到将反射式编程推向极致,3Lisp 会让您大开眼界。
      • 我在谷歌上搜索了有关 3Lisp 的更多信息,但我的搜索几乎没有任何结果,只是确认了它的存在。谁有最新的链接?
      【解决方案4】:

      在第一个版本中(+ 1 2 3) 是原始代码,而在第二个版本中它是数据。通过假设这个陈述的真实性,可以说 Lisp 甚至不是 homiconic。代码具有与数据相同的表示形式,因为它们都是列表/树/S 表达式。但是,你必须明确标记这些列表/树/S-表达式中哪些是代码,哪些是数据,这对我来说似乎说明 Lisp 根本不是 homiconic。

      这不是真的。在第一个版本中,列表(+ 1 2 3)(即数据)被提供给解释器执行,即解释为代码。您必须将 s 表达式标记为代码或数据在特定上下文中这一事实不会使 Lisp 非同音。

      同音点是所有程序都是数据,而不是所有数据都是程序,所以两者还是有区别的。在 Lisp 中,(1 2 3) 是一个有效的列表,但不是一个有效的程序,因为整数不是函数。

      [如果我们看看另一种伟大的同形编程语言Prolog,我们会看到同样的现象:我们可以构建一个数据结构foo(X, 1, bar),但是如果没有foo的定义,我们就无法执行它。此外,变量不能是谓词或事实的名称,因此X. 永远不是有效程序。]

      Lisp 在很大程度上是自我修改的。例如,以下是更改函数定义的方法:

      [1]> (defun foo (x) (+ x 1))
      FOO
      [2]> (defun bar (x) (+ x 2))
      BAR
      [3]> (setf (symbol-function 'foo) #'bar)
      #<FUNCTION BAR (X) (DECLARE (SYSTEM::IN-DEFUN BAR)) (BLOCK BAR (+ X 2))>
      [4]> (foo 3)
      5
      

      解释:在[1],我们将函数foo定义为add-1函数。在[2],我们将bar 定义为add-2 函数。在[3],我们将foo 重置为add-2 函数。在[4],我们看到我们已经成功修改了foo

      【讨论】:

      • “同音点是所有程序都是数据,而不是所有数据都是程序” 之前没有想到。那么我要查找的术语是什么?在机器代码中,它在两个方向上都是同音的。代码->数据和数据->代码。我正在寻找那种语言。顺便说一句,为驳倒我的论点而欢呼:)
      • 一些(很多?全部?)Lisps 还为编译表达式的汇编语言/机器代码/字节码形式(例如(disassemble #'bar))提供了一个 s 表达式接口,但我已经从来没有(勇敢?绝望?)足以尝试以这种形式改变它。我认为,即使没有该功能,同音性也与 AST 相关,而不是与机器相关的编译形式。
      • Lisp 与任何机器代码“一样”,因为所有代码都是数据,但并非所有数据都是有效代码。您可以尝试将$f00fc7c8 作为指令执行,但不能保证处理器能够理解它。
      • @TheIronKnuckle:在机器代码中,如果数据世界是所有符合特定字长的二进制数据字符串,那么并非所有数据都是程序,至少在所有处理器上都不是。有些二进制字符串不能形成有效的操作码,会让你的程序崩溃。
      • 这是不精确的。一旦语法结构良好,所有数据都是程序。甚至评估者也会拒绝将格式错误的数据作为程序的语义正确部分,它仍然必须接受它们作为程序的一部分,因此它会尝试解释它们(并导致一些错误如果它们在语义上不正确),而不是导致不可预测的行为。是否将语义错误提升到一些较早的阶段(并将它们视为“句法”)是此上下文中的一个实现细节。
      猜你喜欢
      • 2010-11-06
      • 1970-01-01
      • 2011-11-29
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      • 2013-12-15
      • 2013-10-12
      • 2012-02-01
      相关资源
      最近更新 更多