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://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/either.md
Either[L, R] API Reference
A discriminated union representing a value of one of two possible types. By convention, Left is the "alternative" path and Right is the "primary" path, but neither implies success or failure.
Table of Contents
Constructors
Type Checking
Value Extraction
Transformations
Swap — exchange left and right
MapLeft / MapRight — transform one side
Match — pattern matching
ForEach — side effects
Either vs Result
Either3[T1, T2, T3] — Three-Type Union
Constructors
Type Checking and Extraction
Pattern Matching
Transformations
Either4 and Either5
Constructors
| Function | Description |
|---|---|
mo.Left[L, R](value L) | Creates a left-side Either |
mo.Right[L, R](value R) | Creates a right-side Either |
Type Checking
| Method | Returns | Description |
|---|---|---|
IsLeft() | bool | True if the value is on the left side |
IsRight() | bool | True if the value is on the right side |
Value Extraction
| Method | Returns | Description |
|---|---|---|
Left() | (L, bool) | Left value and whether it exists |
Right() | (R, bool) | Right value and whether it exists |
MustLeft() | L | Left value or panics |
MustRight() | R | Right value or panics |
LeftOrElse(fallback L) | L | Left value or fallback |
RightOrElse(fallback R) | R | Right value or fallback |
LeftOrEmpty() | L | Left value or zero value |
RightOrEmpty() | R | Right value or zero value |
Unpack() | (L, R) | Both values (one will be zero value) |
Transformations
Swap — exchange left and right
e := mo.Left[string, int]("hello")
swapped := e.Swap() // Either[int, string] — Right("hello")MapLeft / MapRight — transform one side
The callback receives the value and must return a new Either[L, R]:
e := mo.Left[string, int]("hello")
upper := e.MapLeft(func(s string) mo.Either[string, int] {
return mo.Left[string, int](strings.ToUpper(s))
})
// Left("HELLO")
e2 := mo.Right[string, int](42)
doubled := e2.MapRight(func(v int) mo.Either[string, int] {
return mo.Right[string, int](v * 2)
})
// Right(84)Go limitation: Like Option.Map and Result.Map, direct Either methods cannot change the type parameters. Use sub-package either.MapLeft/either.MapRight for type-changing transforms — see Pipelines Reference.
Match — pattern matching
e.Match(
func(left string) mo.Either[string, int] {
fmt.Println("Left:", left)
return mo.Left[string, int](left)
},
func(right int) mo.Either[string, int] {
fmt.Println("Right:", right)
return mo.Right[string, int](right)
},
)ForEach — side effects
e.ForEach(
func(left string) { fmt.Println("Left:", left) },
func(right int) { fmt.Println("Right:", right) },
)Either vs Result
| Feature | Either[L, R] | Result[T] |
|---|---|---|
| Left/Err type | Any type L | Always error |
| Semantics | Two valid alternatives | Success or failure |
| Use case | Cached vs fresh, A vs B | Operation that may fail |
| JSON | Not supported | JSON-RPC format |
Result[T] is equivalent to Either[error, T] — use result.ToEither() to convert.
Either3[T1, T2, T3] — Three-Type Union
Constructors
e := mo.NewEither3Arg1[string, int, bool]("hello") // T1 variant
e := mo.NewEither3Arg2[string, int, bool](42) // T2 variant
e := mo.NewEither3Arg3[string, int, bool](true) // T3 variantType Checking and Extraction
e.IsArg1() // true if T1
e.IsArg2() // true if T2
e.IsArg3() // true if T3
val, ok := e.Arg1() // (T1, bool)
val := e.MustArg1() // T1 or panics
val := e.Arg1OrElse(fb) // T1 or fallback
val := e.Arg1OrEmpty() // T1 or zero value
t1, t2, t3 := e.Unpack() // all three (two will be zero)Pattern Matching
e.Match(
func(s string) mo.Either3[string, int, bool] { ... },
func(i int) mo.Either3[string, int, bool] { ... },
func(b bool) mo.Either3[string, int, bool] { ... },
)Transformations
MapArg callbacks receive the value and return a new Either3:
e.MapArg1(func(s string) mo.Either3[string, int, bool] {
return mo.NewEither3Arg1[string, int, bool](strings.ToUpper(s))
})
e.MapArg2(func(i int) mo.Either3[string, int, bool] {
return mo.NewEither3Arg2[string, int, bool](i * 2)
})Either4 and Either5
Follow the exact same pattern as Either3 with 4 and 5 type parameters respectively:
Either4[T1, T2, T3, T4]:
NewEither4Arg1throughNewEither4Arg4,IsArg1-IsArg4,MapArg1-MapArg4Either5[T1, T2, T3, T4, T5]:
NewEither5Arg1throughNewEither5Arg5,IsArg1-IsArg5,MapArg1-MapArg5
Use Either3+ when you need a type-safe union of multiple types — for example, an API that returns different response shapes depending on the request type.
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