编程 BEAM 运行时上的新选择:Gleam 语言如何用类型系统重新定义后端开发

2026-08-09 06:15:48 +0800 CST views 7

Gleam 编程语言深度拆解:当类型安全遇上 BEAM 运行时——从 OTP 架构到双编译目标,一门让"错误在编译期消失"的工程语言

一、引言:为什么 Gleam 值得你花时间

2026 年 7 月 29 日,Gleam 发布了 1.18.0 版本。在过去一年里,这门语言从 1.12 走到了 1.18,每一次发布都在打磨同一个核心承诺:让你用最少的认知负荷,构建出能 Scale 到任意规模的系统

这不是一句营销话术。Gleam 做到这件事的方式很有意思——它没有发明新轮子,而是把两套久经沙场的运行时拼接在一起:Erlang VM(BEAM)JavaScript 引擎,然后用一套强类型系统把它们包裹起来,消除了这两套运行时原本各自的最大痛点。

对 BEAM 生态来说,Elixir 和 Erlang 已经有成熟的类型注解系统(dialyzer),但那是渐进式的——你可以不写,跑了再说。对 JS/TS 生态来说,TypeScript 已经是标配,但它的类型系统是"结构化"的、"宽松"的——两个名字相同的接口可以互换,只要字段兼容。这意味着大量运行时 bug 其实藏在 TypeScript 的类型缝隙里。

Gleam 的答案是:代数数据类型 + 泛型 + 模式匹配,让不可表示的状态根本无法进入你的代码

本文从架构原理出发,配合可运行的代码示例,系统性地拆解 Gleam 的类型系统、Actor 并发模型、OTP 设计模式、双编译目标,以及 1.18.0 最新引入的 JS 性能优化。


二、Gleam 是什么:从历史脉络说起

2.1 三条技术路线的交汇点

Gleam 的诞生背景很有意思。它站在了三条成熟技术路线的交汇处:

第一条:Erlang/OTP 的容错哲学。 WhatsApp 用 Erlang 构建了支撑 9 亿用户的消息系统, Ericsson 用 Erlang 驱动了电信交换设备。爱立信给 Erlang 的评价是:"这些设备每年宕机时间不超过 5 分钟。" 这种容错能力来自 OTP 库的设计哲学——让故障隔离,而不是让每个组件都假设自己不会故障。

第二条:ML 系语言的结构化类型系统。 Haskell、OCaml、F# 已经证明了代数数据类型(ADT)和模式匹配能写出怎样的可靠代码。Gleam 直接继承了这条血脉,类型系统更接近 OCaml 而非 Haskell——没有 typeclass,没有 Monads,不需要学范畴论就能用。

第三条:JavaScript 生态的工程化需求。 前端开发者需要类型安全,但又不想要复杂的类型系统。TypeScript 的成功证明了一个观点:开发者愿意接受约束,但不愿意接受认知负荷过高的约束。Gleam 编译到 JavaScript 的目标,就是给这部分人一个更严格的选择。

2.2 语言定位:友好但有原则

Gleam 官方对自己的定位是"friendly language for building type-safe, scalable systems"。这里有两个关键词:

  • Friendly(友好):语法现代,没有奇怪的符号噪音。fnletimport 这些关键字对任何现代语言使用者都很直观。
  • Type-safe(类型安全):类型系统是强制的,不是可选的。不写类型注解时,编译器会推断,但不允许隐式类型转换。
// 这段代码在 Gleam 中无法编译——类型不匹配
pub fn main() {
  let name: String = 42  // 错误:期望 String,实际是 Int
}

三、类型系统:让非法状态不可表示

3.1 代数数据类型(ADT)——Gleam 类型系统的基石

Gleam 的类型系统核心是 Algebraic Data Types(ADT)。这听起来很学术,但用法极其直观。

// 定义一个结果类型——比 Result<T, E> 更具体的业务表达
pub type PaymentStatus {
  Pending(order_id: String, created_at: Int)
  Processing(order_id: String, started_at: Int)
  Completed(order_id: String, completed_at: Int, transaction_id: String)
  Failed(order_id: String, reason: String, retry_count: Int)
}

