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防止对列表项求值; 空列表(); NILnil; 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具有相同表示法的语言.
而非独立于语言之外的字符串模板,
因为Lisp是一种源代码与AST具有相同表示法的语言.
(defmacro nil! (x); 定义一个会把(nil! a)翻译成(setf a nil)的宏 (list 'setf x nil))(nil! x); NIL; 展开宏(macroexpand-1 '(nil! x)); (SETF X NIL)