同性恋和“不受限制”的自我修改代码+ lisp是否真的可以自我修改?

时间:2011-12-13 14:12:33

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

我将继续承认我对Lisp的了解非常少。但是我对这门语言非常感兴趣,并计划在不久的将来开始认真学习它。我对这些问题的理解无疑是有缺陷的,所以如果我说出任何错误的内容,请评论并纠正我,而不是贬低。

真正的同性恋和自我修改语言

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

到目前为止,我发现只有三个符合此标准的例子:

  1. 机器代码。 Homoiconic在一切都是一个数字。无限制地可修改,因为它包含指针,可用于操纵任何内存地址,无论该地址是否包含代码或数据。
  2. Malbolge。与机器代码相同的推理。每条指令在执行后都会自行修改
  3. DNA。不是编程语言,但仍然很有趣。它不像机器代码那样自我修改;实际指令+数据在哪里修改到位。然而,它是自我复制的,并且可以根据它的先前状态进行变异/进化(具有副作用,例如辐射时不时地拧紧它)。无论如何,这只是一种间接的自我修改方式。简而言之,DNA可以自我修饰,但它可以通过在相关突变中重现自身的真实性来实现。 DNA的物理串是“不可变的”#34;。
  4. 为什么Lisp不在此列表中

    Lisp不在该列表中,因为在我看来,Lisp只是几乎 Homoiconic,并且只支持受限制的自我修改。你可以做点什么

    (+ 1 2 3)
    

    将与

    做同样的事情
    (eval '(+ 1 2 3))
    

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

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

    '(+ 1 2 3)
    

    '(+ 1 4 3)
    

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

    除非有某种方法可以做到这一点,否则Lisp既不是讽刺也不是自我修改。 (为了推断定义的争论,Lisp不是同源的,也不是自我修改到与机器代码相同的程度。

    如何使Lisp Homoiconic / Unrestrictedly self-modifiable

    我可以看到3种使Lisp像机器代码一样可以单独/自我修改的方法。

    1. 非Von-Neumann架构。如果有人能够发明一些令人惊叹的假想机器,其中程序的最低级别表示是可以直接执行的AST(不需要进一步编译)。在这样的机器上,AST将代表可执行指令以及数据。不幸的是,这个问题还没有解决,因为AST仍然必须是代码或数据。 eval函数的预设并没有改变这一点。在机器代码中,您可以根据需要在代码和数据之间来回切换。而对于eval和Lisp,一旦你被唤醒"从数据到代码的一些列表并执行它,没有办法再将该列表作为数据返回。实际上,该列表已经永远消失,并已被其价值所取代。我们缺少一些至关重要的东西,恰好是指针。
    2. 列出标签。如果要求每个列表也都有唯一标签,则可以通过针对具有给定标签的列表运行函数来进行间接自我修改。结合延续,这最终将允许自动修改代码与机器代码具有相同的意义。标签与机器代码存储器地址等效。例如,考虑一个Lisp程序,其中AST的顶部节点具有标签" main"。在main中你可以执行一个函数,它接受一个标签,一个Integer,一个Atom,并将原子复制到List,其标签与提供给函数的标签相匹配,在Integer指定的索引处。然后在main上调用当前的continuation。你去,自我修改代码。
    3. Lisp Macros。我还没有花时间去理解Lisp宏,事实上它们可能正是我正在考虑的事情。
    4. 第1点与2.结合将产生一个完全自我修改的Lisp。前提是可以生产所描述的神奇的Lisp机器。 2.单独可以产生自我修改的Lisp,但是在冯·诺依曼架构上的实现可能非常有效。

      问题

      1. 机器码,dna和malbolge以外的任何语言都可以完全自我修改并且是同音的吗?
      2. (如果你做了tl,请不要回答;上面的文字博士)。 lisp真的是homoiconic +自我修改吗?如果你这样说,你能准确引用我在论证中误入歧途的地方吗?
      3. 附录

        具有不受限制的自我修改但没有语言的语言

        1. 装配。代码使用单词而不是数字,因此失去了homiconicity,但它仍然有指针,它保留了对内存的完全控制,允许不受限制的自我修改。
        2. 使用原始指针的任何语言。例如,C / C ++ / Objective C.与Assembly
        3. 相同的参数
        4. 包含虚拟指针的JIT语言。例如,C#/ .net在不安全的上下文中运行。与Assembly相同的论点。
        5. 其他可能以某种方式相关/有趣的概念和语言: Lisp,Ruby,Snobol,Forth和它的编译时间元编程,Smalltalk及它的反射,无类型的lambda演算以及它的一切都是函数的属性(假设我们假设我们可以发明一台直接执行lambda演算的机器,lambda演算将是homoiconic而Von Neumann机器代码在所述机器上运行时不会。[并且Godels定理将是可执行的。哈哈,可怕的想法:P])

4 个答案:

答案 0 :(得分:20)

  

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

事实并非如此。在第一个版本中,作为数据的列表(+ 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

答案 1 :(得分:11)

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方言的实现允许这样做。一个典型的事情是使用宏,这些宏可以在运行时由这样的系统计算。其他一些Lisps有所谓的Fexprs,它们是一种类似的机制(但通常无法有效编译)。

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

同质性也不是一个定义明确的概念。对于Lisp我更喜欢我们说源代码可以转换成数据,因为源代码使用s表达式的外部表示,这是一种数据序列化格式。但并非每个s表达式都是有效的Lisp程序。此外,作为Lisp数据的源代码的内部表示在任何方面都不是“标志性的”。 Lisp将源代码作为外部s表达式,在阅读源代码后,它具有Lisp数据的内部表示。 READ读取它,PRINT打印它,EVAL评估它。

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

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

  • 3Lisp提供了无限的口译员。有一个运行程序的Lisp解释器。这个Lisp解释器是由另一个Lisp解释器运行的,它又在另一个解析器中运行,那个也是......

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:2)

我在这里作为一个粉丝遇到过,我之前已经说过了,但是请阅读Paul Graham的 On Lisp 如果你想了解Lisp Macros。在实现其他方面不可行的修改方面,它们是一个大问题。另外,我认为在这里区分Lisp语言系列,给定的Lisp和给定的Lisp实现是很重要的。

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