函数式编程入门 (一)(递归与迭代、高阶函数)

762 阅读4分钟

在了解到 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

上面列出的表达式有两种求值的办法,递归求值和代入法求值。

递归求值

  1. 求各子表达式的值,也就是右侧值
  2. 将所有右值应用到运算符上得到最终结果

这种求值规则被称为「递归求值」。下面是递归求值的一个例子

(* (+ 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) 的值呢?答案是直接带入代码,替换变量。

需要注意的是:

  1. 代入只是为了方便我们理解函数,并不一定是实际工作原理,因为实际原理更复杂。
  2. 只有满足给定一个值,返回的值是确定的函数。才可以使用代入法。

计算的形状

在树形组件中,一定会遇到递归的情况。

递归

求 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

对比递归和迭代:

  1. 递归:先递进(展开),再回归(求值)。
  2. 迭代:从一个状态到下一个状态(有多个变量表示状态,每次更新这几个变量)。

高阶函数

数字能做参数,那么函数能做参数吗?

(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》第一章的主要内容。我们了解的知识点有:

  1. 代入法求值
  2. 递归与迭代的区别
    • 层层递进再层层回来是递归
    • 其他都是迭代
  3. 什么是高阶函数
    • 只要一个函数操作其他函数,那么这个函数就是高阶函数