我听过Ron Garret的Google演讲(http://www.youtube.com/watch?v=_gZK0tW8EhQ)并阅读了论文(http://www.flownet.com/gat/jpl-lisp.html),但我不明白它如何“纠正”所谓的运行代码REPL。 DS-1运行的Lisp代码是某种虚拟机吗?还是它在REPL的现实世界中“活着”?或者Lisp代码是一个可替换的可执行文件?当您通过REPL动态更改运行的Lisp代码时到底发生了什么?
答案 0 :(得分:3)
虽然大多数程序是作为可执行文件构建和分发的,只包含运行程序所必需的组件,但Lisp可以作为一个图像分发,不仅包含特定程序的组件,还包含大部分或全部的Lisp运行时和开发环境。
REPL是提供对正在运行的Lisp环境的交互式访问的典型机制。 REPL,Read和Eval的两个关键组件暴露了Lisp运行时系统的大部分内容。例如,今天许多Lisp系统通过编译提供的表单(由Reader读取),将表单编译为机器代码,然后执行结果来实现Eval。这与解释表格形成对比。有些系统,特别是过去,包含一个快速执行并适合交互式访问的解释器,以及一个生成更好代码的编译器。但现代系统足够快,编译器阶段不明显,只是放弃了解释器。
当然,你今天可以做类似的事情。一个简单的例子是将SSH运行到托管PHP的Linux机器上。您的PHP服务器已启动并正常运行,提供页面和请求。但是你通过SSH登录,检查并修复一个PHP文件,一旦保存该文件,所有用户都会实时看到新结果 - 系统会动态更新。
PHP运行在Linux运行时与运行在Lisp运行时的Lisp这一事实是一个细节。效果是一样的。 PHP未编译的事实也是一个细节。例如,您可以在Java服务器上执行相同的操作:修改JSP,保存它,并将JSP作为Java源代码转换为Servlet,然后由Java运行时动态编译,然后加载到执行容器,替换旧代码。
Lisps执行此操作的能力非常好,而且它在当天很有意思。今天,它不那么重要,因为有不同的系统提供类似的功能。
附录:
不,Lisp不是虚拟机,没有必要让它复杂化。
概念的关键是动态调度。使用动态调度时,在调用函数之前会涉及一些查找。
在像C这样的静态语言中,一旦链接器和加载器完成处理可执行文件以准备开始执行,事物的位置几乎是一成不变的。
所以,在C中你有一些简单的东西:
int add(int i) {
return i + 1;
}
void main() {
add(1);
}
在编译和链接并加载程序之后,add
函数的地址将被设置为石头,因此引用该函数的东西将确切地知道在哪里找到它。
所以,在汇编语言中:(注意这是一种人为的汇编语言)
add: pop r1 ; pop R1 from the stack, loading the i parameter
add r1, 1; Add 1 to the parameter.
push r1 ; push result of function call
rts ; return from subroutine
main: push 1 ; Push parameter to function
call add ; call function
pop r1 ; gather (and ignore) the result
因此,您可以在此处看到add
已修复到位。
在像Lisp这样的东西中,函数是间接引用的。
int add(int i) {
return i + 1;
}
int *add_ptr() = &add;
void main() {
*(add_ptr)(1);
}
在装配中,你得到:
add: pop r1 ; pop R1 from the stack, loading the i parameter
add r1, 1; Add 1 to the parameter.
push r1 ; push result of function call
rts ; return from subroutine
add_ptr: dw add ; put the address of the add routine in add_ptr
main: push 1 ; Push parameter to function
mov r1, add_ptr ; Put the contents of add_ptr into R1
call (r1) ; call function indirectly through R1
pop r1 ; gather (and ignore) the result
现在,您可以在此处看到,不是直接调用add
,而是通过add_ptr
间接调用它。在Lisp运行时,它具有编译新代码的能力,当发生这种情况时,add_ptr
将被覆盖以指向新编译的代码。您可以看到main
中的代码永远不必更改,它将调用add_ptr指向的任何函数。
由于Lisp中的大多数函数都是通过符号间接引用的,因此很多可以在正在运行的系统“后面”进行更改,并且系统将继续运行。
当重新编译一个函数时,旧的函数代码(假设没有其他引用)符合垃圾收集的条件,并且通常会最终消失。
您还可以看到,当系统被垃圾收集时,任何移动的代码(例如add函数的代码)都可以被运行时移动,并且它的新位置在add_ptr
更新了所以系统将在代码之后继续运行并由垃圾收集器重新定位。
所以,关键是,通过一些查找机制调用你的函数。这样可以为您提供很大的灵活性。
注意,您也可以这样做,例如运行C系统。您可以将代码放入动态库中,加载库,执行代码,如果需要,您可以构建一个新的动态库,关闭旧的动态库,打开新的动态库,然后调用新代码 - 所有这些都在“运行“系统。动态库接口提供隔离代码的查找机制。