理解编程语言的严格和惰性计算

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【7月更文挑战第13天】本文介绍惰性计算推迟了表达式求值,直到其值真正需要时才执行,从而优化性能,节省资源。在前端和并发编程中,懒加载和类似技术结合函数式编程特性,如 continuations,平衡了抽象与性能。

0 简介

  任何事物都有其对应的“价值”。即使惰性也是如此。

惰性计算相对于严格实时计算而已,它允许程序的某部分只做计算的一部分,比如初始化和加载某些资源,参数等操作,待其他条件满足或用户再次调用后再完成全部计算。

Python中的生成器(如range, zip, open)和lambda映射体现这一概念。
生成器函数通过yield延迟计算,仅在迭代时产生值。
惰性属性可用装饰器实现,避免类初始化时的昂贵计算。
然而,惰性计算不适用于需严格顺序的场景,可能导致执行顺序难以控制。

1 惰性计算求值

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

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

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

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

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

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

2 几个例子:lazy evaluation

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

17lazy_evaluation_in_py.png

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

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

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

3 range 范围计算

range(5)

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

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

迭代器 > 生成器

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

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

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

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

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

4 zip 压缩

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

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

5 open 加载和读取

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

6 lambda 表达式

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

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

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

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

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

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

7 惰性计算-类的惰性和生成器

生成器计算

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中可以创建一个装饰器类。只有在实际使用时,才去创建这个类的属性.

8 小结

虽然惰性计算有较多的优点,但是在某些需要严格顺序求值的场景并不是适用,比如在惰性语言中很难能保证其中的执行顺序!这也就意味着我们将难以处理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
目录
相关文章
|
6月前
|
存储 算法 编译器
掌握Go语言:探索Go语言递归函数的高级奥秘,优化性能、实现并发、解决算法难题(28)
掌握Go语言:探索Go语言递归函数的高级奥秘,优化性能、实现并发、解决算法难题(28)
124 0
|
4月前
|
存储 缓存 前端开发
编程语言中值函数表示的优化
【7月更文挑战第7天】这段文本是关于编程语言实现中值的表示和优化的总结,特别是讨论了一个叫做OTao的语言。文本最后鼓励读者探索编程语言设计的更多方面,并提供了进一步学习的资源和建议。
45 2
编程语言中值函数表示的优化
|
3月前
|
缓存 Python
Python中更好用的函数运算缓存
Python中更好用的函数运算缓存
|
3月前
|
存储 分布式计算 监控
|
缓存 Java 程序员
函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码
本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。
函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码
|
存储 前端开发 JavaScript
✨从延迟处理讲起,JavaScript 也能惰性编程?
我们从闭包起源开始、再到百变柯里化等一票高阶函数,再讲到纯函数、纯函数的组合以及简化演算;
|
存储 前端开发 JavaScript
听君一席话,如听一席话,解释解释“惰性求值”~
我们习惯将代码编写为 一系列的命令,程序会按照它们的 顺序 进行执行:
|
存储 消息中间件 分布式计算
惰性函数|学习笔记
快速学习惰性函数。
172 0
惰性函数|学习笔记
|
Python
【python】惰性求值是什么意思(附例子)
【python】惰性求值是什么意思(附例子)
292 0
|
Python
python中生成器的惰性机制
python中生成器的惰性机制
106 0
python中生成器的惰性机制
下一篇
无影云桌面