Golang Samber Mo
Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The samber/mo library introduces functional programming abstractions to Go, enabling developers to replace traditional nil checks and error handling with type-safe monadic structures. By utilizing types like Option, Result, and Either, developers can create more robust, composable pipelines that make impossible states unrepresentable. The library is designed for Go 1.18+ and provides a suite of tools for handling side effects, asynchronous computations, and stateful logic, bridging the gap between imperative Go and functional safety patterns.
What it does
- Eliminates nil pointer risks using the Option type.
- Replaces standard (T, error) patterns with the composable Result type.
- Implements Either for handling multiple valid alternative outcomes.
- Provides Future, IO, and Task types for managing side effects and asynchronous operations.
- Enables functional pipeline composition using sub-package functions.
- Supports Do notation to bridge imperative code with monadic error propagation.
When to use it
- When you want to enforce type-level safety for nullable values or error states.
- When building complex data processing pipelines that require clean, composable transformations.
- When you need to represent multiple valid outcomes or alternative strategies clearly.
- When adopting functional programming patterns to improve code maintainability and robustness in Go.
- When you need to handle asynchronous computations or side effects in a structured, lazy manner.
Advantages
- Reduces runtime panics by replacing nil checks with explicit Option types.
- Improves code readability by chaining operations instead of nesting if/else blocks.
- Provides consistent, type-safe error handling across the application.
- Facilitates cleaner API boundaries by converting native Go errors into Result types.
- Offers a standard way to handle complex state and side effects without side-effect-heavy imperative code.
Usage examples
- “How do I safely handle a nullable string in my struct using samber/mo?”
- You can use mo.Option[string] in your struct fields, which allows you to distinguish between an absent value (None) and an empty string, and provides methods like OrElse for safe extraction.
- “How can I chain multiple operations that might fail in Go?”
- You can use mo.Result[T] and its associated pipeline functions like result.Pipe2 to chain transformations; if any step returns an error, the pipeline short-circuits automatically.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/samber/golang-samber-mo/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip golang-samber-mo.zip -d .agents/skills
Documentation by samber
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: Reason as thoroughly as possible when designing multi-step Option/Result/Either pipelines — wrong type choice creates unnecessary wrapping/unwrapping that defeats the purpose of monads. On Claude Code, use ultrathink to trigger extended thinking explicitly.
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 — refer to library documentation and code examples for more information:
For Go package docs, symbols, versions, importers, and known vulnerabilities, → See
samber/cc-skills-golang@golang-pkg-go-devskill (godig), preferred over Context7 for Go package facts.To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See
samber/cc-skills-golang@golang-goplsskill (gopls).Context7 remains a fallback for docs not indexed on pkg.go.dev.
go get github.com/samber/moFor 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... |
|---|---|---|
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] | 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.
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, 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.
// 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, 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.
// 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 */ },
)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.
Do Notation — Imperative Style with Monadic Safety
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.
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:
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.
Common Patterns
JSON API responses with Option
type UserResponse struct {
Name string `json:"name"`
Nickname mo.Option[string] `json:"nickname"` // omits null gracefully
Bio mo.Option[string] `json:"bio"`
}Database nullable columns
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)Wrapping existing Go APIs
// 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)
}Uniform extraction with Fold
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"Best Practices
Prefer
OrElseoverMustGet—MustGetpanics on absent/error values; use it only insidemo.Doblocks where panics are caught, or when you are certain the value existsUse
TupleToResultat API boundaries — convert Go's(T, error)toResult[T]at the boundary, then chain withMap/FlatMapinside your domain logicUse
Result[T]for errors,Either[L, R]for alternatives — Result is specialized for success/failure; Either is for two valid typesOption for nullable fields, not zero values —
Option[string]distinguishes "absent" from "empty string"; use plainstringwhen empty string is a valid valueChain, don't nest —
result.Map(...).FlatMap(...).OrElse(default)reads left-to-right; avoid nested if/else patterns when monadic chaining is cleanerUse 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 https://github.com/samber/mo/issues.
Cross-References
→ See
samber/cc-skills-golang@golang-samber-loskill for functional collection transforms (Map, Filter, Reduce on slices) that compose with mo types→ See
samber/cc-skills-golang@golang-error-handlingskill for idiomatic Go error handling patterns→ See
samber/cc-skills-golang@golang-safetyskill for nil-safety and defensive Go coding→ See
samber/cc-skills-golang@golang-databaseskill for database access patterns→ See
samber/cc-skills-golang@golang-design-patternsskill for functional options and other Go patterns
Files
- ATTRIBUTION.mdAdded by AutoGineer905 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md11.5 KiB
- evals/evals.json23.2 KiB
- references/advanced-types.md8.6 KiB
- references/either.md5.5 KiB
- references/monads-guide.md7.5 KiB
- references/option.md5.2 KiB
- references/pipelines.md5.9 KiB
- references/result.md4.3 KiB
references/monads-guide.md
Functional Programming and Monads in Go
Table of Contents
What is Functional Programming?
What is a Monad?
The Railway Metaphor
Why Monads Are Valuable in Go
1. Compile-Time Nil Safety (Option)
2. Railway-Oriented Error Handling (Result)
3. Composable Pipelines
The Three Core Monads
Option — Represents Absence
Result — Represents Fallibility
Either — Represents Alternatives
When to Use mo vs Plain Go
What is Functional Programming?
Functional programming (FP) treats computation as evaluation of mathematical functions. Core principles:
Immutability — data doesn't change after creation; transformations produce new values
Pure functions — same input always produces same output, no side effects
Composition — build complex behavior by chaining simple functions
Types as documentation — types express constraints and invariants
Go isn't a pure FP language, but Go 1.18+ generics make FP patterns practical. samber/mo brings the most battle-tested FP abstractions — monads — to Go.
What is a Monad?
A monad is a design pattern (not a class or interface) that:
Wraps a value in a context (Option wraps "maybe absent", Result wraps "maybe failed")
Chains operations that transform the wrapped value without unwrapping it
Handles the context automatically — if an Option is None, Map/FlatMap skip the transformation; if a Result is Err, subsequent Maps short-circuit
Think of it as a container with a policy: "I hold a value, and I know what to do when operations succeed or fail."
The Railway Metaphor
Imagine two parallel railway tracks:
Happy track (top): data flows through transformations successfully
Error track (bottom): once something goes wrong, the train switches to the error track and skips remaining transformations
Input → [Transform A] → [Transform B] → [Transform C] → Output
↓ (error) (skipped) (skipped)
Error track ────────────────────────────────────→ ErrorThis is exactly how Result.Map and Result.FlatMap work — errors propagate automatically without explicit if/else checks.
Why Monads Are Valuable in Go
1. Compile-Time Nil Safety (Option)
Go's type system doesn't distinguish "this pointer could be nil" from "this pointer is always valid". Option[T] makes this explicit:
// Without mo — caller must remember to check nil
func FindUser(id string) *User { ... } // might return nil
// With mo — the type TELLS you it might be absent
func FindUser(id string) mo.Option[User] { ... } // caller must handle NoneThe type signature is the documentation. No nil pointer panics at runtime — the compiler forces you to handle absence.
2. Railway-Oriented Error Handling (Result)
Go's idiomatic error handling requires checking errors at every step:
// Without mo — repetitive error checking
data, err := readFile(path)
if err != nil { return err }
config, err := parseConfig(data)
if err != nil { return err }
validated, err := validate(config)
if err != nil { return err }With Result and mo.Do, errors short-circuit through the chain:
// With mo — errors propagate automatically via Do notation
result := mo.Do(func() Config {
data := mo.TupleToResult(readFile(path)).MustGet()
config := mo.TupleToResult(parseConfig(data)).MustGet()
validated := mo.TupleToResult(validate(config)).MustGet()
return validated
})Same logic, less boilerplate. The error path is handled by the monad — any MustGet() failure short-circuits to Err.
Note: Direct .Map/.FlatMap methods cannot change the type parameter (Go methods cannot introduce new generic types). For type-changing pipelines, use sub-package result.Pipe functions or mo.Do notation as shown above.
3. Composable Pipelines
Monads compose naturally. You can build complex data transformations from simple, testable pieces:
import "github.com/samber/mo/option"
result := option.Pipe3(
getUserOption(id),
option.Map(func(u User) string { return u.Email }),
option.FlatMap(func(email string) mo.Option[string] {
if isValid(email) { return mo.Some(email) }
return mo.None[string]()
}),
option.Map(func(email string) EmailAddress { return NewEmailAddress(email) }),
)Each step is a pure function. The pipeline handles None propagation. Each step is independently testable.
The Three Core Monads
Option — Represents Absence
Problem it solves: nil pointer panics, ambiguous zero values.
| Concept | Go without mo | Go with mo |
|---|---|---|
| Value present | *User (non-nil) | mo.Some(user) |
| Value absent | *User (nil) | mo.None[User]() |
| Safe access | if u != nil { ... } | opt.OrElse(defaultUser) |
| Transform | manual nil check | opt.Map(transform) |
Use Option when: a value might legitimately be absent (nullable DB columns, optional config, cache lookups).
Don't use Option when: a zero value is meaningful (empty string is valid, 0 is a valid count).
Result — Represents Fallibility
Problem it solves: verbose error checking, lost error context in chains.
| Concept | Go without mo | Go with mo |
|---|---|---|
| Success | return value, nil | mo.Ok(value) |
| Failure | return zero, err | mo.Err[T](err) |
| Chain ops | if err != nil at each step | .Map(...) / .FlatMap(...) |
| Default | manual fallback | .OrElse(default) |
Use Result when: you're chaining multiple fallible operations and want errors to propagate automatically.
Don't use Result when: you need to inspect or modify the error at each step (standard Go error handling is more explicit).
Either — Represents Alternatives
Problem it solves: functions that legitimately return one of two types.
| Concept | Example |
|---|---|
| Cached vs fresh data | Either[CachedUser, FreshUser] |
| Sync vs async result | Either[SyncResult, AsyncResult] |
| Left vs right strategy | Either[StrategyA, StrategyB] |
Use Either when: both outcomes are valid, neither is an "error". If one side is always an error, use Result instead.
When to Use mo vs Plain Go
Use mo when:
You're building data transformation pipelines with multiple steps
You need type-safe nullable values (especially in JSON/DB models)
Error handling chains become repetitive
You want to make impossible states unrepresentable in the type system
Stick with plain Go when:
Simple one-step operations where
if err != nilis clear enoughPerformance-critical hot paths (monads add thin allocation overhead)
Your team isn't familiar with FP concepts (readability > cleverness)
The operation has complex error recovery at each step (explicit handling is clearer)
Requirements and compatibility
Written for: Claude Code, OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Claude Code frontmatter
The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- samber
- Publisher
- Samuel Berthe (Community)
- Distributed by
- Samuel Berthe
- Source
- samber/cc-skills-golang · skills/golang-samber-mo · commit bb1d43d
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-samber-mo.zip