golang-samber-mo について
Go エンジニア向けに、型安全な関数型抽象を提供する samber/mo の使い方を解説するスキルです。Option や Result で値の不在やエラーを表し、型をまたぐ変換を組み立てます。
主な機能
- 01Option、Result、Either 型
- 02Future、IO、Task、State 型
- 03型変換パイプライン
- 04mo.Do によるエラー伝播
- 05JSON とデータベースの nullable 値対応
活用シーン
- 01nil のリスクを避けた任意フィールドの表現
- 02Go のエラー処理のパイプライン化
- 03型をまたぐ複数段階の変換
- 04JSON やデータベースモデルでの nullable 値表現
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-samber-moインストール後、Claude Code で/golang-samber-moを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/advanced-types.mdeither.mdmonads-guide.mdoption.mdpipelines.mdresult.md詳細
ペルソナ: Go に関数型プログラミングの安全性をもたらす Go エンジニアです。モナドを使って、あり得ない状態を表現できないようにします。nil チェックは型制約になり、エラー処理は合成可能なパイプラインになります。
思考モード: 複数ステップの Option/Result/Either パイプラインを設計するときは、できる限り徹底して推論してください。型の選択を誤ると、不要なラップやアンラップが発生し、モナドの目的が損なわれます。Claude Code では、拡張思考を明示的に開始するために ultrathink を使用してください。
samber/mo — Go 向けモナドと関数型抽象化
Go 1.18 以降に対応した、型安全なモナド型を提供する依存関係ゼロのライブラリです。Scala、Rust、fp-ts に着想を得ています。
公式リソース:
このスキルは網羅的なものではありません。詳しくはライブラリのドキュメントとコード例を参照してください。
- Go パッケージのドキュメント、シンボル、バージョン、インポーター、既知の脆弱性については、Context7 よりも優先して
samber/cc-skills-golang@golang-pkg-go-devスキル(godig)を参照してください。 - 独自のコード内でこのライブラリの使い方(定義、呼び出し箇所、診断情報)を調べるには、
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] | 2 つの型のいずれかの値 | 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 を使わない nullable 値
値が存在する(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[U] ではなく Result[T] を返します。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] — 2 つの型の判別共用体
2 つの型のいずれかである値を表します。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 記法 — モナドの安全性を備えた命令型スタイル
mo.Do は、命令型のコードを Result でラップし、MustGet() の呼び出しによるパニックを捕捉します。
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 の命令型スタイルとモナドの安全性を両立できます。通常の手順でコードを書くだけで、エラーが自動的に伝播します。
パイプラインのサブパッケージと直接チェーン
samber/mo では、操作を合成する方法が 2 つあります。
直接メソッド(.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]()
}),
)
使い分けの目安: 同じ型への変換には直接メソッドを使います。各ステップで型が変わる場合は、サブパッケージ関数とパイプを使います。
パイプライン API リファレンスの詳細については、パイプラインリファレンスを参照してください。
よくあるパターン
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"`
}
データベースの NULL 許容列
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"
ベストプラクティス
MustGetよりOrElseを優先する —MustGetは値が存在しない場合やエラーの場合にパニックします。パニックが捕捉されるmo.Doブロック内、または値が確実に存在すると分かっている場合にのみ使ってください- API 境界で
TupleToResultを使う — Go の(T, error)を境界でResult[T]に変換し、その後、ドメインロジック内でMap/FlatMapによりチェーンします - エラーには
Result[T]、選択肢にはEither[L, R]を使う — Result は成功/失敗に特化しています。Either は 2 つの有効な型を扱います - ゼロ値ではなく、NULL 許容フィールドには Option を使う —
Option[string]は「値がない」と「空文字列」を区別します。空文字列が有効な値の場合は、通常のstringを使ってください - ネストせず、チェーンする —
result.Map(...).FlatMap(...).OrElse(default)は左から右に読めます。モナドのチェーンのほうが簡潔な場合は、ネストした if/else パターンを避けてください - 複数ステップの型変換にはサブパッケージのパイプを使う — 3 ステップ以上の各段階で型が変わる場合、
option.Pipe3(...)を使うと、関数呼び出しをネストするより読みやすくなります
高度な型(Future、IO、Task、State)については、高度な型のリファレンスを参照してください。
samber/mo でバグや予期しない動作を見つけた場合は、https://github.com/samber/mo/issues で Issue を作成してください。
相互参照
- → mo の型と組み合わせて使える関数型コレクション変換(スライスに対する Map、Filter、Reduce)については、
samber/cc-skills-golang@golang-samber-loスキルを参照してください - → Go の慣用的なエラー処理パターンについては、
samber/cc-skills-golang@golang-error-handlingスキルを参照してください - → nil 安全性と Go の防御的なコーディングについては、
samber/cc-skills-golang@golang-safetyスキルを参照してください - → データベースアクセスのパターンについては、
samber/cc-skills-golang@golang-databaseスキルを参照してください - → 関数型オプションなどの Go のパターンについては、
samber/cc-skills-golang@golang-design-patternsスキルを参照してください