保存你的工作
退出Lisp再重新启动后,函数定义就无效了。像定义了一个好函数后我们希望保存我们的工作。
很简单的,我们需要一个文件保存定义式。在Emacs中我们能使用C-x C-f创建新文件,当Emacs提示的时候输入文件名。Common Lisp的源文件习惯上用.lisp后缀名,有些也用.cl。
一旦创建了文件,就能输入之前在REPL中的定义式。需要注意的是在输入正括号和DEFUN后,在Emacs的底部窗口,SLIME会告诉我们需要的参数。具体的依赖于使用什么Common Lisp实现,看起来会像这样:
(defun name varlist &rest body)
这个消息会在开始输入每个新元素的时候消失但是会在每次输入空格的时候出现。在文件输入定义式,可以选择把参数列表之后的部分放到下一行。如果按了Return再按Tab,SLIME会自动的把第二行适当的缩进:
(defun hello-world ()
(format t “hello, world”))
SLIME也能帮助匹配圆括号——输入反括号的时候,会在相对应的正括号闪烁。或者我们能输入C-c C-q(我查阅新的SLIME手册上面写的是 C-c M-q对应命令slime-colse-all-parens-in-sexp,好像是差不多的效果)调用命令slime-close-parens-at-point,对当前所有需要配对的正括号插入对应反括号。
现在有很多方法让定义式添加到Lisp环境中。最简单的就是输入C-c C-c依赖光标的位置,对所在DEFUN后的表或者最近的起作用,会调用命令slime-compile-defun,会把定义式发给Lisp求值和编译。确保这个工作进行了,可以改变一点hello-world再编译它,然后再REPL中用C-c C-z或者C-x b,再调用它。事实上,可以加一点小语法。
(defun hello-world ()
(format t “Hello, world!”))
接下来,再用C-c C-c编译然后C-c C-z切换到REPL试试新版本。
CL-USER> (hello-world)
Hello, wolrd!
NIL
想保存文件以后再继续工作;在hello.lisp缓冲里,输入C-x C-s调用Emacs的save-buffer命令。
现在试看再从源文件加载,需要退出Lisp再启动。想退出可以用SLIME的快捷方式:在REPL中输入,逗号。在Emacs窗口的底部,会有提示命令。输入quit(或者 sayooanra),再按Enter。就会退出Lisp关闭REPL和所有由SLIME创建的缓冲。现在重启SLIME输入M-x slime。
可以试下调用hello-world。
CL-USER> (hello-world)
现在这样SLIME会弹出新的缓冲,开始一些像这样的东西:
attempt to call `HELLO-WORLD' which is an undefined function.
[Condition of type UNDEFINED-FUNCTION]
Restarts:
0: [TRY-AGAIN] Try calling HELLO-WORLD again.
1: [RETURN-VALUE] Return a value instead of calling HELLO-WORLD.
2: [USE-VALUE] Try calling a function other than HELLO-WORLD.
3: [STORE-VALUE] Setf the symbol-function of HELLO-WORLD and call it again.
4: [ABORT] Abort handling SLIME request.
5: [ABORT] Abort entirely from this process.
Backtrace:
0: (SWANK::DEBUG-IN-EMACS #<UNDEFINED-FUNCTION @ #x716b082a>)
1: ((FLET SWANK:SWANK-DEBUGGER-HOOK SWANK::DEBUG-IT))
2: (SWANK:SWANK-DEBUGGER-HOOK #<UNDEFINED-FUNCTION @ #x716b082a> #<Function SWANK-DEBUGGER-HOOK>)
3: (ERROR #<UNDEFINED-FUNCTION @ #x716b082a>)
4: (EVAL (HELLO-WORLD))
5: (SWANK::EVAL-REGION "(hello-world)
" T)
(使用的CL实现不同提示有些不同)
这些是由尝试调用一个不存在的函数引发。虽然输出突然出现,但是Lisp已经用温文的方式处理了。不向Java或者Python,Common Lisp不只是释放——抛出解释和展开栈。并且它明显没有进仅因为你尝试调到未命中的函数而转储内核。相反的Lisp把你引入到调试器。
虽然在调试器中但仍然对Lisp有完全的权限,所以可以对表达式求值以检查程序状态,甚至可以修复东西。输入q就能退出调试器返回REPL显示:
CL-USER> (hello-world)
; Evaluation aborted
CL-USER>
显然能在调试器中做更多事不只是警告——在19章会介绍调试器怎么和错误处理结合。
有很多方法可以让Lisp知道我们在hello.Lisp中的定义式。可以切换到包含文件的缓冲(输入C-x b再输入hello.lisp)再用C-c C-c重新编译这个定义式。或者可以直接加载整个文件,如果文件中有很多定义式的话这是一个更简便的方法,用LOAD函数:
CL-USER> (load “hello.lisp”)
; Loading /home/peter/my-lisp-programs/hello.lisp
T
(使用的CL实现不同提示有些不同)
T表示所有东西加载完成。用LOAD加载一个文件相当于把文件中所有表达式按文件中顺序输入到REPL中,所以再调用LOAD后hello-world就应该被定义了:
CL-USER> (hello-world)
Hello, world!
NIL
另外一个方法加载文件有价值的定义式是先用COMPILE-FILE编译然后再LOAD编译好的文件,叫做FASL文件是fast-load file的简称。COMPILE-FILE返回FASL文件的名字。
CL-USER> (load (compile-file "hello.lisp"))
;;; Compiling file hello.lisp
;;; Writing fasl file hello.fasl
;;; Fasl write complete
; Fast loading /home/peter/my-lisp-programs/hello.fasl
T
(使用的CL实现不同fasl文件的后缀名有些不同,CCL中windows版本的后缀名是wx32fsl)
SLIME也支持在不使用REPL的情况下加载和编译文件。在一个源文件缓冲中,使用C-c C-l由slime-load-file命令加载文件。Emacs会提示文件名,加载已填入的文件名;所以可以直接输入回车执行。或者输入C-c C-k编译和加载当前缓冲代表的文件。在一些CL实现中,编译代码会快一些,另外的可能不会,因为它们会编译全部东西。
认真的Lisp黑客们经常在每天结束保持一个Lisp image,增量式的:增加,重定义和测试他们的一部分程序。
在26章会看到怎么使用REPL和SLIME交互的运行在Web服务器同时服务Web网页。它甚至可以用SLIME连接到Lisp运行在不同的机器上,允许远程调试就像本机调试一样。
1998年在NASA有个远程调试的事故,执行Deep Space1 任务时,太空船上远程调试并且修复了运行中的Lisp代码。