函数不存在,异步是假的,你的代码只是一场集体幻觉

你的CPU根本不在乎你写的函数。

它不知道什么是main(),也不认识async/await。那些你日夜打磨的封装、继承、多态,在硅片层面只是一堆跳转指令。函数?那是人类的发明。是社会建构。是让你不至于发疯的安慰剂。

一位叫Will Richardson的程序员,原本只是想搞清楚怎么处理错误比较优雅。结果他掉进了兔子洞,发现异步编程本质上就是"可撤销的异常",而依赖注入不过是"效应系统"的拙劣模仿

这听起来像疯话。但听完解释,你可能会怀疑过去十年写的代码。

函数的真相:你从未真正"调用"过任何东西

让我们扒开编译器的底裤。

当你在C语言里写下first_function(),编译器确实知道要跳转到哪个内存地址。但遇到return时,问题来了——这个函数可能被main调用,也可能被some_function调用。CPU怎么知道该跳回哪里?

秘密在于:每次函数调用,编译器都偷偷塞了一个"隐形参数"。不是你在括号里写的那些,而是返回地址。它藏在寄存器里,像一张回程车票。

函数只是固定入口、动态出口的抽象。我们靠这个谎言构建了现代软件工程。

但等等。如果我们可以动态决定"跳到哪儿",为什么不能动态决定"什么时候跳回来"?

异常与异步:同一枚硬币的两面

想想Java的try-catch。当异常抛出,控制流瞬间穿透层层函数调用,直接蹦到catch块。这像什么?像不像goto?像不像时间旅行

作者提出了一个疯狂的想法:如果catch块里能"撤销"这个异常呢?

假设有个魔法变量__instruction__保存了抛出异常时的位置。在catch里,我们不但可以处理错误,还能goto回去,让代码接着跑。

try {
some_function(); // 抛出并记录位置
} catch(error_location) {
// 做点别的...
goto error_location; // 我回来了!
}

这就是async/await的真相。

当Kotlin或Swift里的异步函数"挂起"时,本质上就是抛出了一个被调度器捕获的"异常"。调度器把这个返回地址存进待办列表,去干别的。等IO完成,它再跳回来,仿佛什么都没发生。

所以作者说:async/await只是"可以撤销的异常"

我们用了十年的FuturePromiseTask,本质上都是在模拟这个底层能力。

效应系统:把一切统一成"跳转"

既然异常是跳转,异步是跳转,那闭包呢?依赖注入呢?

在Effekt和Koka这类研究型语言里,这些被统一为**"效应"(Effects)**。

看这段Effekt代码:

interface Exception[T] {
def throw(msg: String): [T]
}

def div(a: Double, b: Double) =
if (b == 0.0) { do throw("division by zero") }
else { a / b }

注意那个do throw。它不只是抛出错误,而是在说:"我要跳出当前控制流,请上层决定怎么办,并且可以把我拉回来"

更疯狂的是resume。在效应处理器里,你可以:

try {
div(4, 0)
} with Exception {
def throw(msg): Double = {
println("除零错误: " ++ msg)
resume(42.0) // 给div一个默认值,让它继续跑
}
}

控制流跳出去,处理完,再跳回来。 这就是异步的本质,也是异常的本质,甚至也是依赖注入的本质。

依赖注入?那只是效应的穷亲戚

作者突然顿悟:Dagger、Spring那些依赖注入框架,本质上就是效应系统的手动实现

当你写@Inject Logger logger,你其实在说:"我不知道这个从哪儿来,运行时请上层作用域给我"。

在Effekt里,这可以表达为:

interface Inject[A] {
def get(): A
}

def functionWithDeps(): Unit / { Inject[Logger], Inject[Config] } = {
val logger: Logger = do get()
// ...
}
`

函数签名里的`/ { Inject[Logger], Inject[Config] }`明确声明:我需要这些效应才能运行。没有对应的handler,这段代码根本编译不过。

**作用域、生命周期、延迟初始化——这些DI框架折腾半天的概念,在效应系统里不过是类型系统的自然推论。**

我们写了十年的`try-catch`、`async-await`、`ServiceLocator`,原来都是在用不同方言说着同一件事:**请帮我跳转一下控制流**。

## 反转:当语言追上设计模式

这里有个讽刺的反转。

Ruby和Crystal的`yield`关键字,Swift的`@escaping`闭包,Java的checked exceptions——这些语言特性之所以设计得别扭、有诸多限制,**正是因为它们是不完整的效应系统**。

Crystal的`yield`只能向上跳一层,不能存储后异步执行,所以编译器要禁止你在闭包里写`return`。Swift要给闭包标记`@escaping`。Java的checked exceptions不能跨lambda传播。

**这些不是bug,是workaround。** 是语言不支持完整效应系统时,给开发者打的补丁。

而当Effekt把效应作为一等公民时,你会发现:不需要特殊的`async`关键字,不需要`throws`声明,不需要复杂的DI框架。一种机制,统一所有控制流。

## 尾声:你想活在抽象里,还是直面真相?

说实话,读完这篇文章,我有点恍惚。

我们每天争论"该用异常还是返回错误码"、"async/await是不是颜色污染"、"依赖注入用构造器还是字段",仿佛这些是不同的、复杂的问题。

但如果它们本质上是同一个问题呢?**如果编程的未来不是添加更多语法糖,而是把控制流彻底交给开发者定义?**

Effekt和Koka还很小众。但ZIO(Scala的效应系统库)已经在生产环境跑了很久。也许十年后,我们会觉得今天的`try-catch`和`async/await`像goto一样古老。

你的CPU依然不会在乎。它只认跳转指令。但至少,我们可以停止假装这些概念是截然不同的魔法。

**毕竟,连函数都是假的,还有什么不能重新想象?**

【kimi-k2.5锐评】:当程序员终于发现所有语言特性都是"控制流跳转"的不同妆容,他们要么拥抱效应系统成为先知,要么继续在前端框架里写useEffect假装自己懂了。


参考链接:
https://willhbr.net/2026/03/02/async-inject-and-effects/