我不明白 Lisp 是如何编译和动态的。对于一种能够操作、修改和生成代码的语言来说,是不是需要被解释?一种语言是否可以完全编译并且仍然是动态的?还是我错过了什么?Lisp 做了什么让它既可以编译又可以动态化?
3 回答
Lisp 是一个广泛的语言和实现家族。
Lisp 上下文中的动态意味着代码在运行时具有一定的灵活性。例如,它可以更改或替换。这与动态类型不同。
在 Lisp 中编译
通常 Lisp 实现有一个在运行时可用的编译器。当这个编译器是增量编译器时,它不需要整个程序,但可以编译单个 Lisp 形式。然后我们说编译器支持增量编译。
请注意,大多数 Lisp 编译器都不是Just In Time编译器。作为程序员,您可以调用编译器,例如在 Common Lisp 中使用函数COMPILE
和COMPILE-FILE
. 然后编译 Lisp 代码。
此外,大多数同时具有编译器和解释器的 Lisp 系统允许自由混合执行解释和编译代码。
在 Common Lisp 中,编译器也可以被指示编译代码的动态程度。更高级的 Lisp 编译器,如SBCL(或许多其他)的编译器,可以生成不同的代码。
例子
(defun foo (a)
(bar a 3))
上面foo
的函数调用函数bar
。
如果我们有一个全局函数bar
并重新定义它,那么在 Lisp 中我们通常期望新函数bar
将由foo
. 我们不必重新编译foo
。
让我们看看GNU CLISP。它编译为虚拟机的字节码。它不是本机机器代码,但出于我们的目的,它更易于阅读。
CL-USER 1 > (defun foo (a)
(bar a 3))
FOO
CL-USER 2 > (compile 'foo)
FOO
NIL
NIL
[3]> (disassemble #'foo)
Disassembly of function FOO
(CONST 0) = 3
(CONST 1) = BAR
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
4 byte-code instructions:
0 (LOAD&PUSH 1)
1 (CONST&PUSH 0) ; 3
2 (CALL2 1) ; BAR
4 (SKIP&RET 2)
运行时查找
因此,您会看到对 的调用进行BAR
了运行时查找。它查看符号 BAR
,然后调用符号的函数。因此,符号表用作全局函数的注册表。
这种运行时查找与增量编译器相结合 - 在运行时可用 - 允许我们生成 Lisp 代码,编译它,将其加载到当前的 Lisp 系统中,并让它逐个修改 Lisp 程序。
这是通过使用间接来完成的。在运行时,Lisp 系统会查找名为bar
. 但请注意,这与编译或解释无关。如果您的编译器编译foo
并且生成的代码使用这种机制,那么它是动态的。因此,您将在解释代码和编译代码中都有查找开销。
自 70 年代以来,Lisp 社区付出了很多努力来使编译器和解释器的语义尽可能相似。
像 Common Lisp 这样的语言还允许编译器降低编译代码的动态性。例如,在运行时不为代码的某些部分查找函数。
对于一种能够操作、修改和生成代码的语言来说,是不是需要被解释?
不。
一种语言是否可以完全编译并且仍然是动态的?
是的。
还是我错过了什么?
是的。
Lisp 做了什么让它既可以编译又可以动态化?
它是即时编译的,就像大多数 java 和 PyPy 的实现一样。
It can be compiled and dynamic in the same time because it's late-bound. You can run a list of functions and arguments and then add something to it and then run it again. Basically each part of the code can be run not just entire functions.