把一门纯函数式语言的绝对安全与优雅,和一门现代底层语言的原始执行速度放在一起,会打开一个此前难以触及的维度。此前我介绍过 phpurs,一个把 PureScript 的数学级安全带到仍由 PHP 驱动的七成网络世界的编译器后端。那一次的目标是证明:即使目标运行时乍看并不自然,也不必牺牲现代安全性与开发体验。
这一次要看的正好是光谱的另一端。如果 PHP 代表极致的可移植性与遗留兼容,那么当我们追求纯粹的、接近裸金属的速度时会发生什么?当我们想要真正的多核并发、静态原生二进制,以及为极端高吞吐设计的垃圾回收器时,答案会是什么?
![]()
这就是我最近的项目之一:gopurs,一个高度优化的 PureScript 到 Go 编译器。先把结论说在前面:把 PureScript 的高层语义纯度与 Go 的原始执行力结合起来之后,最终编译出的代码在纯计算基准上追平了 Chez Scheme。Chez Scheme 是函数式提前编译领域历史上的绝对冠军之一,背后是数十年的编译器优化积累。
突破提前编译天花板:TAST 如何改变局面
历史上,把动态或高度多态的函数式语言编译到 Go 这类静态类型语言,一直是个难题。早期尝试往往把所有东西映射到 Go 的 interface{},也就是 any。这样做能跑通,但代价是性能。每个赋给 interface{} 的原始值都会被装箱并逃逸到堆上,给 Go 的垃圾回收器带来巨大压力。
为了解决这个问题,gopurs 最初使用了一个扁平的 Value 结构体,这是一种受 V8 启发的带标签联合体,尽量把数据留在栈上。这已经快了很多,但仍然不够。真正的瓶颈在于缺少结构类型信息:PureScript 标准的 CoreFn AST 会擦除类型。
这正是类型化抽象语法树发挥作用的地方。gopurs 依赖 PureScript 编译器的一个定制分支,以及出色的 purescript-backend-optimizer,把深层类型信息一路保留到代码生成阶段。这让编译器能够执行严格的单态化。热路径上零装箱、零开销。抽象在编译期直接消融,只留下 Go 编译器可以激进优化的高速命令式循环。
无惧并发:Aff 遇上 Goroutine
PureScript 最强大的特性之一是 Aff 单子,一种强大且可组合的异步效果处理方式。在 JavaScript 世界里,Aff 被小心翼翼地映射到单线程事件循环上。而在 gopurs 的目标环境中,情况完全不同:Go 原生提供 Goroutine,这让 PureScript 的异步模型有机会直接落到真正的多核并发之上。
这种结合的意义在于,开发者仍然用 PureScript 的高层抽象编写异步逻辑,不需要手动管理线程或锁,但编译产物却可以利用 Go 运行时的调度器,把工作分布到多个核心上。最终得到的是静态原生二进制,启动快、部署简单,同时保留了函数式语言在组合异步操作时的表达能力。
从 phpurs 到 gopurs,这条路线展示的是同一种思路的两个方向:一端是兼容与普及,另一端是速度与并发。两者共同说明,PureScript 的语义安全并不必然以牺牲运行环境为代价。相反,通过保留足够的类型信息,编译器可以在完全不同的目标上释放出接近手写底层代码的性能。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.