函数不存在,异步是假的,你的代码只是一场集体幻觉
你的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只是"可以撤销的异常"。
我们用了十年的Future、Promise、Task,本质上都是在模拟这个底层能力。
效应系统:把一切统一成"跳转"
既然异常是跳转,异步是跳转,那闭包呢?依赖注入呢?
在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/