Common Lisp

Lisp的两大方言之一, 另一个是Scheme.

表达式是否会立即求值将带来很大的差异, 如何在Lisp里区分宏, 特殊操作符, 函数?

可读/写性差:

  • 括号太多, 即使在编辑器辅助下编写代码也很困难.
  • 缩进不统一, Lisp大量使用"视具体情况对齐代码"风格的缩进, 这使得编辑代码变得困难.
  • 前缀表示, 导致一些基本的运算符调用起来很别扭.

S-表达式的本质是AST, Lisp相当于是一种直接以AST形式进行代码编写的语言.

; 并非所有S-表达式都是合法的Lisp表达式
(foo 1 2); Lisp表达式
("foo" 1 2); 不是Lisp表达式, 因为第一个元素是字符串

Lisp中存在一些特殊操作符, 这些特殊操作不会像一般的表达式那样求值.
除了特殊操作符, 宏也可以避免像表达式那样求值

(quote (+ 1 2)); 创建符号引用(也被用作阻止立即求值), 它的缩写是'
(function +); 创建函数引用, 它的缩写是#'(sharp-quote)
; 例子
(apply #'+ '(1 2 3)); 反射式调用+运算符, 即JavaScript的apply
(funcall #'+ 1 2 3); 即JavaScript的call
; Lisp允许任何非空白符作为符号名
(let ((x 1) (y 2)); 局部变量
(+ x y))
(defparameter *glob* 99); 全局变量, 星号是Lisp全局变量的一种命名约定
(defconstant limit 100); 全局常量
(boundp '*glob*); 谓词, 检查是否为全局变量/常量
; 由于Lisp主要使用函数式范式, 对变量赋值(产生副作用)的场合很少.
(setf *glob* 98); 对变量赋值
; 如果setf的第一个参数不是局部变量, setf会隐式创建全局变量
(setf a 'b
c 'd
e 'f); 同时赋值多个变量

一种(通常)不对自身求值的标识.

'Artichioke; 这是一个对Artichioke符号的引用(quote), 会以ARTICHOKE的形式输出
'|Lisp 1.5|; 带有空格的符号需要在两旁加上|
(symbol-name 'abc); "ABC" 获得符号名(字符串)
; 每个符号都有一个属性表, 称为plist, 可以存一些具有反射意义的数据
(setf (get 'alizarin 'transparency) 'high)
(symbol-plist 'alizarin); (TRANSPARENCY HIGH)
; 以quote形式创建列表(常用)
'(1 2 (+ 1 3)); (1 2 (+ 1 2) 用单引号表示的列表
`(1 2 (+ 1 2)); (1 2 (+ 1 2) 用反引号表示的列表
; 与单引号不同的是, 反引号可以用,(逗号)与,@(comma-at)对其中的一部分求值
`(1 2 ,(+ 1 2)); (1 2 3) 用逗号求值反引号里的列表项
;,@会在求值后展开列表
(setf lst '(a b c)
`(its elements are ,@lst); (ITS ELEMENTS ARE A B C)
; 用list函数创建列表(极少使用)
(list 1 2 3); (1 2 3)
(list :a 1 :b 2 :c 3); (:A 1 :B 2 :C 3)
(getf (list :a 1 :b 2 :c 3) :a); 1
(list 'getf 'cd filed); 用quote防止对列表项求值
; 空列表
(); NIL
nil; NIL
; null函数用于判断列表是否为空
(null lst)
; cons函数把新元素建立在已有的列表之上(本质上是Cons对象的链接)
(cons 'a '(b c d)); (A B C D)
; list函数的本质就是嵌套的cons的语法糖
(cons 'a (cons 'b nil)); 等价于 (list 'a 'b)
; car函数返回第一个列表元素
(car '(a b c)); A
; cdr函数返回第一个列表元素以外的元素
(cdr '(a b c)); (B C)
; 通过混合使用car和cdr这两个基本函数, 可以从列表中取出任何元素
; 重复执行cdr x次
(nthcdr 2 '(a b c); (C)
; 取出列表中的最后一个Cons
(last '(a b c)); (C)
; 根据索引取出列表元素
(nth 1 '(a b c)); B
(setf lst '(c a r a t))
(remove 'a lst); (C R T) 创建一个去除了'a的新列表
; 判断x是否引用相等
(setf x (cons 'a nil))
(eql x x); T
(eql x (cons 'a nil)); NIL
; 判断两个cons是否元素相等
(equal x (cons 'a nil)); T
(mapcar #'(lambda (x) (+ x 10)); 映射
'(1 2 3)); (11 12 13)
(mapcar #'list; mapcar可以接受多个列表, 此代码的效果相当于zip
'(a b c)
'(1 2 3 4)); ((A 1) (B 2) (C 3))
(defstruct point
x
y); 在创建结构时, 会隐式定义相关的函数, 此例定义了make-point, point-p, copy-point, point-x, point-y
(setf p (make-point :x 0 :y 0)); #S(POINT X 0 Y 0)
(point-x p); 0
(setf (point-y p) 2); 2
; Lisp以t为真值, nil为假值.
; 任何非nil的值都被当作真值.
; 返回值为真或假的函数在Lisp里被称为谓词(predicate)
; 这一称呼延续到了其他编程语言, 常见的filter函数的参数就是谓词.
; 逻辑运算
(not nil); T
(and t t); T, 这是一个宏, 具有短路逻辑
(or t nil); T, 这是一个宏, 具有短路逻辑
(if (listp '(a b c)); listp函数: 如果参数是列表, 返回t
(+ 1 2)
(+ 5 6))
(when (oddp that); 相当于只有then-form的if, 与when相对的是unless
(format t "Hmm, that's odd.")
(+ that 1))
; 等同于
(if (oddp that)
(progn
(format t "Hmm, that's odd.")
(+ that 1)))
(defun our-member (obj lst)
(cond ((atom lst) nil); cond相当于具有连续多个条件判断的if
((eql (car lst) obj) lst)
(t (our-member obj (cdr lst)))));
; 等同于
(defun our-member (obj lst)
(if (atom lst)
nil
(if (eql (car lst) obj)
lst
(our-member obj (cdr lst)))))
(defun month-length (mon)
(case month; switch-case
((jan mar may jul aug oct dec) 31)
((apr jun sept nov) 30)
(feb (if (leap-year) 29 28))
(otherwise "unknown month"))); otherwise/t, 省略的话会默认返回nil
(progn
(format t "a")
(format t "b")
(+ 11 12))
(block head; 带有返回功能的区块, head为区块名(函数的区块名是函数名)
(format t "Here we go.")
(return-from head 'idea); 从head区块中返回'idea, 后续代码不再执行
(format t "We'll never see this."))
(block nil; 大多数接受多个表达式(代码行)作为主体的操作符(例如dolist), 都隐含了名为nil的区块
(return 27)); return宏会自动从名为nil区块的返回, 主要用于隐含nil区块的函数
(defun foo()
(return-from foo 27))
(let ((x 7)
(y 2)
(format t "Number")
(+ x y))
(let* ((x 1); let*用于需要由引用另一个参数的值进行初始化的场合
(y (+ x 1)))
(+ x y))
; 迭代
(defun show-squares (start end)
(do ((i start (+ i 1))); do宏的第一个参数是(variable initial update)
((> i end) 'done); do宏的第二个参数是(测试迭代是否结束的表达式, do宏的返回值)
(format t "~A ~A~%" i (* i i)))); do宏的剩余参数是循环体
; 递归版本
(defun show-squares (i end)
(if (> i end)
'done
(progn; progn接受任意数量的表达式, 返回最后一个表达式的值
(format t "~A ~A~%" i (* i i))
(show-squares (+ i 1) end))))
; 迭代列表
(defun our-length (lst)
(let ((len 0))
(dolist (obj lst); dolist宏的第一个参数是(variable list)
(setf len (+ len 1)))
len))
; 递归版本
(defun our-length (lst)
(if (null lst)
0
(+ (our-length (cdr lst)) 1)))
(defun hello-world ()
(format t "hello, world"))
(hello-world); 调用
; lambda是一个符号, 作为列表的第一个元素表示该列表是一个函数
(lambda (x y)
(+ x y))
; Common Lisp 不需要lambda就可以表示函数, 但出于习惯往往还是会用lambda
((x y)
(+ x y))
((lambda (x) (+ x 100) 1); 调用匿名函数
#'(lambda (x) (+ x 100)); 获得匿名函数的引用
(funcall #'(lambda (x) (+ x 100))
1); 调用匿名函数的引用
(setf (symbol-function 'add2); 给匿名函数一个函数名
#'(lambda (x) (+ x 2)))
(defun foo (x)
"Implements an enhanced paradigm of diversity"; 文档字符串
x)
(documentation 'foo 'function); 输出文档字符串
(member '(a) '((a) (z)) :test #'equal); ((A) (Z)) :test #'equal是关键字参数
; 捕获异常
(defun super ()
(catch 'abort
(sub)
(format t "We'll never see this.")))
(defun sub ()
(throw 'abort 99))
; 局部函数
; (labels (name parameters . body) 剩余部分是区块)
(labels ((add10 (x) (+ x 10))
(consa (x) (cons 'a x)))
(consa (add10 3)))
; 变长参数
(defun our-funcall (fn &rest args)
(apply fn args))
; 可选参数
(defun philosoph (thing &optional property)
(list thing 'is property))
; 有默认值的可选参数
(defun philosoph (thing &optional (property 'fun))
(list thing 'is property))
; 关键字参数
(defun keylist (a &key x y z)
(list a x y z))
; 动态作用域(Lisp默认使用闭包, 即词法作用域)
(let ((x 10))
(defun foo()
(declare (special x)); 通过special声明动态作用域
x))
(let ((x 20))
(declare (special x))
(foo)); 20
; 输出
(format t "~A plus ~A equals ~A. ~%" 2 3 (+ 2 3))
; 打印 2 plus 3 equals 5.
; 第一个参数的t表示输出到缺省的设备上(屏幕)
; 输入
(read)

Lisp宏与其他语言的宏的最大区别在于Lisp的宏展开是语法的一部分,
而非独立于语言之外的字符串模板,
因为Lisp是一种源代码与AST具有相同表示法的语言.

(defmacro nil! (x); 定义一个会把(nil! a)翻译成(setf a nil)的宏
(list 'setf x nil))
(nil! x); NIL
; 展开宏
(macroexpand-1 '(nil! x)); (SETF X NIL)