命令行工具
anzellai/sky avatar
anzellai/sky

Sky 语言:用 Elm 风格写全栈,编译成类型安全的 Go

该项目围绕「Sky, an Elm-inspired language that compiles to Go. Hindley-Milner types, server-driven UI (Sky.Live), single binary output.」构建,适用于实际场景的开源实践,提供可复用的工具链与集成方式。

432 个 Star10 个 ForkGoApache-2.0
GitHub

秒懂

它是什么?
Sky 是一门受 Elm 启发的函数式语言,编译为带类型的 Go 代码,主打“能编译就能跑”。本文拆解它的类型系统、多端渲染机制、安装方式,以及它在真实项目中的适用边界。
适合谁用?
Sky 适合两类人:一是想要 Elm 式纯粹性,但又必须部署在 Go 生态里的团队,二是需要从同一套代码生成 Web、桌面、移动端界面的全栈开发者。不适合的是那些对 Rust 工具链有顾虑、或者希望运行时能动态加载代码的人,Sky 的编译期检查严格,但这也意味着每次修改都要重新编译。
能商用吗?
可以。Apache-2.0 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
还在维护吗?
在维护。仓库在最近一天内有新的提交。
用什么语言写的?
主要是 Go(依据 GitHub 的语言统计)。

以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。

开源项目深度解析

一个语言,四种界面,一套状态循环

Sky 解决的核心问题是:全栈应用的状态管理、界面渲染和部署形态,能否用一套代码统一。它借鉴 Elm 的架构,每个应用都围绕 init、update、view 和 subscriptions 四个函数组织。状态更新是纯函数,副作用被封装在 Task 类型里,界面则用 Std.Ui.Element 描述。同一个 view 函数,可以渲染成 DOM(Sky.Live)、终端 ANSI 字符(Sky.Tui)、桌面 WebView(Sky.Webview),或者编译成 wasm 跑在浏览器里(Sky.Spa)。这意味着你不需要为 Web 写一套 React,为移动端写一套 SwiftUI,为 CLI 再写一套 curses。文档里强调,Sky.Live 适合服务端持有状态、需要即时首屏的场景,而 Sky.Spa 适合纯 UI 切换要零延迟、后端是无状态 API 的场景。这种按部署形态而非按平台划分的方式,是它和其他跨平台框架最本质的区别。

Hindley-Milner 类型系统如何保证‘能编译就能跑’

Sky 的类型系统是 Hindley-Milner 风格,支持显式类型标注和穷尽模式匹配。编译器的核心保证是:所有可能失败的函数返回 Result Error a,所有副作用返回 Task Error a,不存在运行时的 null 或未捕获异常。更关键的是,sky check 命令会调用 go build 来验证生成的 Go 代码,所以任何类型形状不匹配都会在编译期暴露。这个设计把类型检查延伸到 Go 的层面,而不仅仅是 Sky 自己的抽象语法树。对于习惯了动态语言的开发者,这种双重检查可能显得繁琐,但它的好处是:一旦编译通过,运行时崩溃的概率被压到极低。README 里举例说,Stripe SDK 的约 7.6 万个 FFI 符号能被编译并 tree-shake 成 4 千行的 main.go,这得益于 Go 包导入时自动生成类型化绑定,而不是手写胶水代码。

编译器重写:从 Haskell 到 Rust 的工程决策

Sky 的编译器最初用 Haskell 编写,但在 v0.17 之后,团队决定用 Rust 重写。原因很具体:旧的编译器有一个数千行的单一 lowering 阶段,使用 IORef 可变状态,这与 Sky 承诺的纯函数语义相悖;Hindley-Milner 求解器需要硬性内存预算才能保持有界。Rust 版本采用 cargo workspace,拆分成 lexer、parser、名称解析、类型推断、类型导向的 lowering、Go 代码生成、FFI、格式化器、LSP 等独立 crate。每个架构决策都有明确的模块边界。旧编译器保留在 legacy-haskell-compiler/ 目录下,作为字节级差分测试的 oracle,直到 v1.0 发布。这个重写对用户的影响是:编译速度更快,内存占用更可预测,但如果你是从旧版本升级,可能需要重新编译所有依赖。

从零开始:安装、初始化、运行一个计数器

安装 Sky 只需要一条命令:curl -fsSL https://raw.githubusercontent.com/anzellai/sky/main/install.sh | sh,适用于 macOS 和 Linux。或者从源码构建,需要 Rust 工具链:git clone 仓库后,在 rust/ 目录下运行 cargo build --release -p sky。sky 二进制内嵌了运行时、标准库和 Sky Console,最终用户只需要 sky 在 PATH 里,以及 Go 1.21+ 用于代码生成。初始化项目用 sky init hello,然后 sky run src/Main.sky 就能在 localhost:8000 启动一个服务端渲染的计数器应用。README 给出的计数器示例展示了类型别名 Model、消息类型 Msg、以及 update 函数用 case 表达式做穷尽匹配。如果你想把同一个 view 渲染到终端,只需把 Live.app 换成 Std.Tui.app。标准库的文档可以用 sky doc --serve 启动本地文档服务。

