golang-samber-mo
samber/cc-skills-golang
Type-safe monadic types for Go — Option, Result, Either, and more for functional error handling and nullable values.
What is golang-samber-mo?
samber/mo provides Go 1.18+ monadic types (Option, Result, Either, Future, IO, Task, State) that make impossible states unrepresentable and error handling composable. Use it when adopting functional programming patterns, working with nullable values safely, or building type-safe error pipelines without nil checks.
- Eliminate nil pointer risks with Option[T] for type-safe nullable values
- Replace (value, error) tuples with Result[T] for composable error handling
- Model discriminated unions with Either[L, R] and Either3/4/5 for multi-type alternatives
- Chain type-changing transformations using pipeline sub-packages (option.Map, result.Pipe3, etc.)
- Implement lazy async/sync computations with Future[T], Task[T], IO[T], and State[S, A]
- Catch panics from MustGet() calls using Do notation for imperative-style monadic safety
How to install golang-samber-mo
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-mo- Go 1.18 or later
- github.com/samber/mo installed via `go get github.com/samber/mo`
How to use golang-samber-mo
- 1.Import the main package: `import "github.com/samber/mo"`
- 2.Choose the right type: Option[T] for nullable values, Result[T] for error handling, Either[L, R] for alternatives
- 3.Use direct methods (.Map, .FlatMap) when output type matches input type
- 4.Import sub-packages (option, result, either) and use Pipe functions for type-changing transformations
- 5.Use Do notation to wrap imperative code and catch panics as Result errors
- 6.Refer to pkg.go.dev/github.com/samber/mo for full API reference and examples
Use cases
- Building type-safe APIs that return Option or Result instead of (value, error) tuples
- Implementing cached vs fresh data patterns with Either[CachedUser, FreshUser]
- Chaining multi-step transformations (bytes → Config → ValidConfig) with Pipe functions
- Replacing nil checks and error propagation with monadic composition
- Writing lazy side-effect computations with IO and Task for deferred execution
- Go engineers adopting functional programming patterns
- Teams building type-safe APIs with explicit error and absence handling
- Projects requiring composable error pipelines and nil-safety at the type level
- Developers familiar with Rust Result/Option, Scala Either, or fp-ts patterns
golang-samber-mo FAQ
Use Result[T] when one path represents an error or failure. Use Either[L, R] when both paths are valid alternatives (e.g., cached vs fresh data, strategy A vs B). Result is specialized for Go's error pattern; Either is a general discriminated union.
Go methods cannot introduce new type parameters. Option[T].Map and Result[T].Map return the same type. For type-changing transforms (e.g., int → string), use sub-package functions like option.Map() or chain with Pipe functions.
Use .OrElse(default) to provide a fallback, .Get() to return (value, bool), or .MustGet() inside mo.Do() to panic safely. Avoid .MustGet() outside Do notation unless you're certain the value exists.
Yes. Option and Result implement json.Marshaler/Unmarshaler, sql.Scanner, and driver.Valuer, so they work directly in JSON structs and database models without manual conversion.
Direct methods (.Map, .FlatMap) work when input and output types match. Sub-package functions (option.Map, result.Map) are required for type-changing transforms and enable readable Pipe chaining for complex pipelines.
Full instructions (SKILL.md)
Source of truth, from samber/cc-skills-golang.
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."
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents, and for projects using Golang.
metadata:
author: samber
version: "1.0.5"
openclaw:
emoji: "🎭"
homepage: https://github.com/samber/cc-skills-golang
requires:
bins:
- go
install: []
skill-library-version: "1.16.0"
allowed-tools: Read Edit Write Glob Grep Bash(go:) Bash(golangci-lint:) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs AskUserQuestion
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: Use ultrathink when designing multi-step Option/Result/Either pipelines. Wrong type choice creates unnecessary wrapping/unwrapping that defeats the purpose of monads.
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. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform. For Go package docs, versions, symbols, and known vulnerabilities, → See samber/cc-skills-golang@golang-pkg-go-dev skill.
go get github.com/samber/mo
For 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 exists - Use
TupleToResultat API boundaries — convert Go's(T, error)toResult[T]at the boundary, then chain withMap/FlatMapinside your domain logic - Use
Result[T]for errors,Either[L, R]for alternatives — Result is specialized for success/failure; Either is for two valid types - Option for nullable fields, not zero values —
Option[string]distinguishes "absent" from "empty string"; use plainstringwhen empty string is a valid value - Chain, don't nest —
result.Map(...).FlatMap(...).OrElse(default)reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaner - Use 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
Related skills
More from samber/cc-skills-golang and the wider catalog.
golang-code-style
Go code style conventions for clarity, control flow, and readability—line breaking, variable declarations, and when comments help.
golang-error-handling
Idiomatic Go error handling: wrapping, inspection, structured logging, and production-grade error tracking.
golang-performance
Go performance optimization patterns: identify bottlenecks with profiling, then apply the right fix.
golang-design-patterns
Idiomatic Go design patterns: functional options, constructors, error handling, resource lifecycle, graceful shutdown, and resilience.
golang-testing
Production-ready Go tests with table-driven patterns, testify integration, parallel execution, fuzzing, and leak detection.
golang-security
Security best practices and vulnerability prevention for Go code—injection, crypto, secrets, and authentication.