语言:严格和惰性计算

1,361 阅读5分钟

1 惰性计算求值

支持惰性求值的编译器会像数学家看待代数表达式那样看待函数式程序:抵消相同项从而避免执行无谓的代码,安排代码执行顺序从而实现更高的执行效率甚至是减少错误。

也被称之为传需求调用,是一个计算机编程中的一个概念,目的是要 最小化计算机初始要做的工作

其目的是实现“高内聚,低耦合”的代码,从经验上看,抽象过多的代码往往意味着低的性能。 机器可以直接执行的汇编性能最强,C 语言其次,Java 因为较高的抽象层次导致性能更低。

业务系统也受到同样的规律制约,底层的数增删改查接口性能最高,上层业务接口,因为增加了各种业务校验,以及消息发送,导致性能较低。

例如java的supply接口,python的生成器表达式和生成器函数是惰性的,在求值时,这些表达式不会马上计算出所有的可能结果。

如果不把计算过程显式打印出来,很难看到惰性求值的结果。下面的几个例子演示了通过引入带有副作用的range()函数生成值的过程。

1.1 几个例子

1.1.1 lazy evaluation

是一种评估策略,将表达式的评估延迟到需要其值为止。并且避免重复,通常为优化代码的策略。

17lazy_evaluation_in_py.png

1+2 在python中为严格(Strict),立即运算. 因为评估是立即完成的,因此它有另一个名称:严格(Strict)。

惰性(lazy)。不同之处在于Lazy Evaluation不会立即评估表达式,而是仅在需要结果时才执行。

懒惰不一定是坏事,它可以提高您的代码效率并节省大量资源。幸运的是,Python 已经悄悄地将惰性求值应用于许多内置函数以优化您的代码。

1.1.2 range

range(5)

它只存储start、stop 、 step值 并在需要时计算每个项目 它返回一个范围类型。

可以迭代此对象以产生一系列数字。无论范围有多大,对象始终具有相同的大小。

迭代器 > 生成器

简单来说,迭代器是一个比生成器更大的概念。迭代器是一个对象,它的类有一个__next__和__iter__方法。

每次next()调用迭代器对象时,都会获得序列中的下一项,直到迭代器对象耗尽并引发StopIteration。

然而,生成器是一种函数返回一个迭代器。它看起来像一个普通函数,只是它使用了yield代替return。

当yield语句被执行时,程序将挂起当前函数的执行并将产生的值返回给调用者。

这是Lazy Evaluation的关键思想,在需要调用者时计算并返回值,而下一个值仍将保持安静并且在程序中什么也不做。

1.1.3 zip

一个非常相似的用例是zip()合并 2 个可迭代对象以生成一系列元组。在 Python2 中,zip(*iterables)将返回一个元组列表。

Python3 开始,它已经改进了返回一个zip类似于range可以迭代的对象的对象。

1.1.4 open

with open(...)不会读取整个文件并将其存储在内存中,而是返回一个可以迭代的文件对象。正因为如此,它能够有效地读取大文件而不会损害内存。

1.1.5 lambda

x = map(lambda x: x*2, [1,2,3,4,5]) 

这样的 lambda 映射对象不占用任何空间? 但是如果你执行 list(x),它会打印所有的值并占用内存空间

该map对象也是一个可以迭代的惰性对象。

x*2每个循环中仅对 1 个项目进行计算。

当您这样做时list(x),您基本上一次计算所有值。

如果您只想迭代map对象,则不必执行list(x).

1.2 惰性计算-生成器 和 类的惰性属性

生成器计算

def lazy_loading(items):
for i in items:
    # you can have complex logic here
    yield i ** 2

items = [i for i in range(100)]
for i in lazy_loading(items):
    print(i)

惰性属性--装饰器

初始化一个类时,某些属性可能需要很长计算时间, 此时,我们在python中可以创建一个装饰器类。只有在实际使用时,才去创建这个类的属性.

2 小结

虽然惰性计算有较多的优点,但是在某些需要严格顺序求值的场景并不是适用,比如在惰性语言中很难能保证其中的执行顺序!这也就意味着我们将难以处理IO,不能调用系统函数做更多的事情。

在某些语言种,一定的函数式设置下(functional setting)代码能以特定顺序执行。

这样的技术使我们就可以在两个优点满足。这些技术包括 continuation, monad 和 uniqueness typing。 除了确保函数求值顺序, continuation 在很多别的情况下也很有用。

在html前端应用,也有“懒加载”,超买超卖也有利用惰性写出高性能且抽象的代码去实现并发的功能。

Java supply 接口,Python 惰性求值,函数式编程高效,原因之一是将计算推迟到需要的时候进行。惰性(也称“非严格”)求值非常重要,Python内置了对它的支持。

参考:

http://lambda-the-ultimate.org/node/2708

http://www.edsko.net/pubs/ifl07-paper.pdf