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.
samber/mo — Monads and Functional Abstractions for Go
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.
CODEBLOCK0
For an introduction to functional programming concepts and why monads are valuable in Go, see Monads Guide.
Core Types at a Glance
| Type | Purpose | Think of it as... |
|---|
| INLINECODE1 | Value that may be absent | Rust's Option, Java's INLINECODE3 |
| INLINECODE4 |
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] | 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 |
Option[T] — Nullable Values Without nil
Represents a value that is either present (Some) or absent (None). Eliminates nil pointer risks at the type level.
CODEBLOCK1
Key methods: Some, None, Get, MustGet, OrElse, OrEmpty, Map, FlatMap, Match, ForEach, ToPointer, IsPresent, IsAbsent.
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.
Result[T] — Error Handling as Values
Represents success (Ok) or failure (Err). Equivalent to Either[error, T] but specialized for Go's error pattern.
CODEBLOCK2
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:
CODEBLOCK3
Key methods: Ok, Err, Errf, TupleToResult, Try, Get, MustGet, OrElse, Map, FlatMap, MapErr, Match, ForEach, ToEither, IsOk, IsError.
For full API reference, see Result Reference.
Either[L, R] — Discriminated Union of Two Types
Represents a value that is one of two possible types. Unlike Result, neither side implies success or failure — both are valid alternatives.
CODEBLOCK4
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).
INLINECODE66 , Either4, and Either5 extend this to 3-5 type variants.
For full API reference, see Either Reference.
Do Notation — Imperative Style with Monadic Safety
INLINECODE69 wraps imperative code in a Result, catching panics from MustGet() calls:
CODEBLOCK5
Do notation bridges imperative Go style with monadic safety — write straight-line code, get automatic error propagation.
Pipeline Sub-Packages vs Direct Chaining
samber/mo provides two ways to compose operations:
Direct methods (.Map, .FlatMap) — work when the output type equals the input type:
CODEBLOCK6
Sub-package functions (option.Map, result.Map) — required when the output type differs from input:
CODEBLOCK7
Pipe functions (option.Pipe3, result.Pipe3) — chain multiple type-changing transformations readably:
CODEBLOCK8
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.
Common Patterns
JSON API responses with Option
CODEBLOCK9
Database nullable columns
CODEBLOCK10
Wrapping existing Go APIs
CODEBLOCK11
Uniform extraction with Fold
INLINECODE78 works uniformly across Option, Result, and Either via the Foldable interface:
CODEBLOCK12
Best Practices
- 1. Prefer
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 exists - Use
TupleToResult at API boundaries — convert Go's (T, error) to Result[T] at the boundary, then chain with Map/FlatMap inside your domain logic - Use
Result[T] for errors, Either[L, R] for alternatives — Result is specialized for success/failure; Either is for two valid types - Option for nullable fields, not zero values —
Option[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid value - Chain, don't nest —
result.Map(...).FlatMap(...).OrElse(default) reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaner - Use sub-package pipes for multi-step type transformations — when 3+ steps each change the type,
option.Pipe3(...) is more readable than nested function calls
For 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 .
Cross-References
- - -> See
samber/cc-skills-golang@golang-samber-lo skill for functional collection transforms (Map, Filter, Reduce on slices) that compose with mo types - -> See
samber/cc-skills-golang@golang-error-handling skill for idiomatic Go error handling patterns - -> See
samber/cc-skills-golang@golang-safety skill for nil-safety and defensive Go coding - -> See
samber/cc-skills-golang@golang-database skill for database access patterns - -> See
samber/cc-skills-golang@golang-design-patterns skill for functional options and other Go patterns
角色定位: 你是一位将函数式编程安全性引入 Go 语言的 Go 工程师。你使用单子(monad)使不可能的状态变得不可表示——空指针检查变成类型约束,错误处理变成可组合的管道。
思维模式: 在设计多步骤的 Option/Result/Either 管道时,使用 ultrathink。错误的类型选择会导致不必要的包装/解包,从而违背了单子的目的。
samber/mo — Go 语言的单子与函数式抽象
Go 1.18+ 库,提供类型安全的单子类型,零依赖。灵感来自 Scala、Rust 和 fp-ts。
官方资源:
此技能并非详尽无遗。请参考库文档和代码示例以获取更多信息。Context7 可作为发现平台提供帮助。
bash
go get github.com/samber/mo
有关函数式编程概念介绍以及单子在 Go 语言中的价值,请参阅 单子指南。
核心类型一览
| 类型 | 用途 | 可以将其视为... |
|---|
| Option[T] | 可能缺失的值 | Rust 的 Option,Java 的 Optional |
| Result[T] |
可能失败的操作 | Rust 的 Result
,替代 (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)的值。在类型层面消除了空指针风险。
go
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
// 包装 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:
go
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 不同,任何一方都不暗示成功或失败——两者都是有效的备选方案。
go
// 返回缓存数据或新数据的 API
func fetchUser(id string) mo.Either[CachedUser, FreshUser] {
if cached, ok := cache.Get(id); ok {
return mo.LeftCachedUser, FreshUser
}
return mo.RightCachedUser, FreshUser)
}
// 模式匹配
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:
go
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)——当输出类型等于输入类型时有效:
go
opt := mo.Some(42)
doubled := opt.Map(func(v int) (int, bool) {
return v * 2, true
}) // Option[int]
子包函数(option.Map、result.Map)——当输出类型与输入不同时需要:
go
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)——以可读的方式链式调用多个类型更改转换:
go
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.N