mcpField
G
golang-samber-mo

golang-samber-mo

github.com/samber开发3.7k38.6k 次安装5 天前更新
代码仓库

关于 golang-samber-mo

面向 Go 工程师的 samber/mo 编程技能,介绍如何用类型安全的 Option、Result、Either 等抽象处理可空值、错误和异步计算,并组合多步转换。

主要功能

  1. 01Option、Result 与 Either 类型
  2. 02Future、IO、Task 与 State 类型
  3. 03使用管道组合类型转换
  4. 04用 mo.Do 传播错误
  5. 05JSON 与数据库 nullable 值支持

适用场景

  1. 01为可选字段建模并避免 nil 风险
  2. 02将 Go 错误处理转为可组合流程
  3. 03构建跨类型的多步转换
  4. 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.json
references/
advanced-types.md
either.md
monads-guide.md
option.md
pipelines.md
result.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"

最佳实践

  1. 优先使用 OrElse,而非 MustGet——MustGet 在值缺失或出错时会触发 panic;仅在能捕获 panic 的 mo.Do 块中,或确定值存在时使用
  2. 在 API 边界使用 TupleToResult——在边界处将 Go 的 (T, error) 转换为 Result[T],然后在领域逻辑中使用 Map/FlatMap 链式调用
  3. 错误使用 Result[T],选项使用 Either[L, R]——Result 专用于表示成功或失败;Either 用于表示两种有效类型
  4. 可空字段使用 Option,而非零值——Option[string] 可以区分“缺失”和“空字符串”;当空字符串是有效值时,使用普通 string
  5. 链式调用,不要嵌套——result.Map(...).FlatMap(...).OrElse(default) 按从左到右的顺序清晰易读;当 Monad 链式调用更简洁时,避免嵌套的 if/else 模式
  6. 多步类型转换使用子包 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-lo skill
  • → 有关符合 Go 惯用风格的错误处理模式,请参阅 samber/cc-skills-golang@golang-error-handling skill
  • → 有关 nil 安全性和防御式 Go 编码,请参阅 samber/cc-skills-golang@golang-safety skill
  • → 有关数据库访问模式,请参阅 samber/cc-skills-golang@golang-database skill
  • → 有关函数式选项和其他 Go 模式,请参阅 samber/cc-skills-golang@golang-design-patterns skill

评论

支持 Markdown