共计 2170 个字符,预计需要花费 6 分钟才能阅读完成。
咱们在惰性求值中,咱们介绍了「惰性列表」的概念,这个概念,其实在 Python
种也有局部原生反对。这就是很受老手困扰的 生成器 和迭代器 了。但之前,咱们首先要回顾一下对于列表的性能。
从二元元组到列表
首先,咱们能够用 \(\lambda\)演算定义一个二元的元组,或者叫pair
:
pair
: \(\lambda a b f.f a b\)first
: \(\lambda p. p(\lambda a b. a)\)second
: \(\lambda p. p(\lambda a b.b)\)
具体实现如下:
pair = lambda a: lambda b: lambda f: f(a)(b)
first = lambda p: p(lambda a: lambda b: a)
second = lambda p: p(lambda a: lambda b: b)
咱们能够定义测试一下:
>>> p = pair(1)(2)
>>> first(p)
1
>>> second(p)
2
当然有了 pair
,定义一个列表就不是难事,即上面的形式组合就好(咱们还是用python
自带的元组示意):
(1, (2, 3, (4, ()))))
咱们将在前面的章节里别离用 元组 和类 / 类型 的形式来定义列表。但在这篇文章里,咱们先回到之前 python
的自带的概念来,看函数式编程如何解决遍历问题的。
列表操作
列表操作,是函数式编程的一个重要概念,实时上它是通过递归来实现对一个线性后果的遍历。比方上面的类 C 格调的代码:
ls = [1, 2, 3, 4]
for i in range(0, len(ls)):
ls[i] = ls[i] + 1
这里呈现了两个副作用,一个是 i
的自增,另一个是对 ls
的原地操作。而且,它们也用到了 变量 的概念。当然,这种写法其实无可非议,可维护性也尚可,算是能够容忍的副作用。当然咱们最简略的实现,相当于大家都晓得是列表表达式(当然,事实上它还是有副作用的):
[i + 1 for i in ls]
当然,大部分人也见过列表表达式的残缺操作,能够自带筛选:
[i + 1 for in in ls if i % 2 == 0]
这就是函数式编程遍历数据最简略的操作,当然,它们还有一个名字,就是 map
和filter
,在 Python
中,它们返回的就是可迭代对象(咱们能够调用 list
转换成 列表):
map(lambda x: x + 1, ls) # [i + 1 for i in ls]
filter(lambda x: x % 2 == 0, ls) # [i for i in ls if x % 2 == 0]
另一个罕用的列表操作是 reduce
,它起到的是聚合作用,咱们只有定义一个 二元运算,就能够将列表从头合并到尾聚合操作。
reduce
操作视图解决的问题就是遍历后汇总值的过程。譬如,咱们要实现 ls
的求和,在个别的过程式编程中,咱们会应用如下的办法:
res = 0
for i in ls:
res += i # 或者和上面更相似的写法 res = res + i
而,应用reduce
,咱们仅须要如下代码即可实现。
from functools import reduce
reduce(lambda x: x + y, ls)
具体的计算过程如下:
- 获取
ls
第一个值1
和第二个值2
,套用lambda x, y: x + y
,失去3
。 - 获取
ls
第三个值3
,套用第一步的后果3
和lambda
失去6
。 - 获取
ls
第三个值4
,套用第二步的后果6
和lambda
失去10
- 实现计算返回后果。
但,其实如果查看 Python
的reduce
函数的参数,咱们会发现它还能够带入初始值,当带入初始值时,在各类函数式语句中,个别把它叫做 fold_left
/foldl
函数。这个有没有初始值成果会不一样很多。第一个就是解决列表是空的问题:
reduce(lambda x, y: x + y, []) # 报错
reduce(lambda x, y: x + y, [], 0) # return 0
咱们甚至能够把这个和后面的过程式编程的各种元素对应起来,0
相当于 res = 0
,lambda x, y: x + y
表白的就是 res = res + i
。然而,其实foldl
比reduce
更弱小的层面,在于,这个运算自身能够波及不同类型。咱们采纳类型标记,就会发现 reduce
函数自身的运算只能是Callable[[S, S], S]
/(S, S) -> S
,但其实咱们在很多场景中,须要的是一个类型装换。比方:
[1, 2, 3, 4]
=>"1234"
[1, 2, 3, 4]
=>[[1], [2], [3], [4]]
- …
如果单纯应用 reduce
咱们无奈操作这种波及类型转换的内容,foldl
带入的二元运算类型标注则是Callable[[S, T], S]
/(S, T) -> S
。这就让咱们能够通过设定一个另一个类型的初始值,来实现这件事,比方下面转换成字符串的例子,咱们很容易找到上面的二元运算(留神前后程序):
lambda x, y: x + str(y)
而初始值仅需设定一个空的 ""
字符串即可,即如下实现(尝试本人实现一下 [1, 2, 3, 4]
=> [[1], [2], [3], [4]]
吧!):
reduce(lambda x, y: x + str(y), ls, "")
总结
本篇文章中,咱们回顾了 Python
原生的列表,以及介绍函数式编程通过列表表达式 / 列表操作来实现过程式中常见的数据遍历的问题来躲避 for
/while
中不可避免的副作用。咱们接下来将会应用 pair
的概念从头实现一个列表,而后咱们就进入到正式的惰性列表的概念中,看看惰性列表如何解决这类问题,以及用函数式思考流式解决、线程的概念。