为什么代码为数据?

时间:2010-11-10 02:33:44

标签: data-structures coding-style lisp scheme common-lisp

什么是代码作为数据?我听说它比“code-as-ascii-characters”要好,但为什么呢?我个人觉得代码 - 数据哲学实际上有点令人困惑。

我已经涉足了Scheme,但我从来没有真正得到整个代码作为数据的东西,并想知道它究竟是什么意思?

6 个答案:

答案 0 :(得分:59)

这意味着您编写的程序代码也是可由程序操作的数据。采用像

这样的简单Scheme表达式
(+ 3 (* 6 7))

您可以将其视为一种数学表达式,在评估时会产生一个值。但它也是一个包含三个元素的列表,即+3(* 6 7)。通过引用列表,

 '(+ 3 (* 6 7))

你告诉方案将它视为后者,即只是一个包含三个元素的列表。因此,您可以使用程序操作此列表,然后然后对其进行评估。它给你的力量是巨大的,当你“理解”这个想法时,有一些非常酷的技巧可以玩。

答案 1 :(得分:42)

代码作为数据实际上只是硬币的一面。另一个是数据代码

在Lisp代码中嵌入任意数据的可能性在运行中加载和重新加载它使得它(数据)非常便于处理,因为它可以消除数据方式之间任何潜在的阻抗不匹配表示代码和代码的工作方式。

让我举个例子。

假设您想要编写某种带有各种怪物类的电脑游戏。您基本上有两种选择:在编程语言中对怪物类进行建模,或者使用数据驱动的方法,例如从XML文件中读取类描述。

在编程语言中进行建模具有易用性和简单性的优点(这总是一件好事)。根据需要,根据怪物类别指定自定义行为也很容易。最后,实现可能已经非常优化了。

另一方面,从数据文件加载所有内容要灵活得多。你可以在语言不支持的情况下进行多重继承;你可以动态打字;你可以在运行时加载和重新加载东西;您可以使用简单的,指向特定于域的语法等等。但是现在你需要为整个事情编写某种运行时环境,指定行为意味着要么在数据文件和游戏代码之间分割数据,要么嵌入脚本语言,这是另一层偶然的复杂性。

或者您可以使用Lisp方式:指定您自己的子语言,将其转换为代码并执行它。如果您正在使用的编程语言具有足够的动态性和语法灵活性,那么您将获得使用数据驱动方法(因为代码是数据)以及将所有内容保存在代码中的简单性(因为数据是代码)的所有好处。

顺便说一句,这不是Lisp特有的。在Lisp和C ++之间存在各种不同的代码 - 数据 - 等价灰色。例如,Ruby使得在应用程序中嵌入数据比Python更容易,而Python使它比Java更容易。数据作为代码和代码作为数据都是连续的,而不是它们或者是问题。

答案 2 :(得分:18)

作为一名Lisp程序员,您将学习将程序源视为数据。它不再是静态文本,而是数据。在某些形式的Lisp中,程序本身就是数据结构,它被执行。

然后所有工具都以这种方式定向。而不是文本宏处理器Lisp有一个宏程序,它作为数据在程序上运行。程序与文本的转换也是其工具。

让我们考虑添加一个向量的两个元素:

(let ((v (vector 1 2 3)))
   (+ (aref v 0)
      (aref v 1)))

没有什么不寻常之处。你可以编译并运行它。

但你也可以这样做:

(let ((v (vector 1 2 3)))
   (list '+
         (list 'aref v 0)
         (list 'aref v 1)))

返回带有加号和两个子列表的列表。这些子列表的符号为aref,然后是v的数组值和索引值。

这意味着构造的程序实际上包含符号,但也包含数据。该数组实际上是子列表的一部分。因此,您可以构建程序,这些程序是数据,可以包含任意数据。

EVAL然后将该程序评估为数据。

CL-USER 17 > (setf *print-circle* t)
=>  T

上面告诉我们打印机应该打印循环数据结构,以便在回读时保留标识。

CL-USER 18 > (let ((v (vector 1 2 3)))
               (list '+
                     (list 'aref v 0)
                     (list 'aref v 1)))
=>  (+ (AREF #1=#(1 2 3) 0) (AREF #1# 1))

现在让我们将数据作为Lisp程序进行评估:

CL-USER 19 > (EVAL (let ((v (vector 1 2 3)))
                     (list '+
                           (list 'aref v 0)
                           (list 'aref v 1))))

=>  3

如果您的编译器希望文本作为源,则可以构造这些文本,但它们永远不能直接引用数据。对于这些基于文本的源构造,已经开发了许多工具,但是其中许多工具倾向于分阶段工作。在Lisp中,操作数据的功能可以直接应用于操作程序,这个功能直接内置并且是评估过程的一部分。

所以Lisp给你一个额外的自由度和新的思考方式。

答案 3 :(得分:12)

代码数据是指您的代码是根据语言的数据结构表示的。我不会试图在这里争论这是编程的最佳方式,但我发现这是一种在代码中表达想法的美妙方式。

其中一个好处是元编程与常规编程几乎相同。使用code-as-ascii-characters,你经常最终不得不做一些严肃的解析来做任何meta,你用Lisp跳过那些讨厌的位。

答案 4 :(得分:10)

在Scheme(或任何Lisp)中,您可以声明列表文字,如下所示:

> '(1 2 3)
=> (1 2 3)

这与许多其他高级语言类似,除了符号的细微差别。例如,这是其他一些语言代表列表文字的方式:

[1, 2, 3] # Python
#(1 2 3) "Smalltalk. This is in fact an array in Smalltalk. Let us ignore that for now."

列表可以包含任何类型的值。由于函数是第一类对象,因此列表也可以包含函数。让我们用函数替换上面列表中的第一个元素:

> '(+ 2 3)
=> (+ 2 3)

单引号(')标识列表文字。 (就像Smalltalk中的#)。如果我们删除报价会发生什么?然后Scheme解释器将特别处理该列表。它会将第一个元素视为函数(或过程),将其余元素视为该函数的参数。该函数被执行(或评估):

> (+ 2 3)
=> 5

使用该语言中的数据结构表示可执行代码的能力开启了一种新的可能性 - 我们可以编写编写程序的程序。这意味着,需要更改编译器和其他语言的运行时系统的扩展可以在Lisp中实现,作为Lisp本身的几行。想象一下,您需要一种名为when的语言的新控制结构。它与if类似,但在某些情况下使阅读代码更自然:

 (when this-is-true do-this)

您可以通过编写一个短宏来扩展您的Lisp系统以支持when

 (defmacro when (condition &rest body)
    `(if ,condition (progn ,@body)))

宏只是一个列表,在编译时会扩展。可以使用这样的列表将更复杂的语言结构甚至整个范例添加到核心语言中。例如,CLOS,Common Lisp Object Systems基本上是用Common Lisp本身编写的宏集合。

答案 5 :(得分:7)

除非您使用旧版Harvard Mark I之类的内容,否则您的代码 以与您的数据相同的位置和方式存储 - 只是(如您所述)可能在表单中ASCII字符,因此很难做任何事情。很可能,大多数Java程序员从未自己解析过Java代码。

查看任何程序 - 源代码本身编码的信息丰富(很好,取决于程序!)。这是它存在的原因!通过不使用同性语言,你隐含地说你没有能够从你写的另一个程序中读取它(或者没有人能够这样做很好)。基本上,计算机上唯一可以读取它的程序是编译器,读取后它唯一能做的就是生成目标代码和错误消息。

想象一下,你必须每天使用其他一些数据源,比如XML文件或RDBMS,并且访问该数据的唯一方法是通过“编译器”运行它,将其转换为您可以阅读的格式。我认为没有人会认为那是个好主意。 : - )

我真的不知道我要去哪里,所以我会试着总结一下我的上述言论:

  • 我认为代码作为数据只是从哈佛建筑到冯诺依曼建筑的合乎逻辑的下一步
  • 我们已经拥有几乎所有其他X的X-as-data,所以排除程序员整天操作的一种数据似乎很奇怪