lisp.org
Table of Contents
1 <practical common lisp> export
1.1 实用技巧
将光标旋转在源代码中一个宏形式的开括号上,并输入C-c RET ,将把宏形式传递到函数MACROEXPAND-1 上,并将结果“美化输出”到一个临时缓冲区上。
1.2 package
1.3 cl-ppcre 正则表达式库
1.4 第3章 简单的数据库
从宏where的定义可以看出,宏函数的调用过程分两步,目标是引出一个可供执行的普通S表达式SS,但第一步是宏函数会将其中可以执行的部分执行(展开为最终S表达式SS的一部分)。
由此可以知道,在定义某个宏函数A的过程中,应始终从想到得到的表达式SS出发,构造整个宏函数(其内部可能需要封闭调用中层的函数B和底层的函数C),可以断言,宏函数定义中的顶层函数C(宏)和底层函数A必然以反引号开头,而中层函数B主要是用于帮助封闭A和并将其进一步转换为C所需要的符号表达式。
考虑到宏函数机制的复杂性,应该避免在其内部制造超过3层的函数调用深度。毕竟,宏函数主要做的,也只是通过LISP控制构造来生成代码,以缩减代码数量。
(defun make-comparison-expr (field value) `(equal (getf cd ,field) ,value)) (defun make-comparisons-list (fields) (loop while fields collect (make-comparison-expr (pop fields) (pop fields)))) (defmacro where (&rest clauses) `(lambda (cd) (and ,@(make-comparisons-list clauses))))
从下一个例子宏when的定义中可断言,宏函数A的所有参数在A的定义式内部出现时,必然是被comma逗号求值的。
(defmacro my-when (pre &rest clauses) `(if ,pre (progn ,@clauses)))
1.5 第5章 参数、函数和宏
1.5.1 可选形参
(defun foo (a b &optional (c 3 c-supplied-p) (list a b c c-supplied-p)))
1.5.2 剩余形参
如果函数带有&rest形参,那么任何满足了必要和可选形参之后的其余所有实参就将被收集到一个列表里成为该&rest形参的值。
1.5.3 关键字形参
关键字形参比前两者都更为灵活,可便于函数调用者直接指定某个形参所绑定的实参,且形参的赋值将是非必须的(类似可选形参)。
(defun foo (&key a b c) (list a b c ))
(foo ) –> (nil nil nil)
(foo :a 1 :c 3) –> (1 nil 3)
1.5.4 return-from
(defun foo (n) (dotimes (i 10) (dotimes (j 10) (if (> (* i j) n) (return-from foo (list i j))))))
1.5.5 loop
(defun plot (fn min max step) (loop for i from min to max by step do (loop repeat (funcall fn i) do (format t "*")) (format t "~%")))
1.5.6 funcall 和apply 的区别
和funcall一样,APPLY的第一个参数是一个函数对象。但在这个函数对象之后,APPLY期待一个列表而非单独的实参。它将函数应该在列表中的值上。APPLY比FUNCALL更方便之处还在于,APPLY还接受孤立的实参,只要最后一个参数是个列表。
1.6 第9章 宏的BUG,变量捕捉
for宏的错误来自于宏定义中定义和显式地使用了局部变量limit的名字,而该名字在运行期可能与其他代码(如由特殊形式do或其他嵌套使用的宏所定义的局部变量名字重复!)
(defmacro for ((var start stop) &body body) ; wrong `(do ((,var ,start (1+ ,var)) (limit ,stop)) ((> ,var limit)) ,@body)) (defmacro for ((var start stop) &body body) ;correct (let ((gstop (gensym))) `(do ((,var ,start (1+ ,var)) (,gstop ,stop)) ((> ,var ,gstop)) ,@body)))
1.7 第8章 如何定义宏
1.7.1 基础宏
另一个定义宏的原则是,宏函数的参数总是不需要被quote引用的。比如一个宏的形式为如下将是不必要和错误的。
(mac '(while (not (null lst)) (print lst)(pop lst)))
P97 当LISP被解释而非编译时,宏展开期和运行期的区别不甚明显。
(defmacro do-primes (var-begin-end body) (let ((var (car ,var-begin-end)) (begin (cadr ,var-begin-end)) (end (caddr ,var-begin-end))) `(do ((,var (next-primep ,begin) (next-primep (1+ ,var)))) ((> ,var ,end)) ,body)))
如下例,当宏函数有多个形参(这几个形参往往有完全不同的表达形式),编写宏并不像看上去那样容易。
首先,不得不利用宏定义函数defmacro 的自动解构能力,将用深层表表示的形参临时绑定,然后,将剩下的部分用剩余参数&rest body来绑定。
由此可见,当某个宏函数的定义需要使用到多个变量v1 v2 v3..时,应该尽量将它们用括号包装起来,便于在宏函数的定义和使用过程中解构变量和增加程序可读性、及时报错(当宏函数在展开期解构形参列表时)。
(defmacro do-primes ((var begin end) &rest body) `(do ((,var (next-prime ,begin) (next-prime (1+ ,var)))) ((> ,var ,end)) ,@body))
1.8 !!!! 第11章集合
向量中包括纯粹的向量和特化向量。
1、字符串,属于特化向量。
2、位向量,属于特化向量。它是由0或1组成的向量。
向量和列表是抽象类型序列的两种具体子类型。
以下用于序列的函数也o可用于向量和列表:length ,elt ,count,find ,position,remove,substitute,它们可以接受的函数关键字参数有 :test,:key,:start,:end,:from-end,:count
1.8.1 序列映射函数
(map 'vector #'* #(1 2 3) #(3 4 5)) --> #(3 8 15) ;和mapcar似无区别 (reduce #'+ '(1 2 3 4)) --> 10 ;将一个两参数函数用于列表的最初两个元素上,再将函数返回值和下一个元素用于该函数直到列表末尾,返回一个结果
1.9 变量????
1.9.1 词法变量和闭包????
理解闭包的关键在于,被捕捉的是绑定而不是变量的值。
(defparameter fn
#'(lambda () (setf count (1+ count))))
1.9.2 6.5赋值????
(defun foo (x) (setf x 10))) (let (( y 20)) (foo y) (print y))
将打印出20而非10,因为传递给foo的y的值在该函数中变成了的值,随后又被setf设置成新值。
1.10 流程控制
1.10.1 强大的loop
(loop for i from 1 to 10 collecting i) (loop for x from 1 to 10 summing (expt x 2)) (loop for x across "the quick brown fox jump over the lazy dog " counting (find x "aeiou")) ;计算字符串中的元音字母 (loop for i below 10 and a = 0 then b and b = 1 then (+ b a) finally (return b)) ;计算斐波那契数 (defun primep (number) (when (> number 1) (loop for fac from 2 to (isqrt number) never (zerop (mod number fac))))) ;use loop function to estimate whether a number is prime number
across,and,below,collecting,counting,finally,for,from,summing,then,to is all keywords for iteration,they allways appear with loop .
1.11 宏
宏避免漏洞的三个规则:
1,出而非有特殊理由,否则需要将展开式中的任何子形式放在一个位置上,使其求值顺序与宏调用的子形式相同。
2,除非有特殊理由,否则需要确保子形式仅被求值一次,方法是在展开式中创建变量来持有求值参数形式所得到的值,然后在展开式中所有需要乃至该值的地方使用这个变量。
3,在宏展开期使用GENSYM来创建展开式中用到的变量名。P88
(defmacro check (form) `(report-result ,form ',form)) ;注意本行末的 ',form 代表先对形参form求得其实参,然后将其quote起来
1.12 第14章 文件和I/O
(pathname-device "d:/working/t.txt")返回驱动器字母d
同理还有pathname-directory、pathname-name、pathname-type函数
(pathname "d:/working/") -> #P"d:/working/"
一般在路径名不完整时,LISP实现将为路径名自动添加默认的目录部分,即:
default-pathname-defaults -> #P"d:/working/"
1.12.1 构造新路径名
(make-pathname
:directory '(:absolute "foo" "bar")
:name "baz"
:type "txt") ;-> #p"/foo/bar/baz.txt"
为了程序易移植,不推荐完全手工生成路径名。
(make-pathname :type "html" :defaults input-file)基于已有的路径名使用MAKE-PATHNAME的关键字参数:defaults来构造新路径名。创建了一个带有.html扩展名的路径名,同时所有其他组件都与变量input-file中的路径名相同。
1.12.2 列目录
(directory (make-pathname :name :wild :type :wild :defaults "D:/working/")) ;列出d:/working/目录下的所有文件
(wild-pathname-p (make-pathname :name :wild)) -> T
(pathnamep #P"d:/working/t.lisp") -> T
(probe-file #P"d:/work.lisp") -> Nil
;在路径d:/working/中,对于路径名的name组件为bri的所有路径名,打印它们。 (walk-directory "d:/working/" #'(lambda(n) (print n)) :test #'(lambda(na) (equal "bri" (pathname-name na))))
1.13 第18章 format
(format nil "~20,8f" pi)
将pi按浮点数输出,小数点后8位,输出的字符串首有空格(以保障整个字符串占20个空位)
1.14 第20章 包和符号
包是一个字符串和符号的映射表。
含有单冒号或双冒号的名字是包限定的名字。只含单冒号的名字必将指向一个外部符号(external symbol)——一个被包导出(export)作为公共接口来使用的符号。含双冒号的名字可以指向命名包中的任何符号。
比如在common-lisp-user中定义(setq a 3),那么即在common-lisp-user中定义了名字为"a"的符号,其值为3,注意默认符号a在common-lisp-user中是未导出(external)的,即只能通过common-lisp-user::a访问,而非common-lisp-user:a
读取器用来访问包中的名字-符号映射的两个关键函数是find-symbol和intern.它们都接受一个字符串和一个可选的包,当包未指定时,包参数默认为全局变量*package*的值,也称为当前包。
所有可在一个给定包中通过FIND-SYMBOL找到的符号被称为在该包中是可访问的。换句话说,一个包中可访问的符号是,该包为当前包时非限定名字指向的符号。
可以通过两种方式访问一个符号。前一种方式要求包的“名字-符号表”中含有该符号的项,这时我们称该符号存在(present)于该包中。当读取器让一个新符号进入一个包时,该符号会添加到包的名字-符号表中。该符号首先停留的包称作该符号的主包。(home package)。
另一种方式是当某个包继承一个符号时,该符号在该包中就是可访问的。一个包通过使用(use)其他包来继承这些包中的符号。在被使用的包中,只有外部(external)符号才能被继承。可以通过在包中导出(export)上符号来使其成为外部符号。导出操作除了可以使包的其他使用者继承符号以外,还可以使其能够通过带有单冒号的 限定名称 来引用。
1.14.1 关键字符号
关键字符号在书写上以一个冒号开始,这类符号在名为keyword的包中创建并自动导出。当读取器在kewword包中创建一个符号时,它也会定义一个以该符号作为其名字和值的常量。
(eql ':foo :foo) -> T
(symbol-name :foo) -> "FOO"
1.14.2 如何定义包
*package*储存当前使用的包名
1.14.3 使用包
1.14.4 导出、隐蔽和导入符号
1.14.5 三个标准包
cl-user为启动时默认的包,CL-USER使用了包COMMON-LISP,后者导出了语言标准定义的所有名字。因为,当在REPL中键入一个表达式时,所有诸如标准函数、宏、变量之类的名字都将转化成从COMMON-LISP包中导出的符号,而其他名字进入到COMMON-LISP-USER包中。名字*PACKAGE*一般是从COMMON-LISP包中导出的。
第三个标准包是KEYWORD包,它被LISP读取器用来创建以冒号开始的名字。你也可以使用显式的包限定方式引用任何关键字符号:
(eql :a keyword:a) -> T
1.14.6 核实符号来源的包
要想查出一个符号最初来源于哪个包,可求值下列表达式:
(package-name (symbol-package 'car)) -> "COMMON-LISP"
(package-name (symbol-package 'foo)) -> "COMMON-LISP-USER"
要想看到在一个特定的实现中COMMON-LISP-USER都从哪些包中继承了符号,可以在REPL中求值下列表达式:
(mapcar #'package-name (package-use-list :cl-user))
1.14.7 定义你自己的包
使用COMMON-LISP-USER包对于在REPL中进行尝试是好的,不过一旦你开始编写实际程序就会需要定义新的包,这样不同的程序可以加载到同一个lisp环境中,而不会破坏彼此的名字。而当你编写可能用于不同环境中的库时,你会想要定义分开的包并导出那些构成了库的公共API的符号。
你可以通过宏DEFPACKAGE来定义新的包,在创建包的同时还能指定它使用哪些包,导出哪些包,以及它从其他包里导入什么符号,还可以创建隐蔽符号来解决冲突。
(defpackage :com.gigamonkeys.email-db (:use :common-lisp)) (in-package :com.gigamonkeys.email-db) (defpackage :com.gigamonkeys.text-db (:use :common-lisp) (:export :open-db :save :store))
这段代码定义了一个包,其继承了由COMMON-LISP包导出的所有符号。
然后定义一个新的包,将导出一些特定的名字,从而使其对于其他包可见。
1.14.8 导入单独的名字
(defpackage :com.gigamonkeys.email-db (:use :common-lisp :com.gigamonkeys.text-db :com.acme.text) (:import-from :com.acme.email :parse-email-address) (:shadow :build-index))
假设你找到了一个可以处理E-mail消息的第三方函数库。该库的API所使用遥名字是从COM.ACME.EMAIL包中导出的,因此你可以通过使用这个包来轻松获得对这些名字的访问权限。但假设你只需要乃至这个库的一个函数,且其他的导出符号跟你已经使用的符号有冲突。此时,你可以用DEFPACKAGE中的:import-from子句只导入所需要的那一个符号parse-email-address.
这个:shadow 子句指明了不需要从使用的库中继承的符号build-index。如果你以后使用了另一个同样导出了BUILD-INDEX符号的包,包系统将会知道这里没有冲突,也就是说你问题希望使用来自COM.GIGAMONKEYS.EMAIL-DB的符号,而不是从其他包里继承的同名符号。
1.15 第26章 用AllegroServer进行WEB编程
以上就是99%的服务器端WEB编程所依赖的基础元素。浏览器发起一个请求,服务器查找用来处理该请求的代码并运行它,然后代码使用查询参数和cookie来决定要做什么。