Wyzer 编程语言
文章摘要
Wyzer 自我定位是一门静态类型、编译型、面向资源的编程语言,主打两个卖点:通过编排式编程(choreographic programming)内建的分布式安全性,以及 Perceus 内存模型。作者在 Show HN 的自述里说,这个项目的起点是对 Rust 的不满:Rust 通过严格的类型检查保证了内存安全,但它保证不了分布式死锁——几个独立的节点或服务永久地互相等待对方持有的资源或消息,形成循环等待——也保证不了跨服务的正确性和协议匹配。Wyzer 的核心工作就是把编排式编程这个概念泛化进一门高级语言,因为这是极少数真正试图填补这类安全缺口的路径之一。相比借用检查器和生命周期,Wyzer 用的是线性/仿射类型加 Perceus 引用计数,作者认为后者对 LSP 来说在计算上要简单得多。他说自己做了 5 个月研究、几周开发,即将发布 0.1.0。
README 对两个核心概念的解释是这样的。编排式编程:传统的分布式编程里,你为客户端和服务端分别写代码,然后祈祷两边的通信协议能对上;编排式编程让你为整个分布式系统写一份统一的视图,编译器再把这份脚本数学地「投影」成每个物理节点(比如 Client 和 Server)各自独立的、无死锁的二进制。Perceus 内存模型:这是一种快速、确定性的内存管理策略,既避开垃圾回收器那种不可预测的停顿,也避开借用检查器那些复杂的标注;它用精确引用计数来判断一个值何时只有单一所有者,从而让编译器直接原地修改数据(即 FBIP,Functional But In-Place),不必复制也不必暂停程序。
设计原则被列为:一切只有一条所有权规则、没有 GC、没有借用检查器、设计上无死锁、避免隐藏的魔法、C ABI 兼容。
README 里给出的关键示例是一个分布式键值存储。代码顶部用 role @Client; 和 role @Server; 声明角色,变量类型带上位置标注(如 str@Server、KVRequest@Client)。客户端构造一个 Put 请求后,只需写 let server_req: KVRequest@Server = put_req;,网络传输就通过所有权转移安全地发生;服务端随后无需 mutex 就能更新自己的状态。因为 Wyzer 把编排与 Perceus 线性类型合并,一次网络传输就是一次绝对的线性移动——跨网络转移变量的所有权会在本地消耗掉它,再次使用会得到编译期错误(README 展示了一段相当漂亮的错误输出:「变量 put_req 在被移动后于此处使用」,并提示这是线性资源、只能使用一次,建议按引用传递)。另一个「请求/响应编排」示例展示了复杂的网络握手如何读起来像一个单一的顺序函数:客户端的 query 转给服务端、服务端查库、结果再转回客户端,全部写在同一个函数里。第三个示例演示 Perceus 的原地修改:一个返回新 User 结构体的函数,在传统函数式语言里会分配新内存,而在 Wyzer 里如果 user 恰好只有一个所有者,编译器会原地改写现有内存,「在没有借用检查器的情况下达到 C 级的速度」。
README 还链接了 DESIGN.md(语言设计与动机)、RESEARCH.md(研究与实现细节)、官方文档站点和 Discord 社区,并特别提到一个引人注目的示例:donut.wyz,用 Wyzer 重写的 donut.c。
需要说明的是,这次讨论中一个反复出现的关键事实是:Show HN 的发帖账号并非语言的真实作者——多位评论者指出发帖人在冒充作者,之后又把 HN 账号凭证留在了 Wyzer 的 Discord 里,于是真正的作者用同一个账号回来回答问题。作者据说是一名 14 岁(也有说 12 岁)的开发者。
HN 评论精华
这条帖子拿到 226 分,但讨论的走向相当特别:绝大多数评论不是在讨论这门语言做得怎么样,而是在批评它的文档没有把核心创新讲出来;中途还岔出一场关于 GC 性能的高强度技术辩论;最后又演变成对发帖人身份真实性的困惑。真正深入编排式编程原理的高质量内容反而是一位学术研究者贡献的。
- jitl 的第一条批评几乎成了全场共识:「你的 README 和文档没有描述这里任何有趣或独特的东西。README 里讲了
if,却没讲编排式编程和 perceus。酷的东西在哪?」作者回复「你没读 RESEARCH.md 吧 :)」,derdi 立刻反驳:「他们为什么要读?README 里关于 RESEARCH.md 只说了『如果你想给语言做贡献请读它』。发帖人不想贡献,他想看一个编排式编程的例子。这不该这么难。难的是猜到底该点哪个随机的 markdown 文档才能找到例子。」andai 补充说 GitHub 「为了你的方便」把 RESEARCH.md 藏起来了,得点「Show All Files」才知道它存在。 - jerf 给出了整场讨论里最被作者认可的一条建设性反馈:「我喜欢这份野心,也喜欢它不是我常见的那种『2015 年技术水平』的语言。它在尝试做真正不一样的事。『把东西从学术界搬出来让它能用』这个领域丰饶而未被充分开垦。」但他随后说,你的光被藏在斗底下了——我得挖半天才能找到那些真正新的东西。他建议整份文档重新校准,把新东西作为焦点:HN 上的人常抱怨语法示例不够醒目,但对你来说,第一件该砸向新访客的东西就是编排这个想法。搭一个简单例子,比如一个由语言构造保证原子安全的并发远程计数器,然后立刻深入解释这意味着什么。「先别管语言其余那些平淡的语法,直接扎进去讲那是什么、意味着什么。我看到 docs/ 目录里的结构,你的程序员脑子大概会因为把编排放在 1-8 章之前而难受,但你可以安全地假设:如果编排勾住了他们,他们会留下来学其余的;而你不能假设新读者会趟过其余所有平淡细节才走到真正有趣的部分。」作者承认自己掉进了「自下而上」组织文档的作者陷阱,正在重构 README 首页。不过这条回复本身引发了新一轮争议:adastra22、Jabbles、valorzard 都觉得这条回复读起来像 LLM 生成的,Jabbles 直言「用 AI 废话回复一条深思熟虑的评论相当无礼」,而 arcrevenant 的观察相对宽容:「这不是 LLM 在说话,是一个正在跟着 LLM 学写作的孩子,仔细看就知道。不过依然令人担忧。」
- vlovich123 提出了整场技术讨论中最核心的问题:「我不明白你怎么能保证不出现分布式死锁。Claire 给 Bob 发消息,但 Bob 在等 Alice,而 Alice 在等 Claire——什么阻止了这种编排?是像 Rust 的内存安全那样,不是所有合法程序都被接受,但所有非法程序都被拒绝吗?」这引出了本帖最有价值的一条回复。fmontesi(从内容看是该领域的学术研究者,提到了自己参与的 choral-lang.org)解释道:编排式语言提供了表达通信意图的抽象,典型的原语形如
Alice.expr -> Bob.x,读作「Alice 把 expr 的求值结果传给 Bob,Bob 把消息存进本地变量 x」。这让写出不匹配的通信动作成为不可能,因为你是在一条原子指令里同时表达了发送和接收动作——它们在构造上就是匹配的。然后你构建一个编译器(通常叫 projection),为 Alice 和 Bob 分别生成分布式程序,前者向 Bob 发送、后者从 Alice 接收。「我们喜欢为这些编译器建立形式模型并数学地证明其正确性。一个尊重编排的编译器会自动保证编译出的代码无死锁,无需复杂的检查,因为源编排在语法上就无法表达死锁的项。」关于表达力问题,他说这是非常活跃的探索领域,尚无定论,但令人惊讶的是,对某些分布式语言理论而言,编排式编程已被证明是完备的——它能捕获这些理论中可建模的所有无死锁系统;第一个这类结果是关于线性逻辑可描述的所有交互行为(在 Curry-Howard 与进程演算的对应下),后续工作还处理了递归行为甚至进程派生(fork)。他还提到用数学建模的编译器让我们可以激进地优化生成代码(例如加入更多异步性,如 Ozone 的做法)。minraws 用更朴素的语言给了同样的直觉:你可以把它想成一份协议,协议不把「Claire 发送」和「Bob 接收」描述成两个互相等待的独立动作,而是描述成全局程序/状态里的单次通信。 - imthenitto 提出了本帖对语言设计最扎实的质疑,值得完整转述:宣传语是「内存、线程、网络只有一条所有权规则」,但这里其实有两条规则,而且它们互相拉扯。所述的资源规则是线性的:资源一旦被使用就不能再用。Perceus 不是这样的——Perceus 存在的前提恰恰是值是被共享的,它插入 dup/drop,然后在引用计数恰好为 1 时原地复用分配。如果语言中一切都真的是「用一次」,你根本不需要引用计数,线性类型系统会静态地帮你完成释放。Perceus 出现在设计里,意味着别名是被允许的,也就意味着「一个所有者」描述的是 socket/中断那一层,而不是内存。作为设计这没问题,但那样一来宣传语就成了「两条押韵的规则」,而 FAQ 的核心主张(「你只需要学一条规则」)会是最先在真实程序面前崩掉的东西。他还提了一个更具体的问题:Koka 和 Lean 之所以能靠引用计数过关,部分原因是它们的数据在构造上压倒性地是无环的;而 Wyzer 的 README 展示了
var绑定和可变结构体字段——可变加引用计数就会产生环,而环会泄漏。计划是什么?环回收器、弱引用、类型层面的无环限制,还是接受泄漏?「DESIGN.md 里应该直说,因为这是任何有 RC 经验的人问的第一个问题。」 - README 里那句「Go、Java、C#、Python 使用垃圾回收器,这让它们更易用但更慢、更不可预测」被 pron(自称在 JVM 团队工作、有约 25 年大型 C++ 软件经验,包括硬实时和软实时系统)盯上,引发了整帖最长最激烈的技术辩论。他的论点是:「垃圾回收器」这个词覆盖了一整个算法谱系,有些会拖慢你(而且未必是你以为的原因),有些的发明目的恰恰是把内存管理做得比 C++ 更快;Python 的(主要是)引用计数 GC 在内存管理开销上更接近 C 而不是 Go 或 Java,而且它也不是 Python 慢的原因;Java 用的是移动式收集器,比 C++ 的内存管理更快,其中一些甚至更可预测。他强调移动式收集器是对空闲链表方式的优化,而非为了便利做的妥协;低级语言并不是为最大性能优化的,而是为最大低级控制优化的,而移动指针与低级控制不兼容,这些语言把低级控制置于一切之上、包括性能。他还给出了一个具体数字:刚跟某家世界最大科技公司的性能团队的人聊过,他们一些较大的 Rust 程序把 30% 的 CPU 花在 malloc/free 上。jesse__(自称写过两个用于动态语言运行时的移动式收集器、做过实时 3D 图形)激烈反驳,说复制式 GC 光是搬运数据就吃掉约 10% 的总内存带宽,而只要用理智的分配策略(arena、freelist、对象池)就能把内存分配的系统资源占用压到 1% 以下,并指责 pron 引用的 1986 年论文对现代硬件几乎不相关。pron 回应说好的移动式收集器被设计成尽量少复制(这就是分代的全部目的),而在你负责一个由大团队维护十几年的 200 万行 C++ 系统时,那些优化的代价非常高昂;他所在的地方把大部分系统(主要是软实时的国防软件)从 C++ 迁到了 Java,为的就是更好的性能。hitekker 出来打圆场:「没必要动气。你回复的这位是语言专家,他不是在贬低你。GC 在过去十年确实进步了不少,尽管它们对你职业生涯关注的用例可能仍然不合适。」
- 关于「为什么不直接改进 Rust」,gokaygurcan 的提问代表了一类怀疑:「看到一门新语言 → 点进去 → 是 Rust。真诚地问:Rust 到底哪里难到你没法说服别人、直接给它贡献呢?我记得这类项目很少能活下来。」DoesntMatter22 的回复相当直接:「这是很糟的态度。『你应该去做你不喜欢的那个东西而不是你喜欢的,因为你喜欢的那个可能不会成功』。」
- 若干评论者指出了相邻的前人工作:pjmlp 问为什么没提更成熟、有产业支持的 Chapel;nicoburns 观察说编排式编程那份文档看起来非常像 Next.js / Dioxus / Leptos 里实现的「server functions」,只不过泛化成了语言特性;slifin 提到 klor;jnpnj 提到 Clojure 的 Electric(同样试图把不同主机上的计算表示为一个表达式);rbr94 分享了一份关于编排的易懂入门漫画;fxj 问它与 MPI、Chapel、Co-Array Fortran 以及 Rust 扩展 ChoRus 的区别。tonyg 在有人调侃「编排式编程是不是在硬造 buzzword」时给出了历史纠正:这个术语在产业界可以追溯到 00 年代初的 WS-* 系列,学术上则源自 90 年代的进程演算和 00 年代末至 10 年代初的会话类型(session types)。
- 也有相当刻薄的声音。dwrobert 说:「我能找到的『编排式编程』测试只有一两行长,什么也没证明,肯定没有研究文本里声称的那么精巧。这就是 slop。」onlyrealcuzzo(自称也在做这个领域的语言)说,从那些极简的示例看,「它看起来就是没有借用检查器的 Rust。借用检查器给 Rust 带来了坏名声,但那只是 Rust 难的很小一部分原因,你没有给出任何关于你怎么解决其余问题的洞察。」
- 最后是身份疑云。stack_framer 把大家的困惑总结成一串:「根据这里的评论,我们真的该相信下面这些事发生了吗?1. v0id_isgood 这个 HN 用户冒充了 Wyzer 的真实作者并发了这条 Show HN。2. 然后他把自己 HN 账号的凭证留在了 Wyzer 的 Discord 里。3. 现在真正的作者显然正在用 v0id_isgood 这个账号回答问题?这整件事感觉像一场非常奇怪的恶作剧。」帖子末尾,作者本人(用同一账号)留下了收尾发言:他听取了反馈并花时间改进了项目,不知道该怎么删掉这条 HN 因为账号被人冒充了;为大家期待的是一门打磨完成的语言、结果看到的是一个半成品而道歉;按大家的要求更新了 README 并把文档挂上了网站,欢迎更多反馈、协助和提问,「不过我还得好好修文档,但我更想先专注在语言本身」。