golang-samber-mo by samber/cc-skills-golang
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-mo角色: 你是一位将函数式编程安全性引入 Go 语言的 Go 工程师。你使用单子(monad)来使不可能的状态无法表示——空值检查变成了类型约束,错误处理变成了可组合的管道。
思考模式: 在设计多步骤的 Option/Result/Either 管道时,使用 ultrathink 模式。选择错误的类型会导致不必要的包装/解包,从而违背了使用单子的初衷。
Go 1.18+ 库,提供类型安全的单子类型,零依赖。灵感来源于 Scala、Rust 和 fp-ts。
官方资源:
此技能介绍并非详尽无遗。更多信息请参考库文档和代码示例。Context7 可以作为一个发现平台提供帮助。
go get github.com/samber/mo
关于函数式编程概念以及为什么单子在 Go 中很有价值的介绍,请参阅 单子指南。
| 类型 | 用途 | 可以理解为... |
|---|---|---|
Option[T] |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 可能缺失的值 |
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 单子 |
表示一个值要么存在(Some),要么不存在(None)。在类型层面消除了空指针风险。
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 参考。
表示成功(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 参考。
表示一个值属于两种可能类型之一。与 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.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 参考。
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 风格和单子安全性之间架起了桥梁 —— 编写直线式代码,获得自动的错误传播。
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]
管道函数(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]()
}),
)
经验法则: 对于同类型转换,使用直接方法。当步骤间类型改变时,使用子包函数 + 管道。
详细的管道 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)
// 将 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)
}
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 会被捕获)或你确定值存在时使用它TupleToResult —— 在边界处将 Go 的 (T, error) 转换为 Result[T],然后在你的领域逻辑内部使用 Map/FlatMap 进行链式调用Result[T] 处理错误,Either[L, R] 处理替代方案 —— Result 专门用于成功/失败;Either 用于两种有效类型Option[string] 区分“缺失”和“空字符串”;当空字符串是有效值时,使用普通的 stringresult.Map(...).FlatMap(...).OrElse(default) 从左到右阅读;当单子链式调用更清晰时,避免使用嵌套的 if/else 模式option.Pipe3(...) 比嵌套函数调用更具可读性关于高级类型(Future、IO、Task、State),请参阅 高级类型参考。
如果你在 samber/mo 中遇到 bug 或意外行为,请在 https://github.com/samber/mo/issues 提交 issue。
samber/cc-skills-golang@golang-samber-lo 技能,了解可与 mo 类型组合的函数式集合转换(切片上的 Map、Filter、Reduce)samber/cc-skills-golang@golang-error-handling 技能,了解符合 Go 语言习惯的错误处理模式samber/cc-skills-golang@golang-safety 技能,了解空值安全性和防御性 Go 编码samber/cc-skills-golang@golang-database 技能,了解数据库访问模式samber/cc-skills-golang@golang-design-patterns 技能,了解函数式选项和其他 Go 模式每周安装次数
96
代码库
GitHub 星标数
276
首次出现
4 天前
安全审计
安装于
opencode91
gemini-cli88
codex88
cursor88
kimi-cli87
amp87
Persona: You are a Go engineer bringing functional programming safety to Go. You use monads to make impossible states unrepresentable — nil checks become type constraints, error handling becomes composable pipelines.
Thinking mode: Use ultrathink when designing multi-step Option/Result/Either pipelines. Wrong type choice creates unnecessary wrapping/unwrapping that defeats the purpose of monads.
Go 1.18+ library providing type-safe monadic types with zero dependencies. Inspired by Scala, Rust, and fp-ts.
Official Resources:
This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.
go get github.com/samber/mo
For an introduction to functional programming concepts and why monads are valuable in Go, see Monads Guide.
| Type | Purpose | Think of it as... |
|---|---|---|
Option[T] | Value that may be absent | Rust's Option, Java's Optional |
Result[T] | Operation that may fail | Rust's Result<T, E>, replaces (T, error) |
Either[L, R] | Value of one of two types | Scala's Either, TypeScript discriminated union |
EitherX[L, R] |
Represents a value that is either present (Some) or absent (None). Eliminates nil pointer risks at the type level.
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
})
Key methods: Some, None, Get, MustGet, OrElse, OrEmpty, Map, FlatMap, Match, ForEach, ToPointer, IsPresent, .
Option implements json.Marshaler/Unmarshaler, sql.Scanner, driver.Valuer — use it directly in JSON structs and database models.
For full API reference, see Option Reference.
Represents success (Ok) or failure (Err). Equivalent to Either[error, T] but specialized for Go's error pattern.
// 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 limitation: Direct methods (.Map, .FlatMap) cannot change the type parameter — Result[T].Map returns Result[T], not Result[U]. Go methods cannot introduce new type parameters. For type-changing transforms (e.g. Result[[]byte] to Result[Config]), use sub-package functions or 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) }),
)
Key methods: Ok, Err, Errf, TupleToResult, Try, Get, MustGet, OrElse, Map, FlatMap, MapErr, Match, , , , .
For full API reference, see Result Reference.
Represents a value that is one of two possible types. Unlike Result, neither side implies success or failure — both are valid alternatives.
// 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.Match(
func(cached CachedUser) mo.Either[CachedUser, FreshUser] { /* use cached */ },
func(fresh FreshUser) mo.Either[CachedUser, FreshUser] { /* use fresh */ },
)
When to use Either vs Result: Use Result[T] when one path is an error. Use Either[L, R] when both paths are valid alternatives (cached vs fresh, left vs right, strategy A vs B).
Either3[T1, T2, T3], Either4, and Either5 extend this to 3-5 type variants.
For full API reference, see Either Reference.
mo.Do wraps imperative code in a Result, catching panics from MustGet() calls:
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 notation bridges imperative Go style with monadic safety — write straight-line code, get automatic error propagation.
samber/mo provides two ways to compose operations:
Direct methods (.Map, .FlatMap) — work when the output type equals the input type:
opt := mo.Some(42)
doubled := opt.Map(func(v int) (int, bool) {
return v * 2, true
}) // Option[int]
Sub-package functions (option.Map, result.Map) — required when the output type differs from input:
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 functions (option.Pipe3, result.Pipe3) — chain multiple type-changing transformations readably:
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]()
}),
)
Rule of thumb: Use direct methods for same-type transforms. Use sub-package functions + pipes when types change across steps.
For detailed pipeline API reference, see Pipelines Reference.
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)
// 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)
}
mo.Fold works uniformly across Option, Result, and Either via the Foldable interface:
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 over MustGet — MustGet panics on absent/error values; use it only inside mo.Do blocks where panics are caught, or when you are certain the value existsTupleToResult at API boundaries — convert Go's (T, error) to Result[T] at the boundary, then chain with Map/FlatMap inside your domain logicResult[T] for errors, for alternatives — Result is specialized for success/failure; Either is for two valid typesFor advanced types (Future, IO, Task, State), see Advanced Types Reference.
If you encounter a bug or unexpected behavior in samber/mo, open an issue at https://github.com/samber/mo/issues.
samber/cc-skills-golang@golang-samber-lo skill for functional collection transforms (Map, Filter, Reduce on slices) that compose with mo typessamber/cc-skills-golang@golang-error-handling skill for idiomatic Go error handling patternssamber/cc-skills-golang@golang-safety skill for nil-safety and defensive Go codingsamber/cc-skills-golang@golang-database skill for database access patternssamber/cc-skills-golang@golang-design-patterns skill for functional options and other Go patternsWeekly Installs
96
Repository
GitHub Stars
276
First Seen
4 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode91
gemini-cli88
codex88
cursor88
kimi-cli87
amp87
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
120,000 周安装
| Value of one of X types |
Scala's Either, TypeScript discriminated union |
Future[T] | Async value not yet available | JavaScript Promise |
IO[T] | Lazy synchronous side effect | Haskell's IO |
Task[T] | Lazy async computation | fp-ts Task |
State[S, A] | Stateful computation | Haskell's State monad |
IsAbsentForEachToEitherIsOkIsErrorEither[L, R]Option[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid valueresult.Map(...).FlatMap(...).OrElse(default) reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaneroption.Pipe3(...) is more readable than nested function calls