标准库与 Go 生态:FFI 不是胶水,是代码生成器

Sky 的标准库覆盖面很广:Std.Db、Std.Auth、Std.Ui、Std.Cache、Std.Email、Std.Log,还有 HTTP 客户端和服务端、WebSocket、JSON、JWT、CSV、加密、可观测性。这些模块是编译进二进制的,所以最终产物是一个静态 Go 可执行文件,可以用 scp 或 Docker 部署,甚至 brew install。真正的杀手锏是 sky add github.com/some/package,编译器会内省 Go 包的 API,生成严格的类型化 Sky 绑定,不需要手写 FFI。这意味着 Go 生态里几乎所有库都能直接使用,只要它不依赖 CGO 或反射这类动态特性。README 提到 Stripe SDK 的例子,说明大型 SDK 也能被编译和 tree-shake。但这里有个隐含的约束:如果 Go 包使用了泛型或高级接口,生成的绑定可能会很复杂,实际效果需要逐个验证。

多端渲染的代价:Sky.Spa 的 wasm 路径与平台限制

Sky.Spa 是 v0.22.0 引入的,它把同一个 view 编译成 wasm,运行在浏览器、桌面 WebView、iOS 和 Android 上。表面上看,这解决了跨平台问题,但有几个限制值得注意。首先,wasm 客户端的 UI 更新是本地和即时的,但后端必须是无状态 API,这意味着你不能像 Sky.Live 那样在服务端持有会话状态。其次,移动端的 iOS 和 Android 是通过 WebView 包装的,性能可能不如原生 UI,而且访问摄像头、GPS 等原生功能需要额外的桥接,文档没有详细说明。最后,wasm 在 iOS 上受限于 Safari 的 JIT 策略,如果应用逻辑复杂,性能可能退化。所以 Sky.Spa 适合 CRUD 类应用,但对图形密集型或需要深度原生集成的应用,可能不是最佳选择。

与 Elm 和 Go 生态的对比:哲学相同,路径不同

Sky 最接近的替代品是 Elm 本身。Elm 也有 HM 类型系统、纯函数 UI 和“没有运行时异常”的保证,但 Elm 编译成 JavaScript,运行在浏览器里,服务端支持有限。Sky 的差异在于它编译成 Go,可以跑在服务端、CLI、桌面和 wasm,而且能直接导入 Go 包。如果你只需要浏览器端应用,Elm 的生态更成熟,工具链更简单。另一个替代方案是直接用 Go 写后端,配合 React 或 Vue 做前端,但这样就失去了类型安全在前后端之间的统一性。Sky 的独特之处是把类型检查延伸到 Go 代码生成层,这是一种更激进的保证。但代价是,你的整个技术栈都被绑定在 Sky 的编译器上,如果编译器有 bug 或者停止维护,迁移成本很高。

维护与升级:v0.23 的稳定承诺与 Rust 重写的影响

根据 README,v0.23.x 的公共 API 对 v1.0 线是稳定的,小版本只会添加功能,不会破坏现有代码。但内部实现仍可能在小版本之间变化。编译器用 Rust 重写后,升级时需要重新编译编译器,但生成的 Go 代码契约不变。旧 Haskell 编译器保留作为差分测试的 oracle,直到 v1.0 发布,这意味着在 v1.0 之前,你依赖的是两个编译器的输出一致性。许可证是 Apache-2.0,这允许商用和修改,但如果你修改了编译器,需要保留版权声明。维护成本方面,Sky 的文档和工具链看起来完善,但它是相对小众的项目,社区规模和第三方库数量远不如 Elm 或 Go 本身。如果你选择它,需要有心理准备:遇到问题时,可能只能靠 GitHub issues 和文档解决。

编辑结论

Sky 适合两类人:一是想要 Elm 式纯粹性,但又必须部署在 Go 生态里的团队,二是需要从同一套代码生成 Web、桌面、移动端界面的全栈开发者。不适合的是那些对 Rust 工具链有顾虑、或者希望运行时能动态加载代码的人,Sky 的编译期检查严格,但这也意味着每次修改都要重新编译。在采用前,先验证三件事:你的 Go 版本是否满足 1.21+,你依赖的 Go 包能否被 Sky 的 FFI 正确生成绑定(尤其是涉及 CGO 或反射的库),以及 Sky.Live 的 SSE 连接在你的部署环境下是否稳定。如果你接受编译期即部署期的哲学,Sky 的 v0.23 稳定 API 值得一试,但如果你需要运行时热更新或动态类型,它可能不是正确的工具。

官方来源

  1. Official README
  2. Project repository
  3. Release notes
社区笔记

社区笔记