Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 15% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 70% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 31% | 0% |
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.
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. 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.
bashgo get github.com/samber/mo
For an introduction to functional programming concepts and why monads are valuable in Go, see Monads Guide.
| 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 |
Represents a value that is either present (Some) or absent (None). Eliminates nil pointer risks at the type level.
goimport "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.
Represents success (Ok) or failure (Err). Equivalent to Either[error, T] but specialized for Go's error pattern.
go// 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:
goimport "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.
Represents a value that is one of two possible types. Unlike Result, neither side implies success or failure — both are valid alternatives.
go// 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.
mo.Do wraps imperative code in a Result, catching panics from MustGet() calls:
goresult := 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.
samber/mo provides two ways to compose operations:
Direct methods (.Map, .FlatMap) — work when the output type equals the input type:
goopt := 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:
goimport "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:
goimport "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.
gotype UserResponse struct { Name string `json:"name"` Nickname mo.Option[string] `json:"nickname"` // omits null gracefully Bio mo.Option[string] `json:"bio"` }
gotype User struct { ID int Email string Phone mo.Option[string] // implements sql.Scanner + driver.Valuer } err := row.Scan(&u.ID, &u.Email, &u.Phone)
go// 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) }
mo.Fold works uniformly across Option, Result, and Either via the Foldable interface:
gostr := 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"
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 existsTupleToResult at API boundaries — convert Go's (T, error) to Result[T] at the boundary, then chain with Map/FlatMap inside your domain logicResult[T] for errors, Either[L, R] for alternatives — Result is specialized for success/failure; Either is for two valid typesOption[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid valueresult.Map(...).FlatMap(...).OrElse(default) reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaneroption.Pipe3(...) is more readable than nested function callsFor 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>.
samber/cc-skills-golang@golang-samber-lo skill for functional collection transforms (Map, Filter, Reduce on slices) that compose with mo typessamber/cc-skills-golang@golang-error-handling skill for idiomatic Go error handling patternssamber/cc-skills-golang@golang-safety skill for nil-safety and defensive Go codingsamber/cc-skills-golang@golang-database skill for database access patternssamber/cc-skills-golang@golang-design-patterns skill for functional options and other Go patternsOther measured skills in the registry, with their headline benchmark lift.