Lisp是一个古老的语言, clojure让这门语言在现代的互联网商业项目中重获生机.
由于Lisp历史久远, 在学习clojure之前, 最好还是探素一下Lisp这个历史宝藏.

我们的重点不在于学习Lisp的语法细节及第三方库, 而是研究它的精神和算法.

Lisp是一个可以改编的编程语言.
自下而上的设计, 而不是自上而下的设计
程序一层一层写, 每一层都是上一层的编程语言.

想真正了解一个语言, 解释是没用的, 只能通过使用它来了解它.

学本这篇文章, 需要先了解lisp的基本语法. 否则看不懂.

Lisp的精髓
程序即数据, 程序像数据一样, 也是可以由程序生成的.

一切从函数开始.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 定义函数
(defun 双倍 (x) (* x 2))
(双倍 2)

#'双倍 ; 用#'符号, 看"双倍"函数对象的内部表示

(eq #'双倍 (car (list #'双倍))) ; 函数可以作为参数, 也可以放到数据结构里面去

定义匿名函数
(lambda (x) ( *  x 2))  ; 匿名函数
#'(lambda (x) ( *  x 2)) ; 用#'符号, 看匿名函数对像
((lambda (x) ( *  x 2)) 3) ; 直接作用到参数3上

定义一个变量
(setq 双倍 2) ; 这个变量可以与函数名一样, lisp是适配的
(双倍 双倍) ; 函数和变量, lisp自己给区别开了, 所以不会报错.

看一个变量名的值
(symbol-value '双倍)
看一个函数名的值
(symbol-function '双倍)


(setq 双倍函数 #'双倍) ; 可以将函数赋给一个变量
(eq (symbol-value '双倍函数) (symbol-function '双倍)) ; 为真

看看defun的本质
(defun 三倍 (x) (* x 3))
(setf (symbol-function '三倍) #'(lambda (x) (* x 3))) ; 与上面是等价的
(三倍 4)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
函数参数

将函数视为数据时的调用方式
apply的两个参数: 调用的函数 将调用的函数的参数列表

(apply #'+ '(1 2))
(apply (symbol-function '+)
       '(1 2))
(apply #'(lambda ( x y) (+ x y)) '( 1 2))
apply 可适用多个参数, 函数应用于最后一个list参数, 前面的几个参数将作为函数参数,

(apply #'+ 1 2 '(3 4))
(apply #'+ 1 '(3 4))

apply 的最后的参数必须是list, 有点不方便,  funcall 无需把参数放在list里. 
(funcall #'+ 1 2 3 4)

lisp里有很多用函数作为参数的函数
(mapcar 函数 列表)
(mapcar #'(lambda (x) (+ x 1)) '( 1 2 3))
(mapcar #'+ '( 1 2 3) '(10 20 30))
在很多语言里, 想把函数作为参数, 需要在源代码里预先定义这个函数
(需要定义一个函数名), 但是用 lambda 用不需要起名字了. 

在lisp里, 有很多内置的函数都是使用函数作为参数的
比如 sort
(sort '( 1 5 2 3 4) #'>)

比如remove-if
(remove-if #'evenp '( 1 2 3  4 ))

(defun my-remove-if (fn lst)
  (if (null lst)
      nil
      (if (funcall fn (car lst))
      (my-remove-if fn (cdr lst))
      (cons (car lst) (my-remove-if fn (cdr lst))))))

(my-remove-if #'evenp '( 1 2 3  4 ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
函数作为属性
写一个动物行为的函数

以下是普通语言写法, 伪代码
(defun 行为 (动物)
  (case 动物
    (狗 (汪汪叫)
    (摇尾巴))
    (猫 (喵喵叫)
    (抓沙发))))

以下是lisp的习惯写法, 伪代码
(defun 行为 (动物)  (funcall (get 动物 '行为)))

(setf (get '狗 '行为)      #'(lambda ()  (汪汪叫)      (摇尾巴)))

在lisp的写法中, 将行为定义在对象的属性里, 可以随时添加, 不需要更改函数
面向对象编程中, 通过继承来实现的可扩展性, 在lisp里, 不需要继承机制就可以出来

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
scope 作用域

common lisp 是词法作用域, 与scheme一样
最古老的lisp则是动态作用域
两者的区别在于, 如何对待自由变量

symbol 作为变量声明的时候, 在表达式里进行绑定
比如: 作为函数参数, 或者在 let 或 do 语句里
没有绑定的 symbol 称为 自由symbol

(let ((y 7))
  (defun scope-test (x)
    (list x y)))

在函数 scope-test 里的 y, 就是自由变量.
x 的值是由调用时候的参数传入的, y 呢? y的值, 即是由作用域的规则来决定的. 

调用时:
(let ((y 5))
  (scope-test 3))

在动态作用域规则中, y 调用的时候就成了 5, 而不是定义时的7.

在词法作用域中, scope-test 中的 y 永远是 7 .

现在可以用 special 来声明变量为动态作用域.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
闭包

通过词法作用域, 含自由变量的函数, 系统会记录定义时候的绑定值
函数与绑定变量的组合, 称为闭包
闭包非常地有用

其实, 每次 mapcar 的时候就已经用闭包了.
而自己还没有意识到, 如下
(defun list+ (lst n)
  (mapcar #'(lambda (x) (+  x n))
      lst))
这里, 在lambda 里的 n 就是闭包了
每次调用 list+ 的时候 就创建了一个新的闭包


闭包还有更引人注目的运用.
闭包是带着局部状态的.
(let ((分数 0))
  (defun 加分 () (incf 分数))
  (defun 归零 () (setq 分数 0)))

全局变量也可以达到同样的效果
但是可能会引入bug 

将带着局部状态的函数作为返回值, 也是一种极强大的手段.  比如:
(defun 加法器 (n)
  #'(lambda (x) (+ x n)))

(setq 加二 (加法器 2)
      加十 (加法器 10))

(funcall 加二 5)
(funcall 加十 5)

在上述的加法器中, 内部的状态是固定的.
在下面的加法器中, 内部的状态是可以换的.

(defun 加法神器 (n)
  #'(lambda (x  &optional 可变)
      (if 可变
      (setq n x) ;; 这里改变了加数
      (+ x n )))) ;; 这里是做正常的加法

(setq 加几 (加法神器 1)) ;; 调用加法神器的时候, n 被保护了. 
(funcall 加几 3)

(funcall 加几 100 t) ;; 被保护的n, 只能由定义时的办法修改, 这里加法器里的加数已经变喽
(funcall 加几 4)
(funcall 加几 10 t) ;; 这里加法器加数又已经变喽
(funcall 加几 4)


可以设计多个闭包, 一起共享同样的数据
(defun 建立数据库 (数据库)
  (list
   #'(lambda (key)
       (cdr (assoc key 数据库)))
   #'(lambda (key val)
       (push (cons key val) 数据库))
   #'(lambda (key)
       (setf 数据库 (delete key 数据库 :key #'car))
       key)))

这个数据库, 一个list里放了三个匿名函数, 共享了一个叫"数据库"的东西

(setq 城市 (建立数据库 '((上海 . 中国) (北京 . 中国))))
(funcall (car  城市) '北京 )

上面用car来调用的方式是比较土的.
现实里是不会把访问函数放到list里来调的. 应该是如下:
(defun 查询 (关键字 数据库) (funcall (car 数据库) 关键字))

闭包是lisp的一种特别的工具. 用别的编程语言根本没有办法搞出类似的功能.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
局部函数

当我们用lambda 表达式来定义函数的时候, 定义出来的函数没有名字.
这意味着: lambda 表达式没有办法定义出递归函数

想用闭包的话, 使用defun定义全局函数就不是很方便
这个时候, 使用labels, 为局部函数命名.
(labels ( (法 (因) (1+ 因)))
  (法 100))

与let不同, labels 定义的函数可以互相调用, 也可以调用自己
注意, 函数列表里, 顺序无所谓的. 前面的可以调用后面的. 
(labels (  (加二 (因) (加一 (加一 因))) (加一 (因) (+ 1 因)))
  (加二 3)) 

(defun 相同结点个数 (结点 原理)
  (labels ((结点数 (理)
         (if (consp 理) ; 若是树, 则遍历
         (+ (if (eq (car 理) 结点) 1 0) ; 若找到则加1, 否则加0
            (结点数 (cdr 理))) ; 再加上, 接着找到的个数
         0))); 不是树, 则为0
    (mapcar #'结点数 原理)))

(相同结点个数 'a '((a b c) ( d a r p a) ( d a r) (a a)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
尾递归
尾递归指函数的最后一步, 是调用它自己

(defun 长度 (理)
  (if (null 理)
      0
      (1+ (长度 (cdr 理)))))
这个函数不是尾递归, 因为最后一步是 1+

(defun 寻找 (法 理)
  (if (funcall 法 (car 理))
      (car 理)
      (寻找 (cdr 理))))
这个函数是尾递归. 

编译器会对尾递归进行优化, 自动优化为循环.

不是尾递归的函数, 总是可以通过嵌入一个局部函数转换为尾递归函数
这个局部函数, 使用一个"积累"变量

比如, 我们可以这样改写上面的长度函数

(defun 长度 (理)
  (labels ((递归 (理 积累)
         (if (null 理)
         积累
         (递归 (cdr 理) (1+ 积累 )))))
    (递归 理 0)))
(长度 '(我 是  中  国 人))
展开列表的时候, 直接记录积累值, 这样我们就不需要通过回退的时候来构建积累值.

lisp 编译器可以把程序编译得很快, 但是我们现在不学这种写法
我们学着很快地写程序, 然后再学怎么写跑得快的程序

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
编译

lisp 函数可以单独编译, 也可以按文件来编译

(defun 例子 (x) (1+ x))

可以查看这个函数是否是编译过的
(compiled-function-p #'例子)

(compile '例子)

解释出来的函数, 与编译出来的函数, 都是lisp的对象, 行为也是一样的
不同的是, 被 compiled-function-p 判断的结果不同. 

compile 的第一个参数, 应该是名字. 如果第一个参数是 nil, 那么第二个参数就是lambda表达式.
(compile nil '(lambda (x) (1+ x)))

如果同时给compile 两个参数, 那么compile就成了编译功能的defun
(progn (compile '加三 '(lambda (x) (+ x 3)))
       (compiled-function-p #'加三))


compile 与 eval 一样 在平时写程序的时候, 不常用.

有两种函数, 不能作为compile的参数
一种是 依赖上下文解释的函数, 比如, 全赖于let变量的

(let ((y 2))
  (defun 不能编译 (x) ( +  x y))
  ; (compile #'不能编译)
  )

一种是已经被编译过的函数, 这种函数作为参数的后果在标准上面没有定义.

平时用的编译函数是全文件编译: compile-file
以文件名为参数, 生成编译后的文件.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
作为列表的函数

在lisp的早期方言中, 函数以列表的形式来表示的
这个时代, 让程序序们可以组装他们自己的lisp程序.
在common lisp里, 函数不再由列表组成, 而是将它们编译为机器码.
但是我们依然可以写出来生成程序的程序, 因为编译器的输入还是列表.