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.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
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.
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.
This skill is not exhaustive. Please 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-dev skill (godig) — prefer it 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-gopls skill (gopls). Context7 remains a fallback for docs not indexed on pkg.go.dev.
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
})
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:
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 datafuncfetchUser(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.
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() // panicsreturn 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:
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 Eitherfunc(v int)string { return fmt.Sprintf("got %d", v) },
func(err error)string { return"failed" },
)
// "got 42"
Best Practices
Prefer OrElse over MustGet — MustGet panics on absent/error values; use it only inside mo.Do blocks where panics are caught, or when you are certain the value exists
Use TupleToResult at API boundaries — convert Go's (T, error) to Result[T] at the boundary, then chain with Map/FlatMap inside 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 plain string when 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