;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
函数化设计

物体的性质由组成物体的元素决定, 就如石头与木头造出的东西有不同手感,
lisp 函数的独特性, 也会让你写的程序拥有特别的架构.

函数化编程, 意味着程序是通过返回值来运作的, 而不是通过“边缘效应”.
边缘效应包括:1.破化性地改变物体, 或者 2.对变量赋值.

边缘效应越少, 程序就更易Debug, 更易测试, 也更易阅读.

(defun 破坏性逆序 (列表)
  (let* ((长度 (length 列表))
     (中限 (truncate (/ 长度 2))))
    (do ((i 0 (1+ i))
     (j (1- 长度) (1- j)))
    ((>= i 中限))
      (rotatef (nth i 理) (nth j 列表)))))

(setq 例子 '(1 2 3 4 5))
(破坏性逆转 例子) ; => nil
例子 ; => ( 5 4 3 2 1 )

(defun 好逆序 (列表)
  (labels ((递归 (列表 积累)
         (if (null 列表)
         积累
         (递归 (cdr 列表) (cons (car 列表) 积累)))))
    (递归 列表 nil)))

(好逆序 '( 1 2 3 4 5)) ; => (5 4 3 2 1)

从函数写出来的缩进, 就可以看出来函数的质量
函数化的编程风格, 依赖于表达式内的参数组合,
由于lisp的参数是被缩进的, 所以, 函数化编程的代码, 缩进表现出很多变化
而指令式编程, 缩进看起来会更死板, 代码是块状的, 像basic

注意:
上面的 '好逆序 函数的时间复杂度是O(n)
而 '破坏性逆序 的时间复杂度是O(n^2)

在 lisp 里, 内置的 reverse 函数是不改变参数的

如果我们想改变参数, 我们需要这样写
(setq 被改者 (reverse 被改者))

如果不用函数式编程, 会觉得 lisp 很不好用.

例子:
lisp 里提供的破坏性的逆转函数 nreverse
(setq 例子 '( 1 2 3 4 5))
(nreverse 例子)
例子
lisp 里面, 即使声明自己有边缘效应的函数, 也未必是特意使用边缘化的, 所以用法上, 坚持用函数化.

所以, 使用以下操作的时候, 要注意
set setq setf psetf psetq incf decf push pop pushnew rplaca rpalcd
rotatef shiftf remf remprop remhash let* 

在很多语言里, 边缘化主要是为了返回多个值.
在lisp里, 函数可以直接返回多个值, 比如
(truncate 28.21875)
当只有一个值被需要的时候, 会只取第一个返回值
(= (truncate 28.21875) 28) ; => T
当多个值被需要的时候, 可以用 multiple-value-bind 来取
(multiple-value-bind (整数部分 小数部分) (truncate 26.21875)
  (list 整数部分 小数部分))
如果想返回多个值, 使用 values
(defun 开方平方 (x)
  (values x (sqrt x) (expt x 2)))
(multiple-value-bind (底 平方根 平方) (开方平方 4)
  (list 底 平方根 平方))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
翻转祈使式编程

函数式编程: 告诉你, 你要的是什么
祈使式编程: 告诉你, 你需要做什么

比如:
函数式: 返回一个列表, 由 a 与 x 的第一个元素的平方组成
(defun 添a (x)
  (list 'a (expt (car x) 2)))
祈使式: 取x第一个元素, 算它的平方, 然后a与这个平方组成list, 返回
(defun 添个a (x)
  (let (y sqr)
    (setq y (car x))
    (setq sqr (expt y 2))
    (list 'a sqr)))

(添a '(4 2))
(添个a '(4 2))

学语言的时候, 大家没有学过函数式编程, 所以直接就用的是第二种.
lisp 是天然的函数式语言, 所以大家可以通过lisp学习函数式编程,
然后用别的语言来用这种方法来写程序.

我们现在来把"添个a"翻转过来.
先看最后一行
(list 'a sqr) 把 sqr 替换为   (expt y 2)
(list 'a (expt y 2)) 再把y替换为 (car x)
(list 'a (expt (car x) 2))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
函数接口

在这里, 我们使用nconc的时候, 用copy-list保护了参数, 以免被改变. 
(defun 数量 (表)
  (nconc (copy-list 表) (list '大表)))
所以, 边缘化并不一定全是坏事, 所更改的东西不被任何函数占据, 那就没有关系.

我们接下来看: 由函数调用产生的归属权

(let ((天生 0))
  (defun 持续增长( 地长)
    (incf 天生 地长)))

(持续增长 2)
(持续增长 20)
在以上例子中可以看到, 函数调用可以安全地修改它专属的数据

参数与返回值的归属权是怎样的?
函数的调用, 占据着作为返回值的结果
传给它的参数, 并不为它所有.

凡更改变了传给它的参数的函数, 被标记为: destructive
但是更改返回给它值的函数, 并没有什么特别

(defun 平凡 (因)
  (nconc (list 'a 因) (list 'c)))

如果稍微改一改
(defun 荒诞 (因)
  (nconc (list 'a) 因 (list 'c)))


(setq 事 (list 1 2 3))
(平凡 事)

(荒诞 事)

参数在这里已经被改变

函数也不可以与别的代码共享数据
(defun 瞎搞 (因)
  (+ 因 *乱* )) ; 这里有全局变量, 会被不知道谁给改了.

函数只去更改属于它的数据, 即是严格的函数化编程.
即用同样的函数两次调用函数, 它的返回值必须是一样的.
这条原则, 是自下而上编程的基础.

破坏性操作, 像全避变量一样, 会改变程序的自主性.
函数化编程, 可以将注意力集中, 只需要考虑调用的函数, 或调用它的函数
如果你破坏性地做更改, 那么影响就会无处不在.

上述的条件, 并不能保证, 函数化编程, 你就可以自主.
但可以大大改善情况.
如下:

(defun 我 (因)
  (let ((变化 (你 因)))
    ;; "我"在这里可以随便搞"变化"么?
    ;; 其实不可以, 如果 我 是 identity 因就直接传给了变化
    ))

从上面的例子可以出来, 函数不可以返回任何有改动危险的东西. 
所以, 不要返回包含引用对象的列表, 比如:

(defun 宣称 (表达)
  (append 表达  '(都 是 我 的)))
(宣称 '(衣 食 住 行))
(nconc * '(才 怪))
(宣称 '(天 地 君 亲 师))
注意: 这里的例子需要在sbcl的环境里跑, 在emacs的slime里不行.

需要改为
(defun 宣称 (表达)
  (append 表达  (list '都 '是 '我 '的))) ;; 每次生成新的挂接列表

还有一种情况,是可以返回引用对象列表的, 即生成宏展开的函数.
宏展开器可以安全地包含所生成的引用列表
尽管如此, 许多程序员还是不信任引用列表, 而是使用 in 宏.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
交互式编程

lisp 并不需要程序全写完, 然后统一测试, 而是可以一点点写, 一点点测
在 toplevel 里, 有很多信息, 可以在函数级测试.
所以函数化编程可以很快地写程序, 而且写出来的代码非常地可靠

有经验的程序员是这样写的:
1. 少数函数边缘化, 大量的函数都是纯的函数化的.
2. 如果函数是边缘化的, 那么至少要把它们做个函数化的接口
3. 给每个函数一个单一, 定义良好的目标