这段代码定义了一个 PaymentStatus,它有且仅有四种状态。不可能出现"支付中但没有订单号"的状态,因为类型系统禁止它。

对比 TypeScript:

// TypeScript 版本——同样的业务逻辑,但类型无法约束状态
interface PaymentStatus {
  status: 'pending' | 'processing' | 'completed' | 'failed'
  order_id?: string
  started_at?: number
  completed_at?: number
  transaction_id?: string
  reason?: string
  retry_count?: number
}
// 这里你可以写出 { status: 'completed' } 但没有 transaction_id
// 运行时才会发现这个问题

这就是"让非法状态不可表示"的含义——在 TypeScript 中,你需要额外的运行时检查或测试来发现这类 bug;在 Gleam 中,编译器直接拒绝编译。

3.2 Result 类型:错误即值

Gleam 对错误的处理方式是 Result<T, E> 模式,这是一种强迫你直面错误的类型设计:

import gleam/io
import gleam/result

pub type DatabaseError {
  ConnectionFailed(host: String, port: Int)
  QueryTimeout(query: String, timeout_ms: Int)
  RecordNotFound(table: String, id: String)
}

// 返回 Result,而不是抛出异常
pub fn find_user(user_id: String) -> Result(User, DatabaseError) {
  case user_id {
    "" -> Error(RecordNotFound("users", "empty_id"))
    id if String.length(id) < 8 -> Error(RecordNotFound("users", id))
    _ -> {
      // 正常查询逻辑
      Ok(User(id: user_id, name: "Alice", email: "alice@example.com"))
    }
  }
}

// 调用方必须处理所有可能的错误路径
pub fn get_user_email(user_id: String) -> String {
  case find_user(user_id) {
    Ok(user) -> user.email
    Error(ConnectionFailed(host, _)) -> {
      io.println("Database connection to " <> host <> " failed")
      "offline@example.com"
    }
    Error(RecordNotFound(_, _)) -> "unknown@example.com"
    Error(QueryTimeout(query, timeout)) -> {
      io.println("Query timed out: " <> query <> " (timeout: " <> int.to_string(timeout) <> "ms)")
      "offline@example.com"
    }
  }
}

关键点:Gleam 没有 try/catch,没有 throw。所有可能失败的操作都返回 Result<T, E>,调用方必须在编译期穷尽所有 case 分支。你不能漏掉一个 Error 分支而不被编译器警告。

这带来一个实际工程价值:当你阅读一段 Gleam 代码时,你可以确信作者已经处理了每一种错误路径。 不存在"这里可能抛异常但我没处理"的情况。

3.3 泛型与类型推导

Gleam 的类型推导足够强大,大多数时候不需要显式标注类型:

// 编译器自动推断:first_element 返回 a 类型的值
pub fn first_element(list: List(a)) -> Result(a, Nil) {
  case list {
    [] -> Error(Nil)
    [head, ..] -> Ok(head)
  }
}

// 显式标注的类型用于文档和接口契约
pub fn map_over_list(list: List(a), f: fn(a) -> b) -> List(b) {
  case list {
    [] -> []
    [head, ..tail] -> [f(head), ..map_over_list(tail, f)]
  }
}

泛型在 Gleam 中通过 fn(a) -> b 这种简洁的语法表达,不需要 Comparable 约束或 where 子句。类型系统足够简单,但足够强。

3.4 类型推断的边界:必须显式标注的场景

Gleam 编译器会推断几乎所有类型,但以下场景需要显式标注:

// 1. 公开 API 的函数签名(模块接口需要明确类型)
pub fn process_payment(payment: PaymentStatus) -> Result(String, DatabaseError) {
  // ...
}

// 2. 自定义类型构造器(防止歧义)
pub fn parse_status(raw: String) -> Result(PaymentStatus, String) {
  case raw {
    "pending" -> Ok(Pending(order_id: "ORD-001", created_at: 1699999999))
    _ -> Error("Unknown status: " <> raw)
  }
}

// 3. 类型别名与 Opaque Types
pub type UserId {
  UserId(value: String)
}

// Opaque Type:外部无法访问内部的 String,只能通过公开函数操作

