欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 你的函数是什么颜色的?What Color is Your Function?

你的函数是什么颜色的?What Color is Your Function?

2024/10/23 15:21:10 来源:https://blog.csdn.net/m0_63629756/article/details/143031848  浏览:    关键词:你的函数是什么颜色的?What Color is Your Function?

前言

2015年,程序员Bob Nystrom在博客上发表文章What Color is Your Function,这篇文章反馈很高,且未随时代发展而过时,原文链接如下(想要看懂原文需要较高的英文水平,作者在里面玩了很多英文梗):

What Color is Your Function? – journal.stuffwithstuff.com

这篇文章的主旨,我认为是以设计编程语言的角度,探讨同步函数与异步函数、以及他们在语言层的最优实现。

本文的内容主要是对文章原内容,按照读者的理解进行的本土化翻译,并添加了一些实例作为解释

但是每个读者都有每个读者的理解,阅读原文还是十分必要的

正文

一门新语言

作者定义了一门语法很类似JS的语言,想讲明什么是作者口中的函数的颜色,同时,作者做出了如下定义:

  1. 每个函数要么是红色,要么是蓝色
  2. 红色函数可以调用蓝色函数,但是只能被红色函数调用
  3. 红色函数调用成本更高(这是个强制定义,可以理解为你调用红色函数就必须给领导写申请文档)
  4. 标准库里一些我们不得不用的函数是红色的

我们自然会得出一个基本结论,优先蓝色,迫不得已采用红色

好的,接下来我们正常的编写代码

我们定义两个不同的函数

String processData() {return "同步处理数据";
}String fetchData() {return "从远程服务器获取数据";
}

调用方法如下,一切顺利进行

void main() {var data = fetchData();print(data);
}

现在fetchData方法突然变得复杂起来,需要调用某个底层库函数的支持,糟糕的是要调用的函数是红色,原本正常的蓝色函数main也变成了红色,同时所有调用fetchData的函数都变成了红色,我们需要重写巨多代码,甚至重构代码结构,这太糟糕了。

红色具有强烈的传染能力。

色彩比喻

作者提到的红色函数就是异步函数,并且就此大力批评了js

1. 蓝色函数(同步)直接返回值,红色函数(异步)需要通过回调处理结果。

2. 在同步函数中无法直接调用异步函数,因为结果尚未准备好。

3. 异步函数很难处理,错误捕获处理方式不同,也不能和同步控制结构良好兼容。

4. Node.js 标准库大量使用异步函数,迫使开发者处理“红色函数”的复杂性。

事实上回调地狱就是红色函数导致的:

function getData(callback) {setTimeout(() => {callback("数据1");}, 1000);
}function processData(data, callback) {setTimeout(() => {callback(data + " -> 处理后的数据");}, 1000);
}function displayData(data) {console.log("显示: " + data);
}// 回调地狱
getData((data1) => {processData(data1, (processedData) => {getData((data2) => {processData(data2, (processedData2) => {displayData(processedData2);});});});
});
人们发明了Promise和Future

Node社区的人受够了回调的痛苦,所以发明了Promise,但是作者认为并没有改进很多,Promise,Async,Await的语法糖缓解了使用红色函数的成本,但是还是让函数有着两种颜色。

比如最开始我们要用回调处理异步结果和错误

function fetchData(callback, errorCallback) {setTimeout(() => {callback("数据加载成功");}, 1000);
}fetchData((data) => console.log(data),(error) => console.log("错误: " + error)
);

经过Promise的改进,我们可以这样写了

function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {resolve("数据加载成功");}, 1000);});
}fetchData().then((data) => console.log(data)).catch((error) => console.log("错误: " + error));

但是我们还是不能让它与同步代码结合:

let data = fetchData();  // 错误,返回的是 Promise 对象
console.log(data);  // 不能直接拿到结果

同步和异步的分裂还是存在,要么全部写异步,要么重新设计代码架构。

