你觉得“惰性求值”在 JS 中会怎么实现?

简介: JS 不像 Haskell,其自身从语言设计层面不支持惰性求值,但是可以通过语法去 模拟实现 这一特性;想一想,我们可以用什么来 JS 语法来模拟这一“延迟计算”的特性?

image.png

接上一篇《听君一席话,如听一席话,解释解释“惰性求值”~》,有掘友问:“我懂惰性求值的意思了,但是在 JS 中如何实现 thunk 的呢?”


image.png

JS 不像 Haskell,其自身从语言设计层面不支持惰性求值,但是可以通过语法去 模拟实现 这一特性;


想一想,我们可以用什么来 JS 语法来模拟这一“延迟计算”的特性?

没思路的话,看前篇这一句:

image.png

在《Haskell趣学指南》中,thunk 被翻译成 保证

在《Haskell 函数式编程入门》,thunk 被解释为:


thunk 意为形实替换程序(有时候也称为延迟计算,suspended computation)。它指的是在计算的过程中,一些函数的参数或者一些结果通过一段程序来代表,这被称为 thunk。可以简单地把 thunk 看做是一个未求得完全结果的表达式与求得该表达式结果所需要的环境变量组成的函数,这个表达式与环境变量形成了一个无参数的闭包(parameterless closure),所以 thunk 中有求得这个表达式所需要的所有信息,只是在不需要的时候不求而已。


那意思是用 Promise 模拟吗?

事实上,不行!


Promise 一旦执行,它就开始执行了,你只知道是在 Pending,但不知道是刚开始执行,或者是快执行完了,还是其它哪个执行阶段;获取 Promise 的时候,内部的异步任务就已经启动了,执行无法中途取消(这也是 Promise 的弊端之一:);


代码🌰

image.png


Promise 是立即求值,不是惰性求值!


那手上还有什么牌?

“延迟执行”不就是“暂停以后再执行”嘛?thunk更像是 Generator !!👏

赋值的时候,我不进行计算,把你包装成一个 <suspended> 暂停等待,等你调用 next() 的时候,我再计算;


代码🌰

image.png


这不就是最简单版本的 JS 惰性求值 Thunk 的实现吗?


Haskell 中的无限列表不就是 MDN 中 Generator 所实现的 无限迭代器 吗?


function* idMaker(){
    let index = 0;
    while(true)
        yield index++;
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
// 0
console.log(gen.next().value);
// 1
console.log(gen.next().value);
// 2
// ...

image.png

实际上 Lazy.js 也正是借助 Generator 实现“惰性”的!

以实现 take 方法为例🌰:


在 Haskell 中,take 函数可以从头连续地取得一个列表的几个元素;

Prelude> take 3 [1,2,3,4,5]
[1,2,3]


JS 模拟实现 take:

function* take(n,items){
  let i = 0;
  if (n < 1) return;
  for (let item of items) {
    yield item;
    i++;
    if (i >= n) {
      return;
    }
  }
}
let thunk=take(3,[1,2,3,4,5])
console.log(thunk.next()) // {value: 1, done: false}
console.log(thunk.next()) // {value: 2, done: false}
console.log(thunk.next()) // {value: 3, done: false}
console.log(thunk.next()) // {value: undefined, done: true} 


使用 lazy.js 是类似这样调用的:

Lazy(stream)
  .take(5) // 仅仅阅读数据中的前五块内容
  .each(processData);


小结


专栏介绍引用的是这句话:


如果要整体了解一个人的核心 JavaScript 技能,我最感兴趣的是他们会如何使用闭包以及如何充分利用异步。—— Jake Archibald

再回看 wiki 上关于闭包的这句解释:


  • 闭包的用途:因为闭包只有在被调用时才执行操作(暂且不论用于生成这个闭包对象本身的开销,比如 C++ 中按值捕获意味着执行复制构造函数),即“惰性求值”,所以它可以被用来定义控制结构。例如:在Smalltalk语言中,所有的控制结构,包括分支条件(if/then/else)和循环(while和for),都是通过闭包实现的。用户也可以使用闭包定义自己的控制结构。


现在看来,惰性求值似乎能连接“如何使用闭包”和“如何充分利用异步”!!

“惰性”的思想深入函数式编程,还有最重要的 Monad,把具有“副作用”的部分延后处理,也与“惰性”呼应,后面有机会再讨论~

好啦,以上便是本篇分享~

掘文不易,点赞鼓励👍👍👍

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


相关文章
|
6月前
|
JavaScript
js惰性函数
js惰性函数
27 0
|
6月前
|
前端开发 JavaScript 定位技术
Flutter vs 前端 杂谈:SliverAppBar、手动实现Appbar、前端Html+JS怎么实现滚动变化型Appbar - 比较
Flutter vs 前端 杂谈:SliverAppBar、手动实现Appbar、前端Html+JS怎么实现滚动变化型Appbar - 比较
115 0
|
机器学习/深度学习 设计模式 JavaScript
|
前端开发 JavaScript
html + css + js 怎么实现主题样式的切换?
html + css + js 怎么实现主题样式的切换?
187 0
html + css + js 怎么实现主题样式的切换?
|
存储 前端开发 JavaScript
✨从延迟处理讲起,JavaScript 也能惰性编程?
我们从闭包起源开始、再到百变柯里化等一票高阶函数,再讲到纯函数、纯函数的组合以及简化演算;
|
JavaScript 前端开发
JavaScript 专题之惰性函数
JavaScript 专题系列第十五篇,讲解惰性函数
132 0
JavaScript 专题之惰性函数
|
JavaScript 前端开发
|
存储 JavaScript 前端开发
|
存储 JavaScript 前端开发
JS编程建议——65:比较函数的惰性求值与非惰性求值
65:比较函数的惰性求值与非惰性求值
2887 0
|
Web App开发 JavaScript 前端开发