四、Actor 并发模型:BEAM 上的 Greenthread

4.1 BEAM 的并发哲学:轻量进程,而非线程

BEAM VM 的并发单元是 进程(Process),但这个"进程"和 OS 进程完全不同——它是一个 轻量 Greenthread,创建成本约 ~1KB 内存,可以在单台机器上启动数百万个。

import gleam/process
import gleam/io

pub fn main() {
  // 创建一个"邮件槽"(Mailbox),用于接收消息
  let subject = process.new_subject()

  // Spawn 一个 Actor,它运行在独立进程中
  process.spawn(fn() {
    // 无限循环处理消息
    loop(subject)
  })

  // 向 Actor 发送消息
  process.send(subject, "Hello from main process!")
  process.send(subject, "Another message")

  // 阻塞等待响应
  let response = process.receive(after: 1000)
  io.debug(response)
}

fn loop(mailbox: process.Subject(String)) {
  case process.receive(after: 5000) {
    Ok(message) -> {
      io.println("Actor received: " <> message)
      loop(mailbox)
    }
    Error(_) -> {
      io.println("Timeout, shutting down actor")
    }
  }
}

这个模型的核心语义是:每个 Actor 有独立的内存空间,消息传递是 Actor 间通信的唯一方式。 这消除了共享内存的所有并发问题——没有锁,没有竞态条件,没有死锁(只要你遵守"只用消息通信"的原则)。

4.2 OTP 监督树:故障即恢复

BEAM 的真正威力来自 OTP(Open Telecom Platform)监督树。这是 Erlang/OTP 生态几十年生产经验总结出的容错架构。

import gleam/otp/actor
import gleam/otp/supervisor

// 定义 Worker 可以处理的消息类型
pub type WorkerMessage {
  ProcessJob(job_id: String, payload: String)
  StopWorker
}

// Worker 的初始化状态
pub type WorkerState {
  WorkerState(jobs_processed: Int, is_running: Bool)
}

// Worker 的消息处理函数
pub fn handle_worker_message(
  message: WorkerMessage,
  state: WorkerState,
) -> actor.Next(WorkerMessage, WorkerState) {
  case message {
    ProcessJob(job_id, payload) -> {
      io.println("Processing job " <> job_id <> ": " <> payload)
      actor.continue(WorkerState(
        jobs_processed: state.jobs_processed + 1,
        is_running: state.is_running,
      ))
    }
    StopWorker -> {
      io.println("Worker stopped after processing " <> int.to_string(state.jobs_processed) <> " jobs")
      actor.stop(process.Normal)
    }
  }
}

// 启动一个带监督的 Worker
pub fn start_worker() {
  let spec = actor.spec(actor.InitialState(
    state: WorkerState(jobs_processed: 0, is_running: True),
    init: fn(_) { actor.continue(WorkerState(jobs_processed: 0, is_running: True)) },
  ))

  // 使用 OneForOne 策略:如果一个 Worker 崩溃,只重启它
  let options = supervisor.typing(supervisor.OneForOne)

  case supervisor.start(spec, options) {
    Ok(_) -> io.println("Worker started successfully")
    Error(_) -> io.println("Failed to start worker")
  }
}

监督树的核心理念:每个 Worker 都有一个 Supervisor 父节点。当 Worker 因未捕获异常崩溃时,Supervisor 会收到退出信号,并根据重启策略决定如何处理。常见策略包括:

  • OneForOne:只重启崩溃的子进程
  • OneForAll:某个子进程崩溃,重启所有子进程(适用于互相依赖的组件)
  • RestForOne:某个子进程崩溃,重启它和所有后续启动的子进程

这意味着你可以把 故障恢复 当作一个架构层面的问题来解决,而不是在每个 Worker 里写 try/catch。Supervisor 负责"什么时候重启",Worker 负责"怎么处理业务逻辑"——关注点分离。

4.3 GenServer 模式:状态化 Actor

对于需要维护状态的消息处理 Actor,Gleam 提供了类似 Erlang GenServer 的模式:

import gleam/otp/actor
import gleam/io

pub type CounterMessage {
  Increment
  Decrement
  GetValue(reply_with: process.Subject(Int))
  Reset
}

