我不明白Lisp是如何编译和动态的。对于能够操作,修改和生成代码的语言,是否需要进行解释?语言是否可以完全编译并且仍然是动态的?或者我错过了什么? Lisp做了什么允许它既编译又动态?
答案 0 :(得分:36)
Lisp是一个广泛的语言和实现系列。
Lisp上下文中的Dynamic 意味着代码在运行时具有一定的灵活性。例如,它可以更改或替换。这与动态类型不同。
在Lisp中编译
Lisp实现通常在运行时提供编译器。当此编译器是 incremental 时,它不需要整个程序,但可以编译单个Lisp表单。然后我们说编译器支持 incremental 编译。
请注意,大多数Lisp编译器不是 Just In Time 编译器。作为程序员,您可以调用编译器,例如在Common Lisp中使用函数COMPILE
和COMPILE-FILE
。然后编译Lisp代码。
此外,大多数具有编译器和解释器的Lisp系统允许自由混合解释和编译代码的执行。
在Common Lisp中,还可以指示编译器编译代码应该是多么动态。像SBCL(或许多其他人)的编译器这样的更高级的Lisp编译器可以生成不同的代码。
示例强>
(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这样的语言也允许编译器使编译的代码不那么动态。例如,在运行时没有为代码的某些部分查找函数。
答案 1 :(得分:3)
对于能够操作,修改和生成代码的语言,是否需要进行解释?
没有
语言是否可以完全编译并且仍然是动态的?
是
或者我错过了什么?
是
Lisp做了什么允许它既编译又动态?
它是动态编译的,就像大多数java和PyPy的实现一样。
答案 2 :(得分:0)
它可以在同一时间编译和动态,因为它是后期绑定的。您可以运行函数和参数列表,然后向其中添加一些内容,然后再次运行它。基本上,代码的每个部分都可以运行,而不仅仅是整个函数。