golang-samber-mo

golang-samber-mo

熱門

使用 samber/mo 為 Golang 提供的單子類型 — Option、Result、Either、Future、IO、Task 和 State 類型,用於型別安全的可空值、錯誤處理以及搭配管道子套件的函數式組合。適用於使用或採用 samber/mo 時、程式碼庫匯入 `github.com/samber/mo` 時,或考慮將函數式程式設計模式作為 Golang 的安全設計時。

2261星標
150分支
更新於 2026/6/6
SKILL.md
readonlyread-only
name
golang-samber-mo
description

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
})

主要方法: SomeNoneGetMustGetOrElseOrEmptyMapFlatMapMatchForEachToPointerIsPresentIsAbsent

Option 實作了 json.Marshaler/Unmarshalersql.Scannerdriver.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) }),
)

主要方法: OkErrErrfTupleToResultTryGetMustGetOrElseMapFlatMapMapErrMatchForEachToEitherIsOkIsError

如需完整 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]Either4Either5 將其擴展到 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.Mapresult.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.Pipe3result.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"

最佳實踐

  1. 優先使用 OrElse 而非 MustGet——MustGet 在值不存在/錯誤時會 panic;僅在 mo.Do 區塊內(panic 會被捕捉)或確定值存在時使用
  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) 從左到右閱讀;當單子鏈結更清晰時,避免巢狀的 if/else 模式
  6. 多步驟型別轉換使用子套件 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 模式