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.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
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