在了解到 React 是借鉴了函数式,而 Lisp 才是正宗的函数式之后。
我学习了 《计算机程序的构造和解释》(以下简称《SICP》), 了解什么是真正的函数式编程。
这篇文章是读完《SICP》第一章之后写的,主要包括下面两个方面:
- LISP 简单语法
- 什么是递归
Lisp 基本语法
算术运算表达式
> (+ 1 2) // 1 + 2
< 3
> (+ 1 2 3 4)
< 10
> (+ (* 3 3) (* 4 4)) // 3 * 3 + 4 * 4
< 25
组合的表达式
较长的表达式,也叫组合的表达式。
和单独表达式运算一样,不过多了几个括号。
(+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))
(+ (* 3
(+ (* 2 4)
(+ 3 5)))
(+ (- 10 7)
6))
命名
也叫定义,不叫赋值。
< (define size 5)
>
< (* size 2)
> 10
if
(if 条件
条件为真的返回值
条件为假的返回值)
(define (absolute x)
(if (< x 0)
(- x)
x))
复合过程
就是定义一个函数。
< (define (add a b) (+ a b))
< (add 1 2)
> 3
< (define (square x) (* x x))
< (square 4)
> 16
上面列出的表达式有两种求值的办法,递归求值和代入法求值。
递归求值
- 求各子表达式的值,也就是右侧值
- 将所有右值应用到运算符上得到最终结果
这种求值规则被称为「递归求值」。下面是递归求值的一个例子
(* (+ 2 (* 4 6))
(+ 3 5 7))
------------------
(* (+ 2 24)
(+ 15))
------------------
(* 26 15)
代入法求值
递归求值可以用来求一些直接的、简单表达式的值,如果定义了多个函数呢?
就需要用到代入法求值。
(define (square x) (* x x)) // square(x) = x * x
(define (sum-of-square x y)
(+ (square x) (square y))) // sum-of-square(x,y) = x*x + y*y
(define (f a)
(sum-of-square (+ a 1) (* a 2))) // f(a) = (a+1)*(a+1) + 2a * 2a
(f 5)
这个时候我们如何的得到 (f 5) 的值呢?答案是直接带入代码,替换变量。
需要注意的是:
- 代入只是为了方便我们理解函数,并不一定是实际工作原理,因为实际原理更复杂。
- 只有满足给定一个值,返回的值是确定的函数。才可以使用代入法。
计算的形状
在树形组件中,一定会遇到递归的情况。
递归
求 n!
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
(factorial 6)
(* 6 (factorial 5))
(* 6 (* 5 (factorial 4)))
(* 6 (* 5 (* 4 (factorial 3))))
(* 6 (* 5 (* 4 (* 3 (factorial 2)))))
(* 6 (* 5 (* 4 (* 3 (* 2 (factorial 1))))))
(* 6 (* 5 (* 4 (* 3 (* 2 1)))))
(* 6 (* 5 (* 4 (* 3 2))))
(* 6 (* 5 (* 4 6)))
(* 6 (* 5 24))
(* 6 120)
720)
可以看出,代码是层层递进再层层回归。
迭代
使用固定的几个变量,最后得出结果。 并没有使用层层递进,再层层回归。
(define (factorial n)
(fact-iter 1 1 n))
(define (fact-iter result n max-n)
(if (> n max-n)
result
(fact-iter (* n result)
(+ n 1)
max-n)))
------------------------------
这种方式是迭代,不是递归
也可以叫尾递归
(factorial 6)
(fact-iter 1 1 6)
(fact-iter 1 2 6)
(fact-iter 2 3 6)
(fact-iter 6 4 6)
(fact-iter 24 5 6)
(fact-iter 120 6 6)
(fact-iter 720 7 6)
720
对比递归和迭代:
- 递归:先递进(展开),再回归(求值)。
- 迭代:从一个状态到下一个状态(有多个变量表示状态,每次更新这几个变量)。
高阶函数
数字能做参数,那么函数能做参数吗?
(define (sum-int a b)
(if (> a b)
0
(+ a (sum-int (+ a 1) b))))
(define (sum-cube a b)
(if (> a b)
0
(+ (cube a) (sum-cube (+ a 1) b))))
(define (sum-square a b)
(if (> a b)
0
(+ squre a) (sum-square (+a 1) b))))
上面三个函数,看起来非常像,我能不能对它进行抽象。
能不能变成一个函数?
(define (???1??? a b)
(if (> a b)
0
(+ (???2??? a)
(???1??? (???3??? a) b))))
(define (sum fn a next b)
(if (> a b)
0
(+ (fn a)
(sum fn (next a) next b))))
然后重写那三个函数
(define (plus n) (+ n 1))
(define (sum-cube a b)
(sum cube a plus b))
(define (nothing x) x)
(define (sum-int a b)
(sum nothing a plus b)
高阶函数是至少满足下列一个条件的函数:
- 接收一个或多个函数作为输入
- 输出一个函数
低级函数只对基本数据进行操作,而高阶函数操作其他函数。
函数能返回数字,那么能返回函数吗?
也可以,书上的例子是数学例子,就不展示给大家了。JS 中的 .bind 就是一个返回函数的函数。
要用 Lisp 写一个例子,需要再学两个语法。
一是 lambda 表达式(可以认为是匿·名函数)。
(define (sum-cube a b)
(sum (lambda (x) (* x x x))
a
(lambda (x) (+ x 1)
b))
二是局部变量声明 let。
(let ((a 1) (b 2))
(+ a b))
等价于
((lambda (a b) (+ a b)) 1 2)
这样我们就可以写一个返回函数的函数了。
(define (add n1)
(lambda (n2) (+ n1 n2))
总结
以上是《SICP》第一章的主要内容。我们了解的知识点有:
- 代入法求值
- 递归与迭代的区别
- 层层递进再层层回来是递归
- 其他都是迭代
- 什么是高阶函数
- 只要一个函数操作其他函数,那么这个函数就是高阶函数