但是有人说,还有await语法糖,我们可以await这个结果

作者认为繁多的语法糖消除了红色函数的使用成本,但是我们仍然会面临困境:

function fetchData(IDs) {let dataList = IDs.map(async (ID) => {let data = await fetchDataFromRemote(ID);return data;});return dataList; // 这里返回的是 Promise 数组,而不是期望的结果
}

所以我们又要这样来写

async function fetchData(IDs) {let dataList = await Promise.all(IDs.map(ID => fetchDataFromRemote(ID)));return dataList;
}
问题的本质

底层的实际问题可以表述为“该如何在异步操作完成时,回到上一次代码执行的地方”。

由于异步操作(如 I/O)不能阻塞整个调用栈,必须回退整个栈并返回到事件循环,这样才能让操作系统处理异步任务。

同步代码可以通过调用栈追踪执行进度,而异步 I/O 需要用闭包保存状态。每次执行后,调用栈会销毁,但闭包让数据保留在堆中。这就是所谓的 continuation-passing style(CPS),将执行上下文通过函数传递下去。

如下面段代码中,每个函数闭合了它的上下文(如 iceCream 和 caramel),这些数据被存储在堆中,而不再依赖于调用栈。每个嵌套的函数都将控制权传递给下一个回调函数。

function makeSundae(callback) {scoopIceCream(function (iceCream) {warmUpCaramel(function (caramel) {callback(pourOnIceCream(iceCream, caramel));});});
}

异步带来的问题:

调用栈无法保存异步函数的执行状态,因此必须依赖回调或闭包去保存这些数据。

异步操作需要手动“回到”先前的执行上下文,而不是自然地通过调用栈返回。

Promise 也没完全解决问题:

尽管 Promise 改善了异步代码的可读性(通过 .then() 链式调用),但仍然需要手动管理这些闭包和函数字面量。代码结构依然是 continuation-passing style,只是 Promise 使其看起来更直观一些。

有了回调、承诺、异步等待和生成器,最终你会把你的异步函数展开成一堆堆在堆内存中的闭包。
你的函数将最外层的闭包传递给运行时。当事件循环或 I/O 操作完成时,它会调用那个函数,你可以从上次离开的地方继续。但这意味着你上面的所有东西也必须返回。你仍然必须展开整个栈。这就是 “红色函数只能被红色函数调用” 规则的由来。你必须将整个调用栈一直闭包化回到 main () 函数或事件处理程序。

结语、我的总结

作者认为Go语言是真正的无色代码

在我看来,Go 语言在这方面做得最为出色。一旦进行任何 I/O 操作,它就会暂停那个协程,并恢复任何其他没有被 I/O 阻塞的协程。

在 Go 语言中,并发是你选择如何构建程序的一个方面,而不是像在某些语言中那样成为标准库中每个函数的一个特定 “颜色” 属性。这意味着上文提到规则所带来的所有痛苦在 Go 语言中被完全消除了。

的确如此:Go 的 Goroutine 是语言原生支持的轻量级线程,启动 Goroutine 不需要特殊标记,调用 Goroutine 和普通函数没有区别,语言设计上没有“异步函数”这个概念,也不需要像 async、await 这样的显式标记,Go 的调度器会自动处理 I/O 的异步性,但对开发者是透明的。

事实上,对于我比较了解的语言,如kotlin,dart,js,他们都不是无色语言。

在 Kotlin 中,挂起函数有着特定的调用规则,即必须在挂起函数或者协程作用域中进行调用。Kotlin 的协程机制要求开发者更多的来参与处理协程的管理,以确保协程能够高效协作。

kotlin的协程并不能说比go差,但一定的是如果要用好协程,他对开发者对协程的使用有着较高的要求。

读完此篇文章后,联想到文章的发布日期--2015年,几乎10年前,不得不感叹笔者的前瞻性,致敬

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com