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/advanced-types.md
Advanced Types Reference
These types are less commonly used than Option/Result/Either but provide powerful abstractions for specific scenarios.
Table of Contents
Type Hierarchy
Future[T] — Asynchronous Values
Constructor
Chaining
Collecting Results
Cancellation
IO[T] — Synchronous Side Effects
Variants by Parameter Count
IOEither[T] — Synchronous Side Effects with Errors
Variants by Parameter Count
Task[T] — Asynchronous Computations
From IO
Variants by Parameter Count
TaskEither[T] — Async Computations with Errors
Methods
State[S, A] — Stateful Computations
Constructor
ReturnState — wrap a value without modifying state
State Manipulation
Chaining State Computations
When to Use Advanced Types
Type Hierarchy
Synchronous Asynchronous
----------- ------------
IO[T] (no error) → Task[T] (no error) → Future[T]
IOEither[T] (with error) → TaskEither[T] (with error) → Future[T]IO wraps a synchronous side-effecting computation
Task wraps an asynchronous side-effecting computation (returns a Future)
Future represents a value that will be available later
Either variants add error handling capability
Future[T] — Asynchronous Values
Represents a value that may not yet be available. Similar to JavaScript's Promise.
Constructor
future := mo.NewFuture(func(resolve func(int), reject func(error)) {
// runs asynchronously
result, err := expensiveComputation()
if err != nil {
reject(err)
} else {
resolve(result)
}
})Chaining
future.
Then(func(v int) (int, error) {
return v * 2, nil // transform on success
}).
Catch(func(err error) (int, error) {
return 0, err // handle error
}).
Finally(func(v int, err error) (int, error) {
// always runs, regardless of success/failure
log.Println("Done")
return v, err
})Collecting Results
value, err := future.Collect() // blocks until resolved
result := future.Result() // blocks, returns Result[T]
either := future.Either() // blocks, returns Either[error, T]Cancellation
future.Cancel() // terminates the future chainIO[T] — Synchronous Side Effects
Wraps a function that performs side effects. The computation is lazy — it only runs when Run() is called. IO never fails.
Variants by Parameter Count
// No parameters
io := mo.NewIO(func() string { return "hello" })
result := io.Run() // "hello"
// 1 parameter
io1 := mo.NewIO1(func(name string) string { return "hello " + name })
result := io1.Run("Alice") // "hello Alice"
// 2-5 parameters (IO2, IO3, IO4, IO5)
io2 := mo.NewIO2(func(a, b int) int { return a + b })
result := io2.Run(1, 2) // 3IOEither[T] — Synchronous Side Effects with Errors
Like IO but the computation can fail. The callback must return Either[error, R], not (R, error).
io := mo.NewIOEither(func() mo.Either[error, string] {
data, err := os.ReadFile("config.yaml")
if err != nil {
return mo.Left[error, string](err)
}
return mo.Right[error, string](string(data))
})
either := io.Run() // Either[error, string]Variants by Parameter Count
// 1 parameter
io1 := mo.NewIOEither1(func(path string) mo.Either[error, string] {
data, err := os.ReadFile(path)
if err != nil {
return mo.Left[error, string](err)
}
return mo.Right[error, string](string(data))
})
either := io1.Run("config.yaml") // Either[error, string]
// IOEither2 through IOEither5 follow the same patternTask[T] — Asynchronous Computations
Lazy async computation — Run() calls the wrapped function, which returns a *Future[T]. Never fails.
task := mo.NewTask(func() *mo.Future[int] {
return mo.NewFuture(func(resolve func(int), reject func(error)) {
time.Sleep(time.Second)
resolve(42)
})
})
future := task.Run() // executes the function, returns *Future[int]
value, err := future.Collect() // blocks until doneNote: NewTask accepts func() *Future[R] — it wraps a Future-producing function for lazy execution.
From IO
io := mo.NewIO(func() int { return 42 })
task := mo.NewTaskFromIO(io) // wrap IO as async TaskVariants by Parameter Count
task1 := mo.NewTask1(func(n int) *mo.Future[int] {
return mo.NewFuture(func(resolve func(int), reject func(error)) {
resolve(n * 2)
})
})
future := task1.Run(21) // *Future[int] resolving to 42TaskEither[T] — Async Computations with Errors
Like Task but the computation can fail. Combines Task semantics with error handling.
te := mo.NewTaskEither(func() *mo.Future[string] {
return mo.NewFuture(func(resolve func(string), reject func(error)) {
resp, err := http.Get("https://api.example.com/data")
if err != nil {
reject(err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
reject(err)
return
}
resolve(string(body))
})
})Note: Like NewTask, NewTaskEither accepts func() *Future[R]. The difference is in the methods available on the returned type — TaskEither provides Match, OrElse, ToEither, and ToTask.
Methods
te.OrElse("fallback") // blocks, returns value or fallback
te.ToEither() // blocks, returns Either[error, T]
te.ToTask("fallback") // converts to Task (uses fallback on error)
te.Match(
func(err error) mo.Either[error, string] { ... }, // on error
func(v string) mo.Either[error, string] { ... }, // on success
)State[S, A] — Stateful Computations
Represents a computation that threads state through a series of operations. The state type S flows through the computation while producing result values of type A.
Constructor
// State computation: takes state, returns (result, newState)
counter := mo.NewState(func(count int) (string, int) {
return fmt.Sprintf("count=%d", count), count + 1
})
result, newState := counter.Run(0) // ("count=0", 1)ReturnState — wrap a value without modifying state
state := mo.ReturnState[int, string]("hello")
result, s := state.Run(42) // ("hello", 42) — state unchangedState Manipulation
// Get — return current state as result
getter := mo.NewState(func(s int) (int, int) { return s, s })
// Put — replace state
putter := state.Put(100)
_, s := putter.Run(0) // (_, 100)
// Modify — transform state
modified := state.Modify(func(s int) int { return s * 2 })
_, s := modified.Run(5) // (_, 10)Chaining State Computations
State is useful for accumulating results while threading context:
// Parse tokens while tracking position
type ParseState struct {
Input string
Position int
}
parseChar := mo.NewState(func(s ParseState) (byte, ParseState) {
ch := s.Input[s.Position]
return ch, ParseState{Input: s.Input, Position: s.Position + 1}
})When to Use Advanced Types
| Type | Use when... |
|---|---|
| Future | You need async computation with chaining (Then/Catch/Finally) |
| IO | You want to defer and compose synchronous side effects |
| IOEither | Deferred side effects that can fail |
| Task | Deferred async computation (lazy Future) |
| TaskEither | Deferred async computation that can fail |
| State | Threading state through a series of pure computations |
Most Go projects only need Option, Result, and Either. The advanced types are valuable when building functional pipelines or when you want explicit control over when side effects execute.
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