函数参数为何不是函数颜色
Function Arguments Are Not Function Colors
网上常有人争论,像 Go 语言中的 context.Context 这样的参数是否也算作函数的“颜色”。作者认为,真正的“颜色”意味着底层函数的任何属性变更,都会强制性地、不可逃避地波及整个调用栈上的所有上层函数。而普通参数变更通常能被中间层封装,不会一路传导至顶层。文章通过对比依赖图形状,指出颜色变更具有非局部性,打破了结构化编程的封装优势。以 Haskell 的 STM 为例,展示了部分颜色如何强制约束中间函数。理解这一区别,有助于我们更清晰地认识语言特性,避免将普通参数传递误判为颜色机制。
颜色变更的本质在于,它必须跨越中间的调用层级,强制将变更施加于相关颜色岛内的所有其他函数。
HN 评论区
42- skavi
在这个框架下,Rust 的 async fns 不会被着色,因为调用栈中的任何函数都可能在 executor 上阻塞。如果你把“spawn point”也算作调用栈的一部分,那么许多其他语言也是类似的。
这感觉不是一个有用的区分,但我认为“函数着色”本身作为一个框架也没什么用。
- mitxela
通常总有一种方法可以在另一个线程(或当前线程)上运行 async 并阻塞它,所以按你的定义,async 不是一种颜色。
我认为颜色应该根据你大概率会做什么来定义。你可以调用 asyncio.run、context.Background 或 unsafePerformIO,但它们都是你不该用的 hack,因为它们不正确,所以在颜色分析中我们忽略它们。
- legobmw99
我觉得这是一个合理的区分,不过值得注意的是,正如作者所述,函数参数_确实可以_是颜色,前提是这些参数必须来自特定位置。例如,在某些语言中,只有 `main` 函数能接收与系统功能相关的特定参数,或者在某种能力框架中,只有顶层函数能接收能力令牌。
- whateveracct
实际上,Haskell 中的 IO monad 脱糖后是
RealWorld -> (a, RealWorld)
其中 RealWorld 只是一个需要被传递下去的“令牌”。你自己无法创建一个,所以必须有人给你。
因此,这个函数参数给 IO 着色了。每个 PL 特性本质上都是 System F 的扩展。
- smilekzs
我个人的衡量标准:
一个概念上的增量变更,对应的是成比例的代码 diff,还是令人惊讶的推倒重来式重构?
代码模式是否自然地留有足够的空间,让你能逐步调整或转换事物的组织方式?
当这些问题的答案不理想时,我往往会发现框架、库或语言(有时是三者同时)中存在所谓的“着色”效应。