Gleam 基础知识参考字典

要点

Gleam 基础层的名词按用途分成五组:数值与字符串、集合与缺省、类型与模式、进程与 OTP、工程与调试。每条给签名、一句话语义、最小示例和坑,示例放进 gleam new 项目即可运行。字典是查的不是读的——叙事式讲解在《Gleam 语法要点》,文末速查表是全量索引。截至本文(2025 年 9 月)稳定线为 Gleam 1.12.x,标准库 gleam_stdlib 处于 0.63 线(0.63.2),版本敏感条目单独标注。

数值与字符串

组内共性:Int 与 Float 是两种类型两套运算符;字符串是 UTF-8 文本,没有插值。

数值:两套运算符与解析

1 + 2        // => 3
1.5 +. 2.0   // => 3.5,浮点运算符带点:+. -. *. /.
int.to_float(5)     // => 5.0;float.truncate(5.9) 向零截断为 Int
int.parse("42")     // => Ok(42);"4x" 得 Error(Nil)
int.modulo(7, by: 2)// => Ok(1),除零得 Error(Nil) 而不是崩溃
int.compare(2, with: 3) // => Lt,order.Order 是排序函数的标准输入

整数与浮点互转必须显式调用函数;外部输入的文本转数值一律走 parse,失败是普通值。

⚠️ 坑:7 / 2 是整数除法得 3,想要小数先 to_float;parse 的错误类型是 Nil,只告诉你”不是数字”——嫌信息少就在调用侧包成自定义类型再外传。

字符串拼接、长度与字节

"gleam" <> " " <> "1.12"   // => "gleam 1.12",拼接用 <>
string.length("中文")      // => 2,按字素(grapheme)数
string.byte_size("中文")   // => 6,UTF-8 字节数
string.split_once("a=b", on: "=")   // => Ok(#("a", "b")),找不到得 Error(Nil)

Gleam 没有插值,拼装数值要先 to_string 再用 <>;截取一律用 string.slice(from:, at_index:, length:)。

⚠️ 坑:按字节切片会切碎中文(一个汉字 3 字节);<> 两侧必须都是 String,拼数值直接编译报错。

集合与缺省

组内共性:List 是单向链表,位置相关操作 O(n);Dict 无字面量;”可能没有”用 Option、”失败”用 Result,语义不混用。

list.map / filter / fold / sort

import gleam/list

[1, 2, 3] |> list.map(with: fn(x) { x * 2 })          // => [2, 4, 6]
[1, 2, 3, 4] |> list.filter(keeping: fn(x) { x % 2 == 0 })  // => [2, 4]
list.fold(over: [1, 2, 3], from: 0, with: fn(acc, x) { acc + x })  // => 6
list.sort([3, 1, 2], by: int.compare)                 // => [1, 2, 3]
list.filter_map(["1", "x", "3"], with: int.parse)     // => [1, 3],筛+转一步

map/filter/fold 覆盖九成日常需求;filter_map 把”转换可能失败”压成一步。

⚠️ 坑:链表没有随机访问,list.length(of:) 也要遍历——热路径里反复取长度、按下标取值先想想该不该用 Dict;sort 必须给比较函数。

dict.get / insert / from_list

import gleam/dict

let d =
  dict.new()
  |> dict.insert("a", 1)
  |> dict.insert("b", 2)

dict.get(from: d, get: "a")     // => Ok(1)
dict.get(from: d, get: "z")     // => Error(Nil)
dict.from_list([#("a", 1), #("b", 2)])   // 等价于上面的构建

构建习惯从空 dict 链式 insert,或从键值元组列表 from_list;键重复时后者胜出;dict.size 取条数是常数时间。

⚠️ 坑:get 的错误是 Nil,分不清”键不存在”和”值恰好错了”——需要区分时先 has_key,或把”值也包 Result”做进自己的值类型;to_list 的键值顺序不保证,别依赖顺序。

option 与 result:处理函数与互转

import gleam/option
import gleam/result

option.unwrap(Some(1), or: 0)     // => 1;None 则取默认值
option.from_result(Ok(1))         // => Some(1),丢弃错误原因换 None
option.to_result(None, "缺配置")   // => Error("缺配置"),补回原因

Ok(1) |> result.map(with: fn(x) { x + 1 })              // => Ok(2)
Error("x") |> result.map_error(with: fn(e) { "前缀:" <> e })  // => Error("前缀:x")
Ok(1) |> result.try(apply: fn(x) { Ok(x * 2) })         // => Ok(2)

Option 表达”可能没有值”,Result 表达”失败且带原因”,from_result/to_result 是两者的语义边界;map 处理成功值、map_error 统一错误类型、try 串接”又可能失败的一步”。

⚠️ 坑:unwrap 的默认值只在”确实无所谓”时用,该报警的事别静默;串两步以上改用 use x <- result.try(...) 拉直,错误类型不一致先 map_error 对齐。

类型与模式

组内共性:类型是”变体清单”,case 是”消费清单”,两边由编译器对账。

pub type 与 case 穷尽检查

pub type Cache {
  Hit(value: String)
  Miss
}

case Hit("user:1") {
  Hit(value) -> "命中 " <> value
  Miss -> "未命中"
  // 注释掉任何一行都编译不过;加新变体则所有 case 都要补
}

漏变体、写不存在的变体、拼错字段名,全部编译期报错;错误类型自定义后,调用方被迫处理每一种失败。

⚠️ 坑:catch-all _ -> 一旦写上,”加变体要补 case”的保护立即失效——以后新增 Forbidden,编译器安静,兜底分支悄悄接住。列全变体,或让兜底显式返回”未知错误”再上抛。

record update:.. 语法

pub type User {
  User(name: String, age: Int)
}

let u = User("alice", 30)
User(..u, age: 31)        // => User("alice", 31),新值,u 不变
User(age: 31)             // 编译错误:name 字段没给——没有默认值机制

.. 复制已有记录并覆盖指定字段,字段多时比全字段构造短。

⚠️ 坑:只能覆盖已有字段,键集合在 type 定义时就锁死;漏写 ..u 就是全新构造,编译器会指出缺哪些字段。

let assert:显式的旁路

let assert Ok(n) = int.parse("42")    // 赌定成功,失败 panic 并指出位置
let assert [head, ..rest] = [1, 2, 3] // 解构非空列表,空列表 panic

编译器无法证明、你却确定不会失败时,let assert 把”赌注”写进代码,崩了有精确行号。

⚠️ 坑:外部输入(请求体、文件、网络)永远不”确定成功”,对它们用 let assert 等于把类型系统的收益丢回去——panic 让 BEAM 进程退出,代码层的正路是 Result,进程级兜底是 OTP 的事。

进程与 OTP

组内共性:进程间不共享内存,通信靠 Subject;条目来自 gleam_erlang 1.3 与 gleam_otp 1.1(1.0 于 2025 年 6 月 GA,与 0.x 教程 API 完全不同)。

process.Subject / send / call

import gleam/erlang/process

let subject = process.new_subject()          // 当前进程拥有的一条收件通道
process.send(subject, "hello")               // 投递,立即返回
process.receive(from: subject, within: 1000) // => Ok("hello"),1 秒等不到得 Error(Nil)

// call 发请求等回复:消息类型里自带回复通道(Request(reply: Subject(String)))
process.call(subject, waiting: 5000, sending: fn(reply) { Request(reply) })

Subject 是 gleam_erlang 1.x 的进程通信入口,代替 0.x 的裸 Pid 消息;send 是单向通知,call 是请求-响应。

⚠️ 坑:call 等不到回复会直接 panic(不是返回 Result)——超时参数要按最坏情况给,或者接受”超时即异常、由上层监督兜底”的语义;receive 的超时是 Error(Nil),别忘了处理。

actor:gleam_otp 的有状态进程

import gleam/erlang/process
import gleam/otp/actor.{type Next, type Started}

pub type Msg {
  Inc
  GetCount(reply: process.Subject(Int))
}

pub fn start() {
  actor.new(0)
  |> actor.on_message(handle)
  |> actor.start    // => Result(Started(Subject(Msg)), StartError)
}

fn handle(count: Int, msg: Msg) -> Next(Int, Msg) {
  case msg {
    Inc -> actor.continue(count + 1)
    GetCount(reply) -> {
      process.send(reply, count)
      actor.continue(count)
    }
  }
}

actor 把”状态 + 消息处理函数”装进一个进程,start 返回的 Started 里 data 字段是对外收件 Subject;状态只在 actor 进程内变化,天然串行,没有锁。进程级重启兜底由同包的 gleam/otp/static_supervisor 提供(new 给 OneForOne 策略、add 挂子进程、restart_tolerance 控制重启强度)。

⚠️ 坑:2025 年 6 月 GA 的 gleam_otp 1.0 重写了整套 API(Builder 风格、Next/continue、Named),旧教程的参数列表与返回值都对不上号,以 1.x 文档为准;处理函数对每条消息必须返回 continue 或 stop,漏分支编译器会指出。

工程与调试

组内共性:Gleam 的调试传统是”编译器 + 测试 + echo”,没有交互式 REPL;工程面从 gleam.toml 与 CLI 子命令开始。

gleam CLI 与 gleam.toml

命令 一句话用途 有什么坑
gleam new 项目名 生成项目骨架 src/ 是库与应用代码,test/ 放 gleeunit
gleam run 编译并执行 main 加 -m 模块名 可跑指定模块
gleam add 包名 加依赖并写入 gleam.toml 包发布在 Hex,与 Elixir/Erlang 共用仓库
gleam dev 跑 dev/ 目录的开发代码(1.11+) dev 依赖不会进生产构建
gleam update 刷新依赖 1.12 起会提示新主版本可用
gleam export erlang-shipment 产出可直接运行的部署产物 目标机仍需 Erlang/OTP 运行时

gleam.toml 是项目清单:name/version、target(erlang 或 javascript)、[dependencies] 表里写版本区间(如 gleam_stdlib = ">= 0.44.0 and < 2.0.0");1.12 起依赖冲突报错明确指出是哪几个约束打架,而不是列一串包名。

⚠️ 坑:target 决定编译后端,Erlang 后端才能用进程/OTP 与 wisp/mist 这类 BEAM 库,切到 javascript 后端这些模块直接不可用——用 BEAM 生态就别动 target。

echo 与 assert

echo int.parse("42")          // 打印到 stderr 且返回原值,可插在管道任意一环
assert x > 10                 // 1.11+:为 False 时 panic,打印左右两侧实际值
echo 11 as "幸运数"            // 1.12+:echo 支持自定义标签

echo 是纯调试输出,assert 面向测试与不变量。⚠️ 坑:echo 本体是 1.9 引入、as 标签是 1.12、assert 是 1.11——1.8 以前的旧文章没有这两个关键字;echo 进 stderr,别拿它当正式日志。

gleeunit:测试与断言

import gleam/int
import gleeunit/should

pub fn parse_ok_test() {
  int.parse("42")
  |> should.equal(Ok(42))
}
// "x" 的失败用例同理,断言 should.equal(Error(Nil))

gleam test 跑 test 目录下所有 _test 结尾的函数,断言失败即 panic。

⚠️ 坑:用例忘了 _test 后缀就永远不执行——”测试全绿但没跑几条”多半是这个;一个 panic 会中断整个测试进程,用例想互相隔离就多开文件。

速查表

条目 一句话用途 有什么坑
+. 与 + / int.parse 两套运算符与文本转数值 混算编译错;除法有整数截断;parse 错误是 Nil
与 string.length 拼接与字素计数 无插值;按字节切会切碎中文
list.map/filter/fold/sort 列表四件套 链表无随机访问;sort 必须给比较函数
dict.get/insert 键值存取 get 错误是 Nil;to_list 无序
option/result 互转 缺省与失败两种语义 unwrap 默认值别静默该报警的事
pub type + case 变体清单对账 catch-all 关掉新增变体的保护
User(..u, age: 31) 记录复制更新 只能覆盖已有字段,漏 .. 编译错
use x <- f() 回调糖(result.try 常用) 语义由右侧函数决定,别嵌太深
let assert 显式赌成功 外部输入禁用,panic 杀进程
process.call / actor 请求-响应与有状态进程 call 超时直接 panic;gleam_otp 1.0 GA 起与旧教程 API 完全不同
echo / assert 调试输出与断言 1.9+/1.11+ 特性,echo 进 stderr
gleeunit 测试框架 只跑 _test 后缀,panic 即失败

下一步

基础层查完往上两层:这些类型怎么落成 HTTP 服务,在《用 Gleam 写一个小型 Web 服务:Mist 与 Wisp》里逐个用到;同门字典对照见《Elixir 基础知识参考字典》,动态与静态的设计取舍对着读更清楚。系列定位入口仍是开山篇。

版本核实(以官方文档为准):Gleam 稳定线 1.12.x(1.12.0 发布于 2025-08-05,echo as、依赖冲突 pubgrub 报错、新主版本提示为 1.12 特性);echo 为 1.9.0(2025-03-08)引入,assert 与 gleam dev 为 1.11.0(2025-06-02)引入;标准库签名以 gleam_stdlib 0.63.2 文档为准(dict.get/int.modulo/string.slice 等为跨小版本稳定 API);gleam_erlang 1.3.0 与 gleam_otp 1.1.0(1.0.0 于 2025-06-12 GA,actor Builder API/Subject 为 1.x 形态);gleam.toml 与 CLI 以 gleam.run 文档为准。本文未引用后续尚未发布的版本特性。

暂无评论

发送评论 编辑评论


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