Lisp是如何动态和编译的? [英] How is Lisp dynamic and compiled?

查看:202
本文介绍了Lisp是如何动态和编译的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不明白Lisp如何被编译和动态化.对于一种能够操纵,修改和生成代码的语言,这不是必须要进行解释的要求吗?语言是否有可能被完全编译并且仍然是动态的?还是我错过了什么? Lisp在做什么使它既可以被编译又可以是动态的?

解决方案

Lisp是广泛的语言和实现系列.

Lisp上下文中的

Dynamic 意味着代码在运行时具有一定的灵活性.例如,可以更改或替换.这与动态输入不同.

Lisp编译

通常Lisp实现有一个在运行时可用的编译器.当此编译器为 incremental 增量编译器时,它不需要整个程序,但可以编译单个Lisp表单.然后我们说编译器支持 incremental 编译.

请注意,大多数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之类的语言还允许编译器使编译后的代码动态性降低.例如,通过在运行时不查找代码某些部分的功能.

I don't understand how Lisp can be compiled and dynamic. For a language to be able to manipulate and modify and generate code, isn't it a requirement to be interpreted? Is it possible for a language to be completely compiled and still be dynamic? Or am I missing something? What is Lisp doing that allows it to be both compiled and dynamic?

解决方案

Lisp is a wide family of language and implementations.

Dynamic in the context of Lisp means that the code has a certain flexibility at runtime. It can be changed or replaced for example. This is not the same as dynamically typed.

Compilation in Lisp

Often Lisp implementations have a compiler available at runtime. When this compiler is incremental, it does not need whole programs, but can compile single Lisp forms. Then we say that the compiler supports incremental compilation.

Note that most Lisp compilers are not Just In Time compilers. You as a programmer can invoke the compiler, for example in Common Lisp with the functions COMPILE and COMPILE-FILE. Then Lisp code gets compiled.

Additionally most Lisp systems with both a compiler and an interpreter allow the execution of interpreted and compiled code to be freely mixed.

In Common Lisp the compiler can also be instructed how dynamic the compiled code should be. A more advanced Lisp compiler like the compiler of SBCL (or many others) can then generate different code.

Example

(defun foo (a)
  (bar a 3))

Above function foo calls the function bar.

If we have a global function bar and redefine it, then we expect in Lisp usually that the new function bar will be called by foo. We don't have to recompile foo.

Let's look at GNU CLISP. It compiles to byte code for a virtual machine. It's not native machine code, but for our purpose here it is easier to read.

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)

Runtime lookup

So you see that the call to BARdoes a runtime lookup. It looks at the symbol BAR and then calls the symbol's function. Thus the symbol table serves as a registry for global functions.

This runtime lookup in combination with an incremental compiler - available at runtime - allows us to generate Lisp code, compile it, load it into the current Lisp system and have it modify the Lisp program piece by piece.

This is done by using an indirection. At runtime the Lisp system looks up the current function named bar. But note, this has nothing to do with compilation or interpretation. If your compiler compiles foo and the generated code uses this mechanism, then it is dynamic. So you would have the lookup overhead both in the interpreted and the compiled code.

Since the 70s the Lisp community put a lot of effort into making the semantics of compiler and interpreter as similar as possible.

A language like Common Lisp also allows the compiler to make the compiled code less dynamic. For example by not looking up functions at run time for certain parts of the code.

这篇关于Lisp是如何动态和编译的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