Lisp源代码文件本身是列表吗?

Ced*_*tin 9 lisp emacs scheme clojure common-lisp

无论Lisp方言如何,看起来每个包含Lisp函数的源代码文件本身都不是一个列表(我第一次"惊讶"这是在处理我的Emacs .el文件时).

我有几个问题,但它们都与同一个"问题"有关,而且可能只是我误解了一些问题.

有没有理由为什么各种Lisp方言的源代码文件似乎是一堆"无组织"的函数,如下所示:

(function1 ...)
(function2 ...)
(function3 ...)
Run Code Online (Sandbox Code Playgroud)

而不是函数的"Lisp列表",可能是这样的:

(
  '(function1 ...)
  '(function2 ...)
  '(function3 ...)
)
Run Code Online (Sandbox Code Playgroud)

我有点惊讶这整个"代码是数据,数据是代码"的东西,看到源代码文件本身显然不是整齐的列表......或者是他们!?

源代码文件是否应该"操纵"?

如果我想将我的.clj(Clojure)源文件中的一个转换为某些CSS + HTML网页,那么源代码文件本身不是一个列表并不是一个"问题"吗?

我从Lisp开始,所以我不知道我的问题是否有意义,任何解释都是受欢迎的.

Rai*_*wig 12

在Common Lisp中,源文件包含lisp forms和注释.Lisp表单是数据或Lisp代码.源文件的典型操作由函数LOADCOMPILE-FILE.

LOAD 将从文件中读取表单并逐个执行.

COMPILE-FILE更加复杂.它通常读取表单并将它们编译为其他表示形式(机器代码,字节代码,C代码......).它不执行代码.

如果文件包含一个表单列表而不是仅包含多个表单,那么它会对您有什么帮助?

  • 你会有一个级别的添加括号
  • 你必须阅读整个列表才能用它做任何事情(或者你需要一个不同的读者机制)
  • 通过程序将表单添加到文件末尾将是一件痛苦的事
  • 你不能在文件中添加一些东西来改变读者对文件其余部分的解释
  • LOAD文件的长度不能太长

现在举个例子,编译器会从文件流中读取lisp表单并逐个编译它们.

如果你想要所有表格,你可以做

CL-USER 170 > (defun read-forms (file)
               (with-open-file (stream file)
                 (loop for form = (read stream nil nil)
                       while form
                       collect form)))
READ-FORMS

