关于 golang-samber-mo
面向 Go 工程师的 samber/mo 编程技能,介绍如何用类型安全的 Option、Result、Either 等抽象处理可空值、错误和异步计算,并组合多步转换。
主要功能
- 01Option、Result 与 Either 类型
- 02Future、IO、Task 与 State 类型
- 03使用管道组合类型转换
- 04用 mo.Do 传播错误
- 05JSON 与数据库 nullable 值支持
适用场景
- 01为可选字段建模并避免 nil 风险
- 02将 Go 错误处理转为可组合流程
- 03构建跨类型的多步转换
- 04在 JSON 结构体或数据库模型中表示可空值
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-samber-mo安装后在 Claude Code 中输入/golang-samber-mo确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/advanced-types.mdeither.mdmonads-guide.mdoption.mdpipelines.mdresult.md详细介绍
角色: 你是一名 Go 工程师,致力于将函数式编程的安全性引入 Go。你使用单子让不可能的状态无法表示——将 nil 检查转化为类型约束,让错误处理成为可组合的流水线。
思考模式: 设计多步 Option/Result/Either 流水线时,请尽可能周密地推理——类型选择错误会导致不必要的包装与拆包,适得其反。在 Claude Code 中,使用 ultrathink 显式触发扩展思考。
samber/mo — 面向 Go 的单子与函数式抽象
适用于 Go 1.18+ 的库,提供零依赖的类型安全单子类型。灵感来自 Scala、Rust 和 fp-ts。
官方资源:
本技能并未涵盖所有内容——更多信息请参阅库文档和代码示例:
- 如需查看 Go 包文档、符号、版本、导入方和已知漏洞,→ 请参阅
samber/cc-skills-golang@golang-pkg-go-dev技能(godig);查询 Go 包信息时优先于 Context7。 - 如需在自己的代码中导航此库的用法(定义、调用位置、诊断信息),→ 请参阅
samber/cc-skills-golang@golang-gopls技能(gopls)。 - 对于未被 pkg.go.dev 收录的文档,Context7 仍可作为备用方案。
go get github.com/samber/mo
如需了解函数式编程概念的入门介绍,以及单子在 Go 中的价值,请参阅 单子指南。
核心类型一览
| 类型 | 用途 | 可将其视为…… |
|---|---|---|
Option[T] | 可能缺失的值 | Rust 的 Option、Java 的 Optional |
Result[T] | 可能失败的操作 | Rust 的 Result<T, E>,用于替代 (T, error) |
Either[L, R] | 两种类型之一的值 | Scala 的 Either、TypeScript 的可辨识联合 |
EitherX[L, R] | X 种类型之一的值 | Scala 的 Either、TypeScript 的可辨识联合 |
Future[T] | 尚不可用的异步值 | JavaScript 的 Promise |
IO[T] | 延迟执行的同步副作用 | Haskell 的 IO |
Task[T] | 延迟执行的异步计算 | fp-ts 的 Task |
State[S, A] | 有状态计算 | Haskell 的 State 单子 |
Option[T] — 无需 nil 的可空值
表示一个值要么存在(Some),要么缺失(None)。在类型层面消除 nil 指针风险。
import "github.com/samber/mo"
name := mo.Some("Alice") // Option[string] with value
empty := mo.None[string]() // Option[string] without value
fromPtr := mo.PointerToOption(ptr) // nil pointer -> None
// Safe extraction
name.OrElse("Anonymous") // "Alice"
empty.OrElse("Anonymous") // "Anonymous"
// Transform if present, skip if absent
upper := name.Map(func(s string) (string, bool) {
return strings.ToUpper(s), true
})
主要方法: Some、None、Get、MustGet、OrElse、OrEmpty、Map、FlatMap、Match、ForEach、ToPointer、IsPresent、IsAbsent。
Option 实现了 json.Marshaler/Unmarshaler、sql.Scanner、driver.Valuer——可直接用于 JSON 结构体和数据库模型。
完整 API 参考请参阅 Option 参考。
Result[T] — 将错误处理作为值
表示成功(Ok)或失败(Err)。它等价于 Either[error, T],但专为 Go 的错误模式而设计。
// Wrap Go's (value, error) pattern
result := mo.TupleToResult(os.ReadFile("config.yaml"))
// Same-type transform — errors short-circuit automatically
upper := mo.Ok("hello").Map(func(s string) (string, error) {
return strings.ToUpper(s), nil
})
// Ok("HELLO")
// Extract with fallback
val := upper.OrElse("default")
Go 的限制: 直接方法(.Map、.FlatMap)无法更改类型参数——Result[T].Map 返回的是 Result[T],而不是 Result[U]。Go 方法无法引入新的类型参数。对于会改变类型的转换(例如从 Result[[]byte] 转为 Result[Config]),请使用子包函数或 mo.Do:
import "github.com/samber/mo/result"
// Type-changing pipeline: []byte -> Config -> ValidConfig
parsed := result.Pipe2(
mo.TupleToResult(os.ReadFile("config.yaml")),
result.Map(func(data []byte) Config { return parseConfig(data) }),
result.FlatMap(func(cfg Config) mo.Result[ValidConfig] { return validate(cfg) }),
)
主要方法: Ok、Err、Errf、TupleToResult、Try、Get、MustGet、OrElse、Map、FlatMap、MapErr、Match、ForEach、ToEither、IsOk、IsError。
完整 API 参考请参阅 Result 参考。
Either[L, R] — 两种类型的判别联合
表示一个值属于两种可能类型之一。与 Result 不同,任一侧都不代表成功或失败,两者都是有效选项。
// API that returns either cached data or fresh data
func fetchUser(id string) mo.Either[CachedUser, FreshUser] {
if cached, ok := cache.Get(id); ok {
return mo.Left[CachedUser, FreshUser](cached)
}
return mo.Right[CachedUser, FreshUser](db.Fetch(id))
}
// Pattern match
result := fetchUser("user-123")
result.Match(
func(cached CachedUser) mo.Either[CachedUser, FreshUser] { /* use cached */ },
func(fresh FreshUser) mo.Either[CachedUser, FreshUser] { /* use fresh */ },
)
何时使用 Either 而非 Result: 当其中一条路径表示错误时,使用 Result[T]。当两条路径都是有效选项时(缓存与新数据、左侧与右侧、策略 A 与策略 B),使用 Either[L, R]。
Either3[T1, T2, T3]、Either4 和 Either5 将其扩展为 3~5 种类型变体。
完整的 API 参考请参阅 Either 参考。
Do 表示法 — 兼具 Monad 安全性的命令式风格
mo.Do 会将命令式代码封装在 Result 中,并捕获 MustGet() 调用引发的 panic:
result := mo.Do(func() int {
// MustGet panics on None/Err — Do catches it as Result error
a := mo.Some(21).MustGet()
b := mo.Ok(2).MustGet()
return a * b // 42
})
// result is Ok(42)
result := mo.Do(func() int {
val := mo.None[int]().MustGet() // panics
return val
})
// result is Err("no such element")
Do 表示法将命令式 Go 风格与 Monad 安全性结合起来——编写顺序执行的代码,同时自动传播错误。
Pipeline 子包与直接链式调用
samber/mo 提供两种组合操作的方式:
直接方法(.Map、.FlatMap)——适用于输出类型与输入类型相同的情况:
opt := mo.Some(42)
doubled := opt.Map(func(v int) (int, bool) {
return v * 2, true
}) // Option[int]
子包函数(option.Map、result.Map)——当输出类型与输入类型不同时必须使用:
import "github.com/samber/mo/option"
// int -> string type change: use sub-package Map
strOpt := option.Map(func(v int) string {
return fmt.Sprintf("value: %d", v)
})(mo.Some(42)) // Option[string]
Pipe 函数(option.Pipe3、result.Pipe3)——以清晰易读的方式串联多个会改变类型的转换:
import "github.com/samber/mo/option"
result := option.Pipe3(
mo.Some(42),
option.Map(func(v int) string { return strconv.Itoa(v) }),
option.Map(func(s string) []byte { return []byte(s) }),
option.FlatMap(func(b []byte) mo.Option[string] {
if len(b) > 0 { return mo.Some(string(b)) }
return mo.None[string]()
}),
)
经验法则: 对于类型不变的转换,使用直接方法。对于跨步骤类型会变化的情况,使用子包函数和 pipe。
详细的 pipeline API 参考请参阅 Pipelines 参考。
常见模式
使用 Option 处理 JSON API 响应
type UserResponse struct {
Name string `json:"name"`
Nickname mo.Option[string] `json:"nickname"` // omits null gracefully
Bio mo.Option[string] `json:"bio"`
}
数据库可空列
type User struct {
ID int
Email string
Phone mo.Option[string] // implements sql.Scanner + driver.Valuer
}
err := row.Scan(&u.ID, &u.Email, &u.Phone)
封装现有 Go API
// Convert map lookup to Option
func MapGet[K comparable, V any](m map[K]V, key K) mo.Option[V] {
return mo.TupleToOption(m[key]) // m[key] returns (V, bool)
}
使用 Fold 统一提取
mo.Fold 通过 Foldable 接口统一适用于 Option、Result 和 Either:
str := mo.Fold[error, int, string](
mo.Ok(42), // works with Option, Result, or Either
func(v int) string { return fmt.Sprintf("got %d", v) },
func(err error) string { return "failed" },
)
// "got 42"
最佳实践
- 优先使用
OrElse,而非MustGet——MustGet在值缺失或出错时会触发 panic;仅在能捕获 panic 的mo.Do块中,或确定值存在时使用 - 在 API 边界使用
TupleToResult——在边界处将 Go 的(T, error)转换为Result[T],然后在领域逻辑中使用Map/FlatMap链式调用 - 错误使用
Result[T],选项使用Either[L, R]——Result 专用于表示成功或失败;Either 用于表示两种有效类型 - 可空字段使用 Option,而非零值——
Option[string]可以区分“缺失”和“空字符串”;当空字符串是有效值时,使用普通string - 链式调用,不要嵌套——
result.Map(...).FlatMap(...).OrElse(default)按从左到右的顺序清晰易读;当 Monad 链式调用更简洁时,避免嵌套的 if/else 模式 - 多步类型转换使用子包 pipe——当 3 个或更多步骤都会改变类型时,
option.Pipe3(...)比嵌套函数调用更易读
高级类型(Future、IO、Task、State)请参阅 高级类型参考。
如果在 samber/mo 中遇到 bug 或意外行为,请在 https://github.com/samber/mo/issues 提交 issue。
交叉引用
- → 有关可与 mo 类型组合的函数式集合转换(对切片执行 Map、Filter、Reduce),请参阅
samber/cc-skills-golang@golang-samber-loskill - → 有关符合 Go 惯用风格的错误处理模式,请参阅
samber/cc-skills-golang@golang-error-handlingskill - → 有关 nil 安全性和防御式 Go 编码,请参阅
samber/cc-skills-golang@golang-safetyskill - → 有关数据库访问模式,请参阅
samber/cc-skills-golang@golang-databaseskill - → 有关函数式选项和其他 Go 模式,请参阅
samber/cc-skills-golang@golang-design-patternsskill