Gleam 入门:BEAM 世界里的类型安全

这篇要解决的问题

BEAM 虚拟机(Erlang/Elixir 跑的那套运行时)有两样别处很难买到的东西:以轻量进程为基础的并发模型,和”随它崩溃”的容错哲学,服务一跑数年不重启。但这个生态的语言全是动态类型——重构靠测试,字段拼错要等运行时才炸。Gleam 补的就是这块:一门编译到 BEAM 的静态类型语言,类型错误在编译期拦下,运行时能力原封不动继承;它还有第二个后端,能编译成 JavaScript 跑在浏览器里。项目 2024 年 3 月发布 1.0,写作时最新版是 1.12(2025 年 8 月初发布),是一门已经过了里程碑、但生态仍在爬坡的语言。这篇按”装起来、看类型系统、与 Elixir 互操作、判断要不要用”的顺序走。

安装与 gleam new

Gleam 依赖 Erlang/OTP 运行时(2025 年时要求 OTP 26 及以上,具体以官方文档为准),先把它装上(brew install erlang 或用 asdf 管理版本),再装 Gleam 本体——官网提供各平台二进制,brew 也有维护。之后是标准流程:

gleam new todo && cd todo   # 生成项目骨架:src/ 放代码,test/ 放 gleeunit 测试
gleam run                   # 编译并执行 src/todo.gleam 里的 main
gleam test                  # 跑测试
gleam add gleam_http        # 加依赖,gleam.toml 是项目清单

编译产物是 Erlang 源码,再经 OTP 编译成 BEAM 字节码——这意味着 Gleam 与 Erlang/Elixir 天然共享一个运行时世界,不是模拟层。依赖也搭了现成的便车:Gleam 的包发布在 Hex 上,与 Elixir 共用同一个包仓库。入口程序长这样:

import gleam/io

pub fn main() {
  io.println("Hello, Gleam!")
}

类型系统与 Result

Gleam 的类型是”代数数据类型”(ADT,algebraic data type):类型由几个变体一次性列全,编译器据此做穷尽检查——你漏处理一个分支,它拒绝编译。值默认不可变,没有 null:

pub type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
}

pub fn area(shape: Shape) -> Float {
  case shape {
    Circle(radius: r) -> 3.14159 *. r *. r
    Rectangle(width: w, height: h) -> w *. h
  }
}

错误处理上,Gleam 没有 BEAM 语言的异常风格,函数失败用 Result(a, e) 表达——成功是 Ok 值,失败是 Error 值,错误是类型的一部分:

import gleam/int
import gleam/io
import gleam/result

pub fn main() {
  // 管道操作符:数据从左往右流,Gleam 没有链式方法调用
  "42"
  |> int.parse          // -> Result(Int, Nil):失败不是异常,是普通值
  |> result.unwrap(0)   // 就地兜底;要显式分派用 case Ok/Error 两分支

  case int.parse("abc") {
    Ok(n) -> io.println("数字是 " <> int.to_string(n))
    Error(_) -> io.println("不是数字")
  }
}

对照 BEAM 上的动态类型语言,差异一目了然:

维度 Elixir/Erlang Gleam
类型检查时机 运行时,动态类型 编译期,静态类型
错误模型 异常 + let it crash Result 值(进程级容错仍在)
空值 nil / :error 约定俗成 没有 null,用 Option 类型
重构支持 靠测试兜底 编译器全量检查
库生态 十余年积累,量大 年轻,常靠 FFI 包一层动态库

与 Elixir 互操作

Gleam 用 @external 声明外部函数,直接调用 BEAM 上任何模块——包括 Elixir 标准库(Elixir 模块在 BEAM 上以 Elixir.String 这样的原子名存在):

@external(erlang, "Elixir.String", "upcase")
fn elixir_upcase(s: String) -> String

pub fn main() {
  io.println(elixir_upcase("hello from gleam"))  // HELLO FROM GLEAM
}

反向也通:Gleam 模块编译后就是普通 BEAM 模块,Elixir/Erlang 代码可以直接调用。测试走 gleeunit,gleam new 生成的 test 目录里已有入口,断言风格接近单元测试框架的常规用法。实践中有一个纪律要点(反模式点名):大量裸调动态类型的 FFI、返回值不做类型包装,等于把动态类型的不可靠又请回了门里——正确姿势是在边界处把外部调用包成 Gleam 类型(返回 Result 或具名类型),让不可靠止步于边界。

什么时候选它

值得现在选的场景:想给现有 Erlang/Elixir 服务补类型安全的团队(Gleam 模块可无缝混入);个人或小团队的新服务,接受为生态年轻付一点成本换取编译期检查;需要前后端同一门语言的小项目(JS 后端 + lustre 这类 Elm 架构的前端框架)。Web 服务侧已有 mist/wisp 这类 BEAM 原生的 HTTP 组件可用,但与 Elixir 的 Phoenix 相比年轻得多,重型 Web 场景仍以 Phoenix 为成熟选项。

要诚实对待的边界:生态规模与 Elixir/Erlang 不在一个量级,很多能力要靠 FFI 包装动态库,团队招聘也困难。它更现实的用法是”在 BEAM 世界里引入类型”的渐进工具,而不是整套技术栈的替代品。对观望者,Gleam 的学习曲线友好(语言本体很小),跑通 hello world 一小时内就能完成,试水成本是这批新语言里偏低的,值得动手一试。

延伸阅读:BEAM 运行时与 Phoenix 的全貌见《Elixir 与 Phoenix 入门:常驻内存与 LiveView》;PHP 侧的常驻内存路线可对照理解 BEAM 进程模型要解决的问题,见《Workerman 常驻内存编程》;常驻进程配套的队列实践见《用 PHP 写一个可靠的队列任务系统》。

版本核实(以官方文档为准):写作时 Gleam 最新版为 1.12(2025-08-05 发布),1.0 于 2024 年 3 月发布;gleam new/run/test/add、ADT 与 case 穷尽检查、Result(a, e)、管道操作符、@external FFI(含 Elixir.String 原子名调用)均为 1.x 已发布行为;mist/wisp/lustre 为当时活跃的社区项目,版本与 Erlang/OTP 最低要求以官方文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