原文链接:https://norvig.com/luv-slides.ps
“毫无疑问,Common Lisp是一门庞大的语言”-- Guy Steele
但是什么才算语言本身呢?
不管怎样,Lisp程序员都需要一些帮助:
使用哪些内置功能
如何使用它
对于不想在重新加载时重新初始化的东西,使用defvar
。
(defvar *options* '())
(defun add-option (x) (pushnew x *options*))
在重新加载文件之前,您可能已经执行了许多次(add-option…)
,有些甚至是从另一个文件加载的。通常不希望因为重新加载这个定义而丢掉所有数据。
另一方面,某些类型的选项确实希望在重新加载时重新初始化…
(defparameter *use-experimental-mode* nil
"Set this to T when experimental code works.")
稍后,您可能会编辑此文件并将变量设置为T,然后重新加载它,希望看到编辑的效果。
建议:忽略CLtL中提到defvar
用于变量而defparameter
用于参数的部分。它们之间唯一有用的区别是,defvar
仅在变量未绑定时才进行赋值,而defparameter
无条件地进行赋值。
(eval-when (:execute) ...)
=
(eval-when (:compile-toplevel) ...)
(eval-when (:load-toplevel) ...)
当然要注意下显式嵌套的eval-when
表达式形式,对大多数人来说,这种效果通常不是直观的。
思考下面示例中对(f (g (h)))
的重复使用:
(do ((x (f (g (h)))
(f (g (h)))))
(nil) ...)
每次你编辑其中一个(f (g (h)))
,你可能也需要编辑另一个。这里有一个更好的模块化方式:
(flet ((fgh () (f (g (h)))))
(do ((x (fgh) (fgh))) (nil) ...))
(这可能被用作do
的参数。)
类似地,你可以使用局部函数来避免仅在动态状态上不同的代码分支中的重复。例如,
(defmacro handler-case-if (test form &rest cases)
(let ((do-it (gensym "DO-IT")))
`(flet ((,do-it () ,form))
(if test
(handler-case (,do-it) ,@cases)
(,do-it)))))
大型程序设计由一种设计风格支持,这种风格将代码分成具有明确设计接口的模块。
Common Lisp包系统用来避免模块之间的命名冲突,并且给每个模块定义接口。
(defpackage "PARSER"
(:use "LISP" #+Lucid "LCL" #+Allegro "EXCL")
(:export "PARSE" "PARSE-FILE" "START-PARSER-WINDOW"
"DEFINE-GRAMMAR" "DEFINE-TOKENIZER"))
有些人把导出的符号放在文件顶部定义它们的地方。
我们觉得最好将它们放在defpackage
中,并使用编辑器找到相应的定义。
Lisp通过提供一个活跃的状况系统来确保代码中的大多数错误不会破坏数据。
了解错误和状况之间的区别。
所有的错误都是状况;但并非所有的状况都是错误。
区别三个概念:
选择符合您意图的错误检测和处理级别。通常您不想让坏数据消失,但在许多情况下,您也不想因为无关紧要的原因而进入调试器。
在适合您的应用程序的宽容和挑剔之间取得平衡。
差的: 如果它不是整数怎么办?
(defun parse-date (string)
"Read a date from a string. ..."
(multiple-value-bind (day-of-month string-position)
(parse-integer string :junk-allowed t)
...))
可疑的: 如果内存耗尽怎么办?
(ignore-errors (parse-date string))
较好的: 只捕获预期的错误
(handler-case (parse-date string)
(parse-error nil))
差的:
(error "~%>> Error: Foo. Type :C to continue.")
较好的:
(cerror "Specify a replacement sentence interactively."
"An ill-formed sentence was encountered:~% ~A"
sentence)
从这开始
好的: 警告的标准用法
(defvar *word* '?? "The word we are currently working on.")
(defun lex-warn (format-str &rest args)
"Lexical warning; like warn, but first tells what word
caused the warning."
(warn "For word ~a: ~?" *word* format-str args))
好的: 处理具体的错误
(defun eval-exp (exp)
"If possible evaluate this exp; otherwise return it."
;; Guard against errors in evaluating exp
(handler-case
(if (and (fboundp (op exp))
(every #'is-constant (args exp)))
(eval exp)
exp)
(arithmetic-error () exp)))
好的: 提供重启动
(defun top-level (&key (prompt "=> ") (read #'read)
(eval #'eval) (print #'print))
"A read-eval-print loop."
(with-simple-restart
(abort "Exit out of the top level.")
(loop
(with-simple-restart
(abort "Return to top level loop.")
(format t "~&~a" prompt)
(funcall print (funcall eval (funcall read)))))))
unwind-protect
实现了每个人都应该知道如何使用的重要功能。它不仅仅适用于系统程序员。
不过,要注意多任务处理。例如,使用unwind-protect
实现某些类型的状态绑定可能在单线程环境中运行良好,但在多任务环境中,您通常必须更加小心。
(unwind-protect (progn form1 form2 ... formn)
cleanup1 cleanup2 ... cleanupn )
form1
会运行。formn
不会运行完成。通常,您需要在进入unwind-protect
之前保存状态,并在恢复状态之前进行测试:
可能不好: (带有多任务)
(catch 'robot-op
(unwind-protect
(progn (turn-on-motor)
(manipulate) )
(turn-off-motor)))
好的: (更安全)
(catch 'robot-op
(let ((status (motor-status motor)))
(unwind-protect
(progn (turn-on-motor motor)
(manipulate motor))
(when (motor-on? motor)
(turn-off-motor motor))
(setf (motor-status motor) status))))
print-unreadable-object
~{~A~^, ~}
和 ~:p
。~& versus ~%
,~&
开头,以~%
结尾。(format t "~&This is a test.~%")
This is a test.
←
和 →
用于处理缩进。*standard-output*
和 *standard-input*
对比 *terminal-io*
*standard-output*
和 *standard-input*
会被绑定到*terminal-io*
(或者,事实上,任何交互式流)。然而,你可以把它们绑定到这样一个流。*terminal-io*
用于输入输出。它主要作为一个流,可以被其他流所绑定,或者可以间接连接(例如,通过同义流 synonym stream )。*error-output*
对比 *debug-io*
*error-output*
用于没有任何用户交互的警告和错误提示。*debug-io*
用于交互式的警告和错误提示,其他与程序正常功能无关的交互。*error-output*
上打印消息,然后在*debug-io*
上执行调试会话。相反,在一个流上一致地进行每个交互。*trace-output*
trace
的输出。如果您编写的调试例行程序在不停止正在运行的程序的情况下有条件地打印有用的信息,请考虑对该流进行输出,以便如果*trace-output*
被重定向,您的调试输出也会被重定向。一个有用的测试:如果有人只重新绑定了您正在使用的几个I/O流中的一个,这会使您的输出看起来很愚蠢吗?