pub type CounterState {
  CounterState(value: Int)
}

pub fn handle_counter_message(
  message: CounterMessage,
  state: CounterState,
) -> actor.Next(CounterMessage, CounterState) {
  case message {
    Increment -> {
      actor.continue(CounterState(value: state.value + 1))
    }
    Decrement -> {
      actor.continue(CounterState(value: state.value - 1))
    }
    GetValue(reply_with) -> {
      process.send(reply_with, state.value)
      actor.continue(state)
    }
    Reset -> {
      actor.continue(CounterState(value: 0))
    }
  }
}

pub fn start_counter(initial: Int) {
  let state = CounterState(value: initial)
  actor.start(state, handle_counter_message)
}

五、双编译目标:一份代码,两套运行时

5.1 编译到 Erlang/Elixir 生态

Gleam 最成熟的编译目标是 BEAM(Erlang VM)。编译产物是 .beam 文件,可以直接部署到任何 Erlang 运行时环境。

// gleam.toml 中指定编译目标
// name = "my_project"
// version = "1.0.0"

pub fn compile() {
  // Gleam 编译为 Erlang 的中间表示
  // pub fn main() -> Int 变成:
  // main() -> 42.
  42
}

当你编译到 Erlang 目标时,Gleam 会生成符合 Erlang/Elixir 互操作规范的代码。你可以:

  • 在 Gleam 中调用 Erlang/Elixir 函数
  • 在 Erlang/Elixir 中调用 Gleam 函数
  • 享受 BEAM 的热代码加载(Hot Reloading)——生产环境中无需停机更新服务

5.2 编译到 JavaScript:Node.js 与浏览器

Gleam 1.18.0 对 JavaScript 编译目标做了重要的性能优化。编译器现在能够识别语义上等价的数据结构,并在多次使用时复用同一个实例,而非每次都构造新对象。

// 编译到 JavaScript 时的优化示例
pub type Color {
  Red
  Green
  Blue
  Custom(r: Int, g: Int, b: Int)
}

// 如果代码中多次使用常量 Color.Red,
// 编译器会将其优化为单一实例引用,而非每次都创建新对象
pub fn get_default_color() -> Color {
  Red  // 编译到 JS:const RED = { type: "red" }; return RED;
}

pub fn paint_background() -> Color {
  Red  // 不再创建新对象,直接引用同一 RED 实例
}

这个优化的意义在于:Gleam 的不可变数据结构在 JS 中如果每次都重新构造,会产生 GC 压力。值相等(Structural Equality)的数据共享同一引用,既保证了语义正确(不可变数据共享是安全的),又减少了内存分配和 GC 开销。

5.3 双目标代码共享的实际场景

// 这段代码可以同时编译到 Erlang 和 JavaScript
import gleam/io

pub type ApiResponse {
  Success(data: String)
  Error(message: String)
  Loading
}

pub fn parse_response(json: String) -> ApiResponse {
  case json {
    "" -> Loading
    s if String.starts_with(s, "ERROR:") -> Error(message: String.drop_left(s, 6))
    data -> Success(data: data)
  }
}

// 在 BEAM 上:作为高性能后端服务处理请求
// 在 JS 上:作为前端数据模型处理 API 响应

六、实战:从零构建一个订单处理系统

6.1 项目结构

my_order_system/
├── gleam.toml
└── src/
    ├── order_system.gleam       // 领域模型
    ├── order_processor.gleam    // 业务逻辑
    ├── order_supervisor.gleam   // OTP 监督树
    └── main.gleam               // 入口

6.2 领域模型

// src/order_system.gleam

pub type Order {
  Order(
    id: String,
    items: List(OrderItem),
    status: OrderStatus,
    total: Int,           // 金额,单位:分
    created_at: Int,
  )
}

pub type OrderItem {
  OrderItem(product_id: String, name: String, quantity: Int, unit_price: Int)
}

pub type OrderStatus {
  Created
  PaymentPending
  Paid
  Processing
  Shipped
  Delivered
  Cancelled(reason: String)
}

