如果在 Lisp 中读取、编译和运行时没有区别,有人能给我一些直观的例子吗?

If there is no distinct between read, compile and runtime in Lisp, can someone give me some intuitive examples?

当我阅读博客 Revenge of the nerds 时,它说(在使 Lisp 与众不同的部分):

The whole language there all the time. There is no real distinction between read-time, compile-time, and runtime. You can compile or run code while reading, read or run code while compiling, and read or compile code at runtime.

Running code at read-time lets users reprogram Lisp's syntax; running code at compile-time is the basis of macros; compiling at runtime is the basis of Lisp's use as an extension language in programs like Emacs; and reading at runtime enables programs to communicate using s-expressions, an idea recently reinvented as XML.

为了理解这句话,我画了一个statechart diagram:

我有两个问题:

  1. 如何理解to read at runtime enable programming to communicate using s-expression, an idea reinvented as XML
  2. 读时编译或编译时读可以做什么?

XML 允许您在运行时在程序之间(或同一程序的不同调用之间)交换数据。 JSON 也是如此,它是 Javascript 的一个子集,在精神上更接近 Lisp。但是,Common Lisp 可以更好地控制不同步骤的执行方式;如引号中所述,您可以重用与 Lisp 环境相同的工具,而不是像其他语言那样构建框架。

基本上,您将数据打印到文件中:

(with-open-file (out file :direction :output)
  (write data :stream out :readably t))

...您稍后恢复它:

(with-open-file (in file) (read in))

你在其他语言中称其为 "serialization" 或 "marshalling"(事实上,在某些 Lisp 库中)。 READ 步骤可以自定义:您可以读取以自定义语法编写的数据(JSON.parse accepts a reviver function, so it is a little bit similar; the Lisp reader works for normal code too). For example, the local-time 库具有特殊的日期语法,可用于从流中重建日期对象。

实际上,这有点复杂,因为并非所有数据都具有简单的可读形式(如何保存网络连接?),但您可以编写可以在加载时恢复信息的形式(例如恢复连接)。所以 Lisp 允许您自定义 READ and PRINT, with readtables and PRINT-OBJECT, but there is also LOAD-TIME-VALUE and MAKE-LOAD-FORM, which allows you to allocate objects and initialize them when loading code. All of this is already available in the language, but there are also libraries that make things even easier, like cl-conspack:您只需将 类 存储到文件中并加载它们,而无需定义任何特殊内容(假设您保存所有插槽)。这要归功于元对象协议。

Common Lisp

READ 是一个函数,它 读取 s 表达式和 returns Lisp 数据。

CL-USER> (read)
(defun example (n) (if (zerop n) 1 (* (example (1- n)) n)))  ; <- input
(DEFUN EXAMPLE (N) (IF (ZEROP N) 1 (* (EXAMPLE (1- N)) N)))  ; <- output

再次上次结果:

CL-USER> *
(DEFUN EXAMPLE (N) (IF (ZEROP N) 1 (* (EXAMPLE (1- N)) N)))                                                                                                       

正在将变量 code 设置为最后的结果。

CL-USER> (setf code *)
(DEFUN EXAMPLE (N) (IF (ZEROP N) 1 (* (EXAMPLE (1- N)) N)))                                                                                                       

第三个元素是什么?

CL-USER> (third code)
(N)                   

我们可以评估这个列表,因为它看起来像有效的 Lisp 代码:

CL-USER> (eval code)
EXAMPLE                                                    

函数EXAMPLE已定义。让我们获取函数对象:

CL-USER> (function example)
#<interpreted function EXAMPLE 21ADA5B2>                           

这是一个解释函数。我们使用 Lisp 解释器。

让我们通过将函数映射到列表来使用它:

CL-USER> (mapcar (function example) '(1 2 3 4 5))
(1 2 6 24 120)                                      

让我们编译函数:

CL-USER> (compile 'example)
EXAMPLE                                                                                                                                                           
NIL                                                                                                                                                               
NIL                    

函数编译成功。编译器没有警告,函数现在应该 运行 快得多。

让我们再次使用它:

CL-USER> (mapcar (function example) '(1 2 3 4 5))
(1 2 6 24 120) 

这是相同的结果,但计算速度可能快得多。

既然已经编译好了,我们来反汇编一下函数:

CL-USER> (disassemble #'example)
0          : #xE3E06A03 : mvn            tmp1, #12288                                                                                                             
4          : #xE18D6626 : orr            tmp1, sp, tmp1, lsr #12                                                                                                  
8          : #xE5166030 : ldr            tmp1, [tmp1, #-48]                                                                                                       

...以及更多行的 ARM 汇编机器代码