CL-USER 171 > (read-forms (capi:prompt-for-file "source file"))
((DEFPARAMETER *UNITS-TO-SHOW* 4.1)
 (DEFPARAMETER *TEXT-WIDTH-IN-PICAS* 28.0)
 (DEFPARAMETER *DEVICE-PIXELS-PER-INCH* 300)
 (DEFPARAMETER *PIXELS-PER-UNIT* (* (/ (/ *TEXT-WIDTH-IN-PICAS* 6)
                                       (* *UNITS-TO-SHOW* 2))
                                    *DEVICE-PIXELS-PER-INCH*))
...
Run Code Online (Sandbox Code Playgroud)

如果你想围绕一切使用括号PROGN:

 (progn
   'form-1
   (defun function-defintion-form () )
   42)
Run Code Online (Sandbox Code Playgroud)

PROGN 保留其子表格的"顶级".

旁注:Lisp已经探索了几十年的替代方案.最突出的例子是现在已经不复存在的施乐公司的Interlisp-D.Interlisp-D是由Xerox PARC与Smalltalk并行开发的.Interlisp-D最初使用结构编辑器编辑Lisp数据,源代码被编辑为Lisp数据.开发环境基于这个想法.但从长远来看,"源文本"赢了.你仍然可以在许多当前的Lisp环境中模仿其中的一些.例如,许多Lisp系统允许写入当前执行存储器的"图像" - 该图像包括所有数据和所有代码(也包括编译代码).因此,您可以处理此数据/代码并不时保存图像.

  • 我认为应该强调源文件包含需要"读取"的文本. (2认同)

Jer*_*emy 10

源代码文件只是存储列表的便利位置.Lisp代码(通常)旨在在read-eval-print-loop(REPL)中执行,其中每个输入本身就是一个列表.因此,当您执行源代码文件时,您可以想到它,因为它中的每个列表都被逐个读入REPL.我们的想法是,您拥有一个完全互动的环境,这个环境与"代码就是数据"范例相称.

当然,您可以将文件视为一个巨型列表,但是您暗示该文件具有明确定义的结构,但情况并非总是如此.如果你真的想要创建一个包含一个巨大列表的文件,那么没有什么能阻止你这样做.您可以使用Lisp阅读器将其作为一个大型列表(数据?)读取并根据需要对其进行处理(可能使用某种eval?).以Leiningen的project.clj文件为例.它们通常只是一个很大的反对项目清单.

  • 哪里说Lisp代码一般是在REPL中执行的?由于REPL本身是用Lisp编写的,它在哪里执行? (4认同)

bmi*_*are 6

要彻底,所有源文件都是文本,而不是lisp数据结构.要评估或编译代码,lisp必须首先READ是文件,这意味着将文本转换为lisp数据结构.回想一下首字母缩略词REPL,前两个字母代表READ,和EVAL.READ获取代码的字符串表示形式,并返回表示代码的数据结构.EVAL获取返回的数据结构,并将数据结构解释(或编译并运行)为代码.因此,重要的是要记住涉及中间步骤.

一个很好的问题是,当READ你提到多个s表达式时,会发生什么,并且它们不在列表中?

如果你查看代码,你通常会发现多个版本READ,clojure read-string只读取并返回第一个s表达式,忽略其余的.但是,在clojure中使用的读者load-file将采用整个字符串,并且"有效地"(实现可能不同)围绕所有表单包装隐式do(或progn通常的lisp),然后将其传递给eval.此行为与REPL中发生的情况形成对比,表单按顺序读取,评估和打印.

但在这两种情况下,这种"幕后"行为都是为了简洁而进行的权衡.我们可以假设当我们加载s表达式的文本文件时,我们希望它们都被评估,并且最多返回最后一个s-expression的值.


650*_*502 5

在Lisp中有两级源代码,或根本没有源代码,具体取决于您如何定义源代码.

这两个级别是存在的,因为Lisp解释器/编译器(通常)执行两个单独的概念步骤.

第一步:"阅读"

在此步骤中,源代码是一系列字符,例如来自文件.这里括号,引用的字符串,数字,符号,引号,甚至部分准语法都被处理并转换为Lisp数据结构.在此级别,语法规则是关于括号,数字,管道,引号,分号,尖锐符号,逗号,符号等.

第二步:"编译"/"解释"

在此步骤中,输入是Lisp数据结构,输出是机器代码,字节代码,或者源可能由解释器直接执行.在这个级别的语法是关于特殊形式......如含义(if ...),(labels ...),(symbol-macrolet ...)等等.Lisp代码中的结构是统一的(只是列表和原子),但语义不是(if表单看起来像函数调用,但它们不是).

因此,在这种观点中,您的答案的问题是肯定而不是.对于步骤1是否,对于步骤2是.如果您仅考虑文件,则答案为否...文件包含字符,而不是列表.这些字符可以由读者转换为列表.

Lisp没有语法

为什么然后有人说Lisp没有语法,实际上有两种不同的语法级别?原因是这两个级别都在程序员的控制之下.

您可以通过定义读取器宏来自定义级别1,并且可以通过定义宏来自定义级别2.因此Lisp没有固定的语法,因此源文件可以以"lispy"外观开始,并且可以看起来与Python代码完全相同.

源文件可以包含任何内容(从某一点开始),因为初始表单可以定义一些新的阅读规则,这些规则将改变后续字符的含义.

通常Lisp程序员不会在读取级别上做疯狂的事情,因此大多数Lisp源代码文件看起来就像Lisp表单的序列,并且它们仍然是"lispy".

但这不是一个严格的约束......例如,我并不是在开玩笑说Lisp语法变成了Python:有人这样做了.