听君一席话,如听一席话,解释解释“惰性求值”~

简介: 我们习惯将代码编写为 一系列的命令,程序会按照它们的 顺序 进行执行:

image.png

止观初探



我们习惯将代码编写为 一系列的命令,程序会按照它们的 顺序 进行执行:


思考以下代码:

const myFunction = function(a, b, c) {
  let result1 = longCalculation1(a,b);
  let result2 = longCalculation2(b,c);
  let result3 = longCalculation3(a,c);
  if (result1 < 10) {
    return result1;
  } else if (result2 < 100) {
    return result2;
  } else {
    return result3;
  }
}


没错,再正常不过的 myFucntion,依次声明了 result1result2resulit3 3 个变量,分别赋值为 longCalculation1(a,b)longCalculation2(b,c)longCalculation3(a,c)longCalculation1/2/3 顾名思义,是一些包含很长的计算过程的函数;


然后进入 if...else if...else... 判断;

最后 return 输出;


那这段代码 合理吗?

image.png


只要调用 myFunctionlongCalculation1/2/3 都必将执行!但是实际上,我们可能不需要它们所有的运算结果;无差别 的完成 3 个很长过程的计算会很影响效率;


有了这个认识之后,我们再来改进代码:

const myFunction = function(a, b, c) {
  let result1 = longCalculation1(a,b);
  if (result1 < 10) {
    return result1;
  } else {
    let result2 = longCalculation2(b,c);
    if (result2 < 100) {
     return result2;
    } else {
      let result3 = longCalculation3(a,c);
      return result3;
    }
  }
}


没错,这确实是改进过后的代码 ╮(╯▽╰)╭

虽然在结构上看,更难看了(多层嵌套,确实难受),但是:

它让 longCalculation1/2/3 不用每次都全部执行,只有在进入确定的条件,需要对值进行返回的时候,才需要计算;


这,就,是, —— 惰性求值的思想体现(不需要立即返回的值,就先别计算;)


庐山面目



来看下 wiki 释义:

惰性求值又叫惰性计算、懒惰求值,也称为传需求调用,是一个计算机编程中的一个概念,目的是要 最小化 计算机要做的工作。


在使用惰性求值的时候,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值


这句话很重要!怎么理解?

image.png


比如:let result1 = longCalculation1(a,b); 这个表达式,意思是把 longCalculation1(a,b) 计算的返回值赋给 result1

在惰性求值中,赋值时,先不对 longCalculation1(a,b) 进行计算,而是等result1 被取用的时候(在示例中,就是 return的时候)再进行计算。


那它是怎样实现的呢?

引用 Reincarnation 的回答:

通过将表达式包装成一个thunk实现的;

例如计算f (g x),实际上给f传递的参数就是一个类似于包装成(_ -> (g x))的一个thunk;

然后在真正需要计算g x的时候才会调用这个thunk;

事实上这个thunk里面还包含一个boolean表示该thunk是否已经被计算过(若已经被计算过,则还包含一个返回值),用来防止重复计算;


第一节示例的 JavaScript 的代码虽然是有惰性求值的思想体现,但是其本身并不是惰性求值;


惰性求值是编程语言的特性设计,很多纯粹的函数式编程语言都支持这种设计;

比如在 Haskell 中实现上述示例:


myFunction :: Int -> Int -> Int -> Int
myFunction a b c =
  let result1 = longCalculation1 a b
      result2 = longCalculation2 b c
      result3 = longCalculation3 a c
  in if result1 < 10
       then result1
       else if result2 < 100
         then result2
         else result3


看上去,这和 JavaScript 示例代码 1 一样,但是它实际上实现的却是 JavaScript 示例代码 2 的效果;


在 GHC 编译器中,result1, result2, 和 result3 被存储为 “thunk” ,并且编译器知道在什么情况下,才需要去计算结果,否则将不会提前去计算!

有点像 Promise 的意思,你不告诉我 resolve/reject,我就 pending;Haskell 中,你不告诉我什么时候调用这个值,我就维持 thunk 的状态;


无限列表



在 Haskell 中可以定义一个数组,它的项是无限多的;

let infList = [1..] // 定义一个 1,2,3... 不断递增的数组;

image.png


为什么在 Haskell 中行,在 JavaScript 中不行?

因为它是懒惰的,你定义归你定义,反正定义的时候,我又不用分配无穷大的内存,等你开始调用的时候,我再开始计算分配吧!

延迟计算很棒,不过事物都有两面性,这样做坏处是什么?


举例🌰:要计算 1 到 1 亿的和;

用 JavaScript 很快得解;

let sum = 0
for(let i=0;i<=100000000;i++){
    sum=sum+i
}
console.log(sum) //5000000050000000


而在 Haskell 中,则会报错 内存溢出

foldl (+) 0 [1..100000000]
*** Exception: stack overflow


因为前者是对变量 sum 不断进行累加,而后者是:

(((((1 + 2) + 3) + 4) + …) + 100000000)


该运行记录中涉及的所有计算都是懒惰的;也就是说,所有单独的数字都同时在内存中,因为只有在 + 操作执行时,才会调用值去计算;

所以,惰性计算带来的最大麻烦就是:内存泄露


内存泄露 → 剩余内存不足 → 后续申请不到足够内存 →内存溢出;

不过,它也是有解决办法的,有兴趣了解:


懒惰奥义



听君一席话,如听一席话,希望看完本篇后,有人再问你“什么是惰性求值”,能心里有个基本的谱~~

image.png


人天性爱偷懒,能不做的事儿先不做,先放着,等要做的时候再去做,这也未尝不是一种智慧;要知道激情是最容易被磨灭的,别让琐碎的提前“计算”消磨掉仅有不多的激情~


看准再做,“慢”也是一种“快”!

我是掘进安东尼,公众号同名,输出暴露输入,技术洞见生活,再会~


相关文章
|
关系型数据库 MySQL Java
Sakai-21部署
记录了两种部署方式: ① 二进制部署 ② 源文件部署
Sakai-21部署
|
2月前
|
人工智能 开发者
零部署OpenClaw接入飞书实现热门新闻主动推送功能
OpenClaw是可部署于本地或云环境的开源AI智能体,支持飞书等平台接入。本案例指导在华为开发者空间零部署启动OpenClaw,快速接入飞书,实现热门新闻主动推送,打造专属“数字管家”。
|
2月前
|
人工智能 数据挖掘 程序员
Claude Skills:如何将提示词升级为可复用技能
深入解析 Claude Skills 的核心原理、渐进披露架构和最佳实践,手把手教你创建自定义技能,实现从临时提示词到可复用资产的升级
404 1
|
4月前
|
存储 JSON 数据格式
FossFLOW:开源等距图表工具,为技术文档注入立体活力!
FossFLOW是一款创新的开源等距图表工具,专为技术文档设计。它通过立体视角将复杂的系统架构转化为直观的3D图表,支持拖放式操作和离线使用,让技术图表变得生动易懂。无需注册,数据安全存储在本地,并提供JSON导入导出功能。无论是Docker快速部署还是在线体验,FossFLOW都能为架构图、流程图注入立体活力,是提升技术文档表现力的得力助手。
244 6
|
4月前
|
人工智能 监控 API
AI智能体的开发流程
AI智能体开发区别于传统AI,具备自主规划、工具调用与自我反思能力。涵盖目标设定、任务拆解、工具集成、记忆构建、框架选型、评测对齐及部署运营七大环节,实现从“被动响应”到“主动执行”的跃迁,推动AI应用迈向自动化与智能化。#AI智能体 #AI应用 #软件外包公司
|
4月前
|
小程序 JavaScript 前端开发
2026最新基于Vue+thinkPhP6前后端分离的婚恋交友管理系统/交友小程序源码搭建
基于ThinkPHP6+Vue.js构建,采用MySQL存储数据、Redis提升性能,实现用户管理、智能匹配与实时聊天。前端使用Vue+UniApp多端适配,Element UI与uView优化交互,通过WebSocket与JWT保障实时通信与安全认证,支持H5、小程序及App全平台部署。
290 0
|
5月前
|
数据采集 数据可视化 数据挖掘
Python 高效学习指南:从入门到全场景的科学路径
Python学习应避免贪多求全,遵循“筑基→深化→定向→实战”四阶段路径:先掌握核心语法与编程思维,再深入面向对象与代码质量,随后聚焦Web、数据或自动化方向,最后通过项目整合技能。强调实践闭环与工程化思维,助力从入门迈向实用开发。
488 0
|
5月前
|
jenkins Java 持续交付
Jenkins配置编译项目
基于Jenkins实现自动化编译部署,涵盖后台Java模块、前端Vue项目。流程包括:GitLab拉取代码,Maven/Node编译,Docker打包镜像,推送至CCE镜像仓库,kubectl更新服务。通过自由风格任务配置构建步骤,支持手动触发与参数化导出镜像为tar包,适配不同Docker版本环境,提升发布效率与兼容性。
262 0
|
SQL 存储 JSON
开源低代码平台开发实践二:从 0 构建一个基于 ER 图的低代码后端
低代码后端 rxModels 的设计开发分享。如果自己开发一个低代码后端,通过该文章,可以借鉴一些设计思路,少踩一点坑。
1902 0
开源低代码平台开发实践二:从 0 构建一个基于 ER 图的低代码后端