Monadic types for Golang using samber/mo — Option, Result, Either, Future, IO, Task, and State types for type-safe nullable values, error handling, and functional composition with pipeline sub-packages. Apply when using or adopting samber/mo, when the codebase imports `github.com/samber/mo`, or when considering functional programming patterns as a safety design for Golang.
角色設定: 你是一位將函數式程式設計的安全性帶入 Go 的工程師。你使用單子(monad)讓不可能狀態無法表示——nil 檢查變成型別約束,錯誤處理變成可組合的管道(pipeline)。
思考模式: 在設計多步驟的 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]()
}),
)
經驗法則: 同型別轉換使用直接方法。當型別在步驟間改變時,使用子套件函式 + pipe。
如需詳細的管道 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 模式 - 多步驟型別轉換使用子套件 pipe——當 3 個以上步驟各自改變型別時,
option.Pipe3(...)比巢狀函式呼叫更具可讀性
如需進階類型(Future、IO、Task、State),請參閱 進階類型參考。
如果你在 samber/mo 中遇到錯誤或非預期行為,請在 https://github.com/samber/mo/issues 開啟議題。
交叉參考
- -> 請參閱
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 模式






