使用 samber/mo 为 Golang 提供单子类型——Option、Result、Either、Future、IO、Task 和 State 类型,用于类型安全的可空值、错误处理和函数式组合,并包含管道子包。适用于使用或采用 samber/mo 时、代码库导入 `github.com/samber/mo` 时,或考虑将函数式编程模式作为 Golang 安全设计时。
角色: 你是一位将函数式编程安全性引入 Go 的 Go 工程师。你使用单子(monad)使不可能的状态变得不可表示——nil 检查变成类型约束,错误处理变成可组合的管道。
思考模式: 在设计多步骤的 Option/Result/Either 管道时使用 ultrathink。错误的类型选择会导致不必要的包装/解包,从而违背单子的目的。
samber/mo — Go 的单子与函数式抽象
Go 1.18+ 库,提供类型安全的单子类型,零依赖。灵感来自 Scala、Rust 和 fp-ts。
官方资源:
本技能并非详尽无遗。请参考库文档和代码示例获取更多信息。Context7 可作为发现平台提供帮助。对于 Go 包文档、版本、符号和已知漏洞,→ 参见 samber/cc-skills-golang@golang-pkg-go-dev 技能。
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] 带值
empty := mo.None[string]() // Option[string] 不带值
fromPtr := mo.PointerToOption(ptr) // nil 指针 -> None
// 安全提取
name.OrElse("Anonymous") // "Alice"
empty.OrElse("Anonymous") // "Anonymous"
// 存在时转换,不存在时跳过
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 的错误模式进行了特化。
// 包装 Go 的 (value, error) 模式
result := mo.TupleToResult(os.ReadFile("config.yaml"))
// 同类型转换——错误自动短路
upper := mo.Ok("hello").Map(func(s string) (string, error) {
return strings.ToUpper(s), nil
})
// Ok("HELLO")
// 带默认值提取
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"
// 类型转换管道:[]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
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))
}
// 模式匹配
result := fetchUser("user-123")
result.Match(
func(cached CachedUser) mo.Either[CachedUser, FreshUser] { /* 使用缓存 */ },
func(fresh FreshUser) mo.Either[CachedUser, FreshUser] { /* 使用新鲜数据 */ },
)
何时使用 Either 与 Result: 当一条路径是错误时使用 Result[T]。当两条路径都是有效的替代方案(缓存 vs 新鲜、左 vs 右、策略 A vs B)时使用 Either[L, R]。
Either3[T1, T2, T3]、Either4 和 Either5 将其扩展到 3-5 种类型变体。
完整 API 参考,请参见 Either 参考。
Do 表示法——具有单子安全性的命令式风格
mo.Do 将命令式代码包装在 Result 中,捕获来自 MustGet() 调用的 panic:
result := mo.Do(func() int {
// MustGet 在 None/Err 时 panic——Do 将其捕获为 Result 错误
a := mo.Some(21).MustGet()
b := mo.Ok(2).MustGet()
return a * b // 42
})
// result 是 Ok(42)
result := mo.Do(func() int {
val := mo.None[int]().MustGet() // panic
return val
})
// result 是 Err("no such element")
Do 表示法桥接了命令式 Go 风格与单子安全性——编写直线代码,自动传播错误。
管道子包 vs 直接链式调用
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 类型变化:使用子包 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]()
}),
)
经验法则: 同类型转换使用直接方法。当步骤间类型变化时使用子包函数 + pipes。
详细的管道 API 参考,请参见 管道参考。
常见模式
使用 Option 的 JSON API 响应
type UserResponse struct {
Name string `json:"name"`
Nickname mo.Option[string] `json:"nickname"` // 优雅地省略 null
Bio mo.Option[string] `json:"bio"`
}
数据库可空列
type User struct {
ID int
Email string
Phone mo.Option[string] // 实现 sql.Scanner + driver.Valuer
}
err := row.Scan(&u.ID, &u.Email, &u.Phone)
包装现有 Go API
// 将 map 查找转换为 Option
func MapGet[K comparable, V any](m map[K]V, key K) mo.Option[V] {
return mo.TupleToOption(m[key]) // m[key] 返回 (V, bool)
}
使用 Fold 统一提取
mo.Fold 通过 Foldable 接口统一适用于 Option、Result 和 Either:
str := mo.Fold[error, int, string](
mo.Ok(42), // 适用于 Option、Result 或 Either
func(v int) string { return fmt.Sprintf("got %d", v) },
func(err error) string { return "failed" },
)
// "got 42"
最佳实践
- 优先使用
OrElse而非MustGet——MustGet在值缺失/错误时 panic;仅在mo.Do块内(panic 会被捕获)或确定值存在时使用 - 在 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)从左到右阅读;当单子链式调用更清晰时避免嵌套的 if/else 模式 - 多步骤类型转换使用子包 pipes——当 3 步以上且每一步都改变类型时,
option.Pipe3(...)比嵌套函数调用更可读
有关高级类型(Future、IO、Task、State),请参见 高级类型参考。
如果你在 samber/mo 中遇到错误或意外行为,请在 https://github.com/samber/mo/issues 提交 issue。
交叉引用
- -> 参见
samber/cc-skills-golang@golang-samber-lo技能,了解可与 mo 类型组合的函数式集合转换(Map、Filter、Reduce on slices) - -> 参见
samber/cc-skills-golang@golang-error-handling技能,了解惯用的 Go 错误处理模式 - -> 参见
samber/cc-skills-golang@golang-safety技能,了解 nil 安全和防御性 Go 编码 - -> 参见
samber/cc-skills-golang@golang-database技能,了解数据库访问模式 - -> 参见
samber/cc-skills-golang@golang-design-patterns技能,了解函数式选项和其他 Go 模式