// 业务约束:用类型系统保证订单有效性
pub fn validate_order(order: Order) -> Result(Order, List(ValidationError)) {
  let errors = []
  let errors = case order.items {
    [] -> [ValidationError("items_empty", "订单至少需要一件商品")]
    _ -> errors
  }
  let errors = case order.total <= 0 {
    True -> [ValidationError("invalid_total", "订单金额必须大于0"), ..errors]
    False -> errors
  }
  case errors {
    [] -> Ok(order)
    _ -> Error(errors)
  }
}

pub type ValidationError {
  ValidationError(code: String, message: String)
}

6.3 业务逻辑处理器

// src/order_processor.gleam

import gleam/io
import gleam/otp/actor
import gleam/process
import order_system.{Order, OrderStatus, ValidationError}

pub type ProcessorMessage {
  SubmitOrder(order: Order, reply_with: process.Subject(Result(Order, List(ValidationError))))
  CancelOrder(order_id: String, reason: String, reply_with: process.Subject(Result(String, String)))
  GetOrderStatus(order_id: String, reply_with: process.Subject(Result(OrderStatus, String)))
}

pub type ProcessorState {
  ProcessorState(
    orders: List(#(String, Order)),  // 内存存储,生产环境应接数据库
    next_id: Int,
  )
}

pub fn handle_processor_message(
  message: ProcessorMessage,
  state: ProcessorState,
) -> actor.Next(ProcessorMessage, ProcessorState) {
  case message {
    SubmitOrder(order, reply_with) -> {
      case order_system.validate_order(order) {
        Ok(validated) -> {
          let new_order = Order(..validated, status: PaymentPending)
          let new_state = ProcessorState(
            orders: [#(new_order.id, new_order), ..state.orders],
            next_id: state.next_id,
          )
          process.send(reply_with, Ok(new_order))
          io.println("Order " <> new_order.id <> " submitted successfully")
          actor.continue(new_state)
        }
        Error(errs) -> {
          process.send(reply_with, Error(errs))
          actor.continue(state)
        }
      }
    }
    CancelOrder(order_id, reason, reply_with) -> {
      case find_and_update_order(order_id, state.orders, fn(_) {
        OrderStatus.Cancelled(reason: reason)
      }) {
        Ok(#(updated_orders, _)) -> {
          process.send(reply_with, Ok("Order " <> order_id <> " cancelled"))
          actor.continue(ProcessorState(..state, orders: updated_orders))
        }
        Error(msg) -> {
          process.send(reply_with, Error(msg))
          actor.continue(state)
        }
      }
    }
    GetOrderStatus(order_id, reply_with) -> {
      case find_order(order_id, state.orders) {
        Ok(order) -> {
          process.send(reply_with, Ok(order.status))
          actor.continue(state)
        }
        Error(msg) -> {
          process.send(reply_with, Error(msg))
          actor.continue(state)
        }
      }
    }
  }
}

fn find_order(id: String, orders: List(#(String, Order))) -> Result(Order, String) {
  case orders {
    [] -> Error("Order not found: " <> id)
    [#(id, order), ..] -> Ok(order)
    [_, ..rest] -> find_order(id, rest)
  }
}

fn find_and_update_order(
  id: String,
  orders: List(#(String, Order)),
  update: fn(OrderStatus) -> OrderStatus,
) -> Result(#(List(#(String, Order)), Order), String) {
  case orders {
    [] -> Error("Order not found")
    [#(oid, order), ..rest] if oid == id -> {
      let updated = Order(..order, status: update(order.status))
      Ok([#(id, updated), ..rest], updated)
    }
    [pair, ..rest] -> {
      case find_and_update_order(id, rest, update) {
        Ok(#(updated_rest, result)) -> Ok([pair, ..updated_rest], result)
        Error(e) -> Error(e)
      }
    }
  }
}

6.4 OTP 监督树配置

// src/order_supervisor.gleam

import gleam/otp/supervisor
import gleam/otp/actor
import gleam/io
import order_processor.{handle_processor_message, ProcessorState}
import order_system.{Order, OrderItem, OrderStatus}

// 启动带监督的订单处理器
pub fn start_order_processor() {
  let processor_spec = actor.spec(actor.InitialState(
    state: ProcessorState(orders: [], next_id: 1000),
    init: fn(_) { actor.continue(ProcessorState(orders: [], next_id: 1000)) },
  ))

  // 使用 OneForOne:订单处理器崩溃后只重启它自己
  // max_restarts: 10, period: 1小时
  let options = supervisor.typing(supervisor.OneForOne)

  case supervisor.start(processor_spec, options) {
    Ok(pid) -> {
      io.println("Order processor started with PID: " <> process.pid_to_string(pid))
      Ok(pid)
    }
    Error(reason) -> {
      io.println("Failed to start order processor: " <> error_to_string(reason))
      Error(reason)
    }
  }
}

fn error_to_string(reason: supervisor.StartError) -> String {
  case reason {
    actor.StartError -> "Actor failed to start"
    process.StartError -> "Process failed to start"
  }
}

七、Gleam 1.18.0 新特性:语言服务器与编译优化

7.1 语言服务器的三项核心改进

Gleam 1.18.0(2026-07-29)最引人注目的改进是语言服务器(Language Server)功能的完善:

记录字段的完整 IDE 支持。 开发者终于可以在 VS Code 等编辑器中获得:

  • 跳转到定义:点击 record.field,跳到字段声明
  • 查找引用:找到所有使用该字段的地方
  • 重命名:修改字段名,所有引用自动更新
  • 支持范围包括:字段声明、带标签参数、带标签模式匹配、跨模块访问
// 在支持语言服务器的编辑器中,你可以:
// 1. 光标放在 status 上 → Ctrl+Click → 跳转到 OrderStatus 定义
// 2. 光标放在 Custom 上 → Find All References → 找到所有使用 Custom 的地方
// 3. 重命名 r → 整个项目中的 r/g/b 参数自动同步更新
pub type Color {
  Red
  Green
  Blue
  Custom(r: Int, g: Int, b: Int)
}

类型变量重命名。 这是一个被很多语言忽略的痛点——当你有一个泛型函数,类型参数命名不佳时,修改名字是个体力活:

// 之前:泛型类型参数 a/b 是内部实现
pub fn pipeline(value: a, transform: fn(a) -> b) -> b {
  transform(value)
}

// 1.18.0:可以重命名类型变量,IDE 自动更新所有出现位置
// 重命名 a -> InputValue 后,所有 fn(a) -> b 自动变为 fn(InputValue) -> b

模块重命名自动化。 当你重命名一个 .gleam 文件时,IDE 会自动查找所有 import 语句并更新路径。

7.2 JavaScript 编译目标的数据结构去重优化

这是 1.18.0 对 JavaScript 用户最有价值的优化。Gleam 编译器现在会分析代码中创建的常量数据结构,当两个数据结构"值相等"时,生成相同的 JavaScript 引用:

// 编译前(两份代码)
pub fn default_config() -> Config {
  Config(host: "localhost", port: 8080)
}

pub fn get_config() -> Config {
  Config(host: "localhost", port: 8080)  // 与 default_config 返回相同值
}
// 编译后(优化前:两次构造)
function default_config() {
  return { type: "Config", host: "localhost", port: 8080 };
}
function get_config() {
  return { type: "Config", host: "localhost", port: 8080 }; // 新对象
}

// 优化后:共享同一引用
const _CONFIG = { type: "Config", host: "localhost", port: 8080 };
function default_config() { return _CONFIG; }
function get_config() { return _CONFIG; }  // 直接返回同一引用

因为 Gleam 的数据结构是不可变的,所以这种优化是语义安全的——调用方无法修改共享对象,所以共享引用不会引入任何 bug。

7.3 Git 依赖的 Path 字段支持

// gleam.toml
// 1.18.0 之前:只能引用整个仓库
// dependencies = [
//   { name = "my_lib", version = "~> 1.0", from = "github.com/org/monorepo" }
// ]

// 1.18.0 之后:可以指定仓库内的子目录
dependencies = [
  { name = "my_lib", version = "~> 1.0", from = "github.com/org/monorepo", path = "packages/my_lib" }
]

这对使用 monorepo 的团队来说是重大利好——不再需要为了引入一个子包而克隆整个仓库。


八、与竞品的深度对比

8.1 Gleam vs TypeScript

维度GleamTypeScript
类型系统标称类型(Nominal),ADT结构化类型(Structural)
类型强制编译期强制编译期可选(strict: false
错误处理Result<T, E>,必须穷举try/catch + undefined/null
并发模型Actor(BEAM 原生)async/await(Promise)
编译产物.beam 或 .js.js(自举编译)
生态小而精(专用 BEAM 库)巨大(npm 生态)
学习曲线中等(需要理解 Actor 思维)低(JS 演进而来)

Gleam 适合:后端服务、高并发系统、需要强一致性的业务逻辑。TypeScript 适合:快速迭代的前端、需要丰富生态的工具

8.2 Gleam vs Elixir

Gleam 和 Elixir 都运行在 BEAM 上,都可以使用 OTP 库。核心区别在于类型:

维度GleamElixir
类型系统静态强类型,编译期检查动态类型 + Dialyzer(可选)
编译期检查强制,穷举所有分支可选(dialyzer 是外部工具)
语法风格ML 系(let/case/fn)Ruby 衍生(def/case/fn)
ADT 支持原生需要 defstruct + 类型规范
JavaScript 编译原生支持需要额外工具(如 ElixirScript)
生态成熟度成长期成熟(10+ 年)

Gleam 适合:喜欢类型安全、来自静态类型语言背景的开发者。Elixir 适合:需要丰富框架(Phoenix)、现有 BEAM 团队、快速原型开发

8.3 Gleam vs Rust

两者都是现代静态类型语言,都追求内存安全,但方向截然不同:

维度GleamRust
内存安全方式GC(BEAM 的并发 GC)所有权系统(无 GC)
并发模型Actor 消息传递Send+Sync trait + async/await
运行时开销BEAM 运行时(有 GC 暂停,但很短)零运行时(无 GC)
编译速度快(Gleam 编译器用 Rust 写)慢(尤其是增量编译)
JavaScript 编译原生需要 wasm-pack + WASM

Gleam 适合:快速构建高并发服务、团队需要快速迭代。Rust 适合:对性能有极致要求、需要控制内存布局、需要 WASM 目标


九、踩坑清单:Gleam 生产环境十大经验

基于社区和项目实践经验,总结以下踩坑点:

1. Opaque Type 的边界是编译期,不是运行时

// 正确使用 Opaque Type
pub type UserId

pub fn create_user_id(raw: String) -> UserId {
  UserId(value: raw)  // 内部构造函数是公开的,可以构造
}

// 在模块外部,你无法访问 UserId 的内部 String
// 但这不意味着它是真正封装的——因为 new_user_id() 是公开函数

2. 模式匹配必须穷举,漏掉分支会编译失败

// 这段代码无法编译——漏掉了 Cancelled 分支
pub fn describe_status(status: OrderStatus) -> String {
  case status {
    Created -> "已创建"
    PaymentPending -> "等待支付"
    Paid -> "已支付"
    Processing -> "处理中"
    Shipped -> "已发货"
    Delivered -> "已送达"
    // 漏掉了:Cancelled(reason) -> ...
  }
}

3. 列表推导中不能直接使用多步逻辑

Gleam 的列表推导(List.map / for 推导)是函数式的,不支持命令式的多步累积——需要用 foldreduce

4. process.receive(after: N) 的超时精度

after 参数的单位是毫秒,但 BEAM 的调度精度取决于 Erlang VM 的 tick rate。生产环境不应依赖亚毫秒级定时精度。

5. 循环需要尾递归优化

Gleam 不支持命令式 for 循环,所有循环都必须用递归实现。要避免栈溢出,必须使用尾递归:

// 正确:尾递归,编译器优化为循环
pub fn sum_list(list: List(Int), acc: Int) -> Int {
  case list {
    [] -> acc
    [head, ..tail] -> sum_list(tail, acc + head)
  }
}

// 错误:非尾递归,栈会无限增长
pub fn sum_list_slow(list: List(Int)) -> Int {
  case list {
    [] -> 0
    [head, ..tail] -> head + sum_list_slow(tail)
  }
}

6. JSON 序列化需要显式导出类型

Gleam 的自定义类型默认不导出,需要在 gleam.toml 或函数签名中明确使用类型名才能序列化。

7. OTP Actor 的消息类型必须完全匹配

// 如果 Actor 处理 ProcessorMessage,发送其他消息类型会编译失败
// 这保证了类型安全,但也意味着你需要仔细设计消息类型层次

8. JavaScript 编译目标不支持某些 Erlang 特定 API

loggercrypto 等 Erlang 特定模块,在 JS 编译目标下不可用。需要使用跨平台抽象或条件编译。

9. 调试信息不包含运行时栈跟踪

BEAM 的 Actor 崩溃产生的堆栈信息与 OS 进程不同。你需要在 Actor 的 init 函数中添加监控和日志,而不是依赖传统 try/catch 的堆栈。

10. 版本兼容性:依赖管理需要锁定版本

Gleam 的语义化版本支持 ~> 约束,但生产环境建议使用精确版本号(==1.18.0)避免自动升级引入破坏性变更。


十、总结:Gleam 的工程哲学

Gleam 的核心工程哲学可以浓缩为一句话:用编译期的约束,换取运行时的自由。

当你用 ADT 定义业务状态时,你是在告诉编译器:"这五种状态是合法的,其他状态不存在。" 编译器接受了这个约束,然后确保没有人能绕过它。

当你用 Actor 模型处理并发时,你是在约束自己:"进程间只用消息通信,不共享状态。" BEAM 接受了这个约束,然后提供了百万级并发、热更新、超长正常运行时间作为回报。

当你用 Result<T, E> 处理错误时,你是在约束自己:"每个错误路径都必须被处理。" 编译器接受了这个约束,然后让你在代码审查时不需要问"这个函数会不会抛异常"。

Gleam 不适合所有人——它要求你接受约束,要求你用函数式思维,要求你理解 Actor 模型。但如果你在构建需要高可靠性、高并发、需要长期维护的系统,这些约束恰恰是你需要的护栏。

2026 年,Gleam 生态仍在成长。库的数量不如 TypeScript 或 Elixir,但每一个库的代码质量普遍较高。1.18.0 的语言服务器改进表明开发团队正在解决"开发者体验最后一公里"的问题。如果你想尝试一门既有强类型保证、又有成熟运行时、语法还足够友好的语言,Gleam 值得放进你的工具箱。

下一步行动:

# 安装 Gleam
curl -fsSL https://gleam.run/install.sh | sh

# 创建第一个项目
gleam new my_first_gleam_project
cd my_first_gleam_project
gleam run

# 添加依赖(查看生态)
# 编辑 gleam.toml:
# dependencies = [
#   { name = "gleam_otp", version = "~> 0.1" }
# ]

附录:Gleam 核心语法速查

// 变量
let x: Int = 42
let name = "Alice"  // 自动推断 String

// 函数
pub fn add(a: Int, b: Int) -> Int {
  a + b
}

// 高阶函数
pub fn apply(list: List(a), f: fn(a) -> b) -> List(b) {
  list.map(list, f)
}

// 类型别名
pub type UserId = String

// ADT
pub type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
  Square(side: Float)
}

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

// Result 处理
pub fn safe_divide(a: Int, b: Int) -> Result(Float, String) {
  case b {
    0 -> Error("Division by zero")
    _ -> Ok(int.to_float(a) /. int.to_float(b))
  }
}

// 列表操作
pub fn process_all(items: List(Int)) -> List(Int) {
  items
  |> list.filter(_, fn(x) { x > 0 })
  |> list.map(_, fn(x) { x * 2 })
  |> list.sort(_, int.compare)
}

// Actor 消息
pub type Msg {
  Increment
  Decrement
}

pub fn handle(state: Int, msg: Msg) -> Int {
  case msg {
    Increment -> state + 1
    Decrement -> state - 1
  }
}

推荐文章

微信小程序热更新
2024-11-18 15:08:49 +0800 CST
用 Rust 玩转 Google Sheets API
2024-11-19 02:36:20 +0800 CST
程序员茄子在线接单