为什么代码为数据?

Pet*_*r C 37 lisp scheme coding-style common-lisp data-structures

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

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

I G*_*ERS 61

这意味着您编写的程序代码也是可以由程序操作的数据.采用一个简单的Scheme表达式

(+ 3 (* 6 7))
Run Code Online (Sandbox Code Playgroud)

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

 '(+ 3 (* 6 7))
Run Code Online (Sandbox Code Playgroud)

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

  • 好评,但我只想说:最好的名字!:) (22认同)
  • 谢谢你的回答只是在我的脑海里抓住了一些东西,现在我理解了许多关于Lisp的东西 (2认同)

Mat*_*ard 43

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

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

让我给你举个例子.

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

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

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

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

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


Rai*_*wig 18

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

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

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

(let ((v (vector 1 2 3)))
   (+ (aref v 0)
      (aref v 1)))
Run Code Online (Sandbox Code Playgroud)

这没什么不寻常的.您可以编译并运行它.

但你也可以这样做:

(let ((v (vector 1 2 3)))
   (list '+
         (list 'aref v 0)
         (list 'aref v 1)))
Run Code Online (Sandbox Code Playgroud)

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

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

然后EVAL将程序评估为数据.

CL-USER 17 > (setf *print-circle* t)
=>  T
Run Code Online (Sandbox Code Playgroud)

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

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))
Run Code Online (Sandbox Code Playgroud)

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

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

=>  3
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我只想说:哦,我的脑袋.`签名,程序员程序员` (3认同)

trp*_*lin 12

代码作为数据是指您的代码是根据语言的数据结构表示的.我不会试图在这里认为它是编程的最佳方式,但我觉得它是表达在代码的思想一个美丽的方式.

其中一个好处是元编程与常规编程几乎相同.随着代码为ASCII码字符,你往往最终不得不做一些认真的分析做任何事情元,而你和Lisp跳过那些讨厌位.


Vij*_*hew 11

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

> '(1 2 3)
=> (1 2 3)
Run Code Online (Sandbox Code Playgroud)

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

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

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

> '(+ 2 3)
=> (+ 2 3)
Run Code Online (Sandbox Code Playgroud)

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

> (+ 2 3)
=> 5
Run Code Online (Sandbox Code Playgroud)

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

 (when this-is-true do-this)
Run Code Online (Sandbox Code Playgroud)

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

 (defmacro when (condition &rest body)
    `(if ,condition (progn ,@body)))
Run Code Online (Sandbox Code Playgroud)

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


Ken*_*Ken 7

除非你使用像一个老马克一号,你的代码存储在相同的地点和方式为您的数据-只(正如你指出)的ASCII字符可能的形式,所以这真的很难做任何事情.很可能,大多数Java程序员从未自己解析过Java代码.

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

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

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

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