
Golang Samber Mo
- 33.1k installs
- 2.8k repo stars
- Updated July 27, 2026
- samber/cc-skills-golang
samber/mo is a Go library providing type-safe monadic types (Option, Result, Either) that eliminate nil checks and enable functional error handling pipelines.
About
samber/mo is a Go library providing type-safe monadic types (Option, Result, Either, Future, IO, Task, State) with zero dependencies. Developers use it to replace nil checks with type constraints and transform error handling from imperative (if err != nil) to composable pipelines. It matters because impossible states become unrepresentable at the type level - Some(value) or None, Ok(value) or Err, Left(value) or Right(value) - reducing runtime crashes.
- Type-safe monadic types: Option, Result, Either, Future, IO, Task, State
- Eliminates nil checks and unwrap-or-panic patterns at the type level
- Pipe sub-packages for composable, type-changing transformations
Golang Samber Mo by the numbers
- 33,118 all-time installs (skills.sh)
- +426 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #26 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
golang-samber-mo capabilities & compatibility
- Works with
- postgres · mysql
- Use cases
- api development · refactoring
What golang-samber-mo says it does
Eliminates nil pointer risks at the type level.
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-moAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33.1k |
|---|---|
| repo stars | ★ 2.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | samber/cc-skills-golang ↗ |
What it does
Type-safe nullable values and error handling in Go using monadic abstractions that eliminate nil checks and enable composable pipelines.
Who is it for?
Functional programming in Go; type-safe nullable values; composable error handling; JSON APIs with optional fields; database queries
Skip if: Teams using plain *T pointers with acceptable DTO separation, or projects not using samber/mo or database/sql struct scanning.
When should I use this skill?
Need to eliminate nil checks; building type-safe error handling pipelines; using Go 1.18+ generics; working with optional values or two-type alternatives
What you get
Go struct definitions using mo.Option[T], sql.Scanner-compatible nullable fields, and JSON-marshalable API models without separate DTOs.
- Option[T] struct fields
- Shared scan-and-JSON model definitions
By the numbers
- Eval scenario covers 2 nullable User columns: phone (VARCHAR) and bio (TEXT)
Files
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.
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
1. 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 2. 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 3. Use `Result[T]` for errors, `Either[L, R]` for alternatives — Result is specialized for success/failure; Either is for two valid types 4. Option for nullable fields, not zero values — Option[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid value 5. Chain, don't nest — result.Map(...).FlatMap(...).OrElse(default) reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaner 6. 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
[
{
"id": 1,
"name": "option-vs-pointer-for-nullable-db-field",
"description": "Tests whether the model uses Option[T] instead of *T for nullable database columns when both SQL scanning and JSON marshaling are needed",
"prompt": "I'm adding a REST API to an existing Go service. The User table has a nullable 'phone' column (VARCHAR) and a nullable 'bio' column (TEXT). The same User struct is used both for database row scanning and for JSON API responses. Right now I'm using *string for these fields. I have samber/mo available. Is there a better option and what does the struct look like?",
"trap": "Without the skill, the model suggests keeping *string (it works for both DB and JSON), or proposes sql.NullString for DB + a separate DTO struct for JSON. The skill teaches that mo.Option[T] natively implements BOTH sql.Scanner/driver.Valuer AND json.Marshaler/Unmarshaler, eliminating the need for two types or custom serialization code.",
"assertions": [
{"id": "1.1", "text": "Recommends switching from *string to mo.Option[string] for the nullable fields"},
{"id": "1.2", "text": "Explains that Option implements sql.Scanner and driver.Valuer so row.Scan works directly"},
{"id": "1.3", "text": "Explains that Option implements json.Marshaler/Unmarshaler so the same struct works for JSON responses"},
{"id": "1.4", "text": "Explicitly states that *string requires custom JSON handling to distinguish null vs absent, which Option avoids"},
{"id": "1.5", "text": "Does NOT recommend maintaining two separate structs (one for DB, one for JSON) as the solution"}
]
},
{
"id": 2,
"name": "result-vs-tuple-error-boundary",
"description": "Tests whether the model knows when to use Result[T] vs (T, error)",
"prompt": "I'm writing a Go function that reads a config file, parses YAML, validates the config, and returns the result. The function is part of a public API package. Should I use samber/mo Result[T] as the return type?",
"trap": "Without the skill, the model either always uses Result or always uses (T, error). The correct answer is: use (T, error) at public API boundaries for Go idiom compliance, but use Result internally for chaining.",
"assertions": [
{"id": "2.1", "text": "Recommends returning (Config, error) at the public API boundary, not Result[Config]"},
{"id": "2.2", "text": "Suggests using Result[T] internally for chaining the read-parse-validate pipeline"},
{"id": "2.3", "text": "Shows TupleToResult to convert from Go-style to Result at the start of the chain"},
{"id": "2.4", "text": "Shows .Get() or extraction at the end to convert back to (T, error) for the public return"},
{"id": "2.5", "text": "Explains that Result is for internal composition, not public API signatures"}
]
},
{
"id": 3,
"name": "either-vs-result-two-valid-types",
"description": "Tests whether the model uses Either[L,R] when both outcomes are valid (not errors)",
"prompt": "I have a Go function that looks up a user. It can return either a cached user (from Redis, includes cache metadata) or a fresh user (from the database, no cache metadata). Both outcomes are perfectly valid. What type should the return be?",
"trap": "Without the skill, the model uses an interface, two separate return values, or Result. Either[CachedUser, FreshUser] is correct because neither outcome is an error.",
"assertions": [
{"id": "3.1", "text": "Uses mo.Either[CachedUser, FreshUser] or equivalent Either type"},
{"id": "3.2", "text": "Does NOT use Result[T] (neither outcome is an error)"},
{"id": "3.3", "text": "Explains that Either is for two valid alternatives, Result is for success/failure"},
{"id": "3.4", "text": "Shows Left/Right constructors for the two outcomes"},
{"id": "3.5", "text": "Shows Match or IsLeft/IsRight to handle both cases"}
]
},
{
"id": 4,
"name": "sub-package-for-type-changing-map",
"description": "Tests whether the model uses sub-package functions when Map needs to change types",
"prompt": "I have an mo.Option[int] and I want to convert it to mo.Option[string] using strconv.Itoa. How do I do this with samber/mo?",
"trap": "Without the skill, the model tries Option.Map which cannot change types (Map returns Option[T] not Option[R]). The sub-package option.Map is required for type-changing transforms.",
"assertions": [
{"id": "4.1", "text": "Uses option.Map from the github.com/samber/mo/option sub-package"},
{"id": "4.2", "text": "Does NOT try to use the direct .Map method for type-changing transform"},
{"id": "4.3", "text": "Imports github.com/samber/mo/option"},
{"id": "4.4", "text": "Shows the curried form: option.Map(func(int) string)(opt)"},
{"id": "4.5", "text": "Explains that Go methods cannot introduce new type parameters, hence sub-packages"}
]
},
{
"id": 5,
"name": "do-notation-for-imperative-monadic",
"description": "Tests knowledge of mo.Do for imperative-style monadic code",
"prompt": "I have several mo.Option and mo.Result values in Go that I need to combine. I want to extract all their values, do some computation, and get a Result back. The FlatMap chaining is getting deeply nested. Is there a simpler way?",
"trap": "Without the skill, the model doesn't know about mo.Do which catches MustGet panics and converts them to Result errors, enabling imperative-style monadic code.",
"assertions": [
{"id": "5.1", "text": "Suggests using mo.Do to wrap imperative-style code"},
{"id": "5.2", "text": "Shows MustGet() calls inside the Do block (panics caught by Do)"},
{"id": "5.3", "text": "Explains that mo.Do catches panics from MustGet and converts them to Err"},
{"id": "5.4", "text": "The result of mo.Do is a Result[T]"},
{"id": "5.5", "text": "Shows that this is cleaner than deeply nested FlatMap chains"}
]
},
{
"id": 6,
"name": "pipe-composition-multi-step",
"description": "Tests whether the model uses Pipe functions for multi-step type-changing pipelines",
"prompt": "I need to transform an mo.Option[int] through 3 steps in Go: convert to string, then to []byte, then validate and return Option[ValidatedData]. Each step changes the type. Show me how to compose these.",
"trap": "Without the skill, the model nests function calls or uses intermediate variables. Pipe3 from the option sub-package provides readable left-to-right flow.",
"assertions": [
{"id": "6.1", "text": "Uses option.Pipe3 (or equivalent PipeN) from github.com/samber/mo/option"},
{"id": "6.2", "text": "Each step uses option.Map or option.FlatMap as a curried function"},
{"id": "6.3", "text": "The pipeline reads top-to-bottom or left-to-right, not nested inside-out"},
{"id": "6.4", "text": "Imports github.com/samber/mo/option sub-package"},
{"id": "6.5", "text": "Uses option.FlatMap for the validation step that may return None"}
]
},
{
"id": 7,
"name": "future-vs-task-eager-vs-lazy",
"description": "Tests whether the model distinguishes Future (eager) from Task (lazy)",
"prompt": "I'm building a Go system where I want to define an async computation that should NOT start executing until I explicitly trigger it. Later I'll run it and get the result. Should I use mo.Future or mo.Task?",
"trap": "Without the skill, the model uses Future which starts executing immediately on creation. Task is lazy — it only runs when .Run() is called.",
"assertions": [
{"id": "7.1", "text": "Recommends mo.Task (not Future) because Task is lazy"},
{"id": "7.2", "text": "Explains that Future starts executing immediately on construction"},
{"id": "7.3", "text": "Explains that Task only executes when .Run() is called"},
{"id": "7.4", "text": "Shows that task.Run() returns a *Future[T]"},
{"id": "7.5", "text": "Demonstrates the deferred execution pattern with Task"}
]
},
{
"id": 8,
"name": "tuple-to-result-wrapping-stdlib",
"description": "Tests knowledge of TupleToResult for wrapping Go stdlib calls",
"prompt": "I want to wrap os.ReadFile and strconv.Atoi calls into samber/mo Result types in Go so I can chain them. What's the most concise way?",
"trap": "Without the skill, the model manually calls the function, checks error, and constructs Ok/Err. TupleToResult wraps (T, error) tuples directly.",
"assertions": [
{"id": "8.1", "text": "Uses mo.TupleToResult(os.ReadFile(path)) to wrap directly"},
{"id": "8.2", "text": "Uses mo.TupleToResult(strconv.Atoi(s)) or mo.Try for the second call"},
{"id": "8.3", "text": "Does NOT manually check err and construct Ok/Err separately"},
{"id": "8.4", "text": "Chains the two results using Map or FlatMap"},
{"id": "8.5", "text": "Shows that TupleToResult converts (T, error) to Result[T] in one call"}
]
},
{
"id": 9,
"name": "when-not-to-use-monads",
"description": "Tests whether the model correctly advises against monads for simple cases",
"prompt": "I have a simple Go function that opens a file. If it fails, I log the error and return. There are no subsequent operations to chain. Should I use samber/mo Result for this?",
"trap": "Without the skill, the model might over-apply monads to trivial cases. Plain if err != nil is clearer for single-step error handling.",
"assertions": [
{"id": "9.1", "text": "Advises against using Result for this simple one-step case"},
{"id": "9.2", "text": "Recommends standard Go if err != nil pattern"},
{"id": "9.3", "text": "Explains that Result shines with multi-step chains, not single operations"},
{"id": "9.4", "text": "Does NOT wrap everything in Result just because the library is available"}
]
},
{
"id": 10,
"name": "option-json-serialization-behavior",
"description": "Tests knowledge of Option's JSON marshaling behavior",
"prompt": "In my Go API response struct, I have a field that should be null in JSON when absent and the actual value when present. I'm currently using *string with json omitempty but it shows null instead of omitting. How should I handle this with samber/mo?",
"trap": "Without the skill, the model doesn't know that Option marshals Some(x) to x and None to null, and that Go 1.24+ supports omitzero for full omission.",
"assertions": [
{"id": "10.1", "text": "Uses mo.Option[string] for the nullable JSON field"},
{"id": "10.2", "text": "Explains that Some(x) marshals to the raw value x, None marshals to null"},
{"id": "10.3", "text": "Mentions omitzero tag (Go 1.24+) or IsZero for omitting None fields entirely"},
{"id": "10.4", "text": "Shows the struct definition with json tag"},
{"id": "10.5", "text": "Does NOT require a custom MarshalJSON method — Option handles it natively"}
]
},
{
"id": 11,
"name": "emptyable-to-option-zero-value",
"description": "Tests knowledge of EmptyableToOption for zero-value detection",
"prompt": "I'm receiving a Go struct from an external API where empty string means 'not provided'. I want to convert these empty strings to None and non-empty strings to Some. What's the most concise way with samber/mo?",
"trap": "Without the skill, the model writes if/else to check for empty string. EmptyableToOption does this automatically for any comparable type.",
"assertions": [
{"id": "11.1", "text": "Uses mo.EmptyableToOption to convert zero values to None"},
{"id": "11.2", "text": "Shows that EmptyableToOption returns None for zero value, Some for non-zero"},
{"id": "11.3", "text": "Does NOT write a manual if s == \"\" check when EmptyableToOption exists"},
{"id": "11.4", "text": "Mentions that this works for any comparable type (int 0, empty string, etc.)"}
]
},
{
"id": 12,
"name": "pointer-to-option-nil-handling",
"description": "Tests knowledge of PointerToOption for nil pointer conversion",
"prompt": "I'm parsing a Go JSON payload where fields are *int (pointer to int, nil when absent). I want to convert these to Option[int] for safer downstream processing. How?",
"trap": "Without the skill, the model dereferences the pointer manually with nil check. PointerToOption does this in one call.",
"assertions": [
{"id": "12.1", "text": "Uses mo.PointerToOption(ptr) to convert *int to Option[int]"},
{"id": "12.2", "text": "Explains that nil pointer becomes None, non-nil becomes Some(*ptr)"},
{"id": "12.3", "text": "Does NOT manually check if ptr != nil before wrapping"}
]
},
{
"id": 13,
"name": "result-map-vs-flatmap-choice",
"description": "Tests understanding of when to use Map vs FlatMap on Result",
"prompt": "I have a mo.Result[string] in Go. I need to chain two operations: (1) convert string to uppercase (infallible), (2) parse the string as an integer (fallible, returns Result[int]). Which methods should I use?",
"trap": "Without the skill, the model uses Map for both or FlatMap for both. Map is for infallible transforms, FlatMap is for transforms that return Result.",
"assertions": [
{"id": "13.1", "text": "Uses MapValue (or Map returning nil error) for the infallible uppercase operation"},
{"id": "13.2", "text": "Uses FlatMap for the fallible parse operation that returns Result[int]"},
{"id": "13.3", "text": "Does NOT use FlatMap for the simple uppercase transform"},
{"id": "13.4", "text": "Shows the chain: result.MapValue(toUpper).FlatMap(parse) or equivalent"},
{"id": "13.5", "text": "Explains the distinction: Map wraps the return value, FlatMap takes a function returning Result"}
]
},
{
"id": 14,
"name": "option-map-bool-semantics",
"description": "Tests understanding of Option.Map's (T, bool) return type",
"prompt": "I have an mo.Option[int] in Go and I want to keep the value only if it's greater than 10, otherwise turn it into None. How do I do this with Map?",
"trap": "Without the skill, the model doesn't realize Option.Map returns (T, bool) where the bool controls Some/None, acting as both map and filter.",
"assertions": [
{"id": "14.1", "text": "Uses opt.Map(func(v int) (int, bool) { return v, v > 10 })"},
{"id": "14.2", "text": "Explains that returning false from Map's callback converts to None"},
{"id": "14.3", "text": "Shows the (T, bool) return signature of Map's callback"},
{"id": "14.4", "text": "Does NOT use FlatMap with an if/else to accomplish filtering"}
]
},
{
"id": 15,
"name": "fold-for-uniform-value-extraction",
"description": "Tests knowledge of mo.Fold for extracting a value from Option, Result, or Either with two-callback pattern",
"prompt": "I have an mo.Result[int] in Go. I want to convert it to a display string: if it's Ok, show the number formatted as 'value: N'; if it's Err, show 'error: <message>'. I'm currently using IsOk/IsError with an if/else. Is there a more declarative way with samber/mo?",
"trap": "Without the skill, the model uses if/else with IsOk/IsError and manual extraction. mo.Fold accepts two callbacks — success and failure — and dispatches uniformly. The model often misses Fold entirely or confuses it with Match (which returns the same type as its receiver, requiring type-changing sub-package use).",
"assertions": [
{"id": "15.1", "text": "Uses mo.Fold to replace the if/else dispatch"},
{"id": "15.2", "text": "Passes a success callback (func(int) string) as the first function argument"},
{"id": "15.3", "text": "Passes a failure callback (func(error) string) as the second function argument"},
{"id": "15.4", "text": "The output type (string) differs from the input type (int) — Fold is used for type-changing extraction"},
{"id": "15.5", "text": "Does NOT use IsOk/IsError with separate MustGet/Error calls as the primary approach"}
]
},
{
"id": 16,
"name": "io-for-testable-side-effects",
"description": "Tests knowledge of IO for deferring and testing side effects",
"prompt": "I have a Go function that reads from stdin, formats the input, and writes to stdout. I want to make this testable by separating the side effects from the logic. How can samber/mo help?",
"trap": "Without the skill, the model uses interfaces or dependency injection for io.Reader/io.Writer. IO[T] provides a functional approach to defer side effects.",
"assertions": [
{"id": "16.1", "text": "Uses mo.IO or mo.IOEither to wrap the side-effecting operations"},
{"id": "16.2", "text": "Explains that IO is lazy — the side effect only runs when Run() is called"},
{"id": "16.3", "text": "Shows that IO1 or IO2 can parameterize the side effect for testing"},
{"id": "16.4", "text": "Demonstrates composability of IO operations"}
]
},
{
"id": 17,
"name": "result-to-either-conversion",
"description": "Tests knowledge of Result.ToEither() conversion",
"prompt": "I have a mo.Result[User] in Go and I need to pass it to a function that expects mo.Either[error, User]. How do I convert?",
"trap": "Without the skill, the model manually matches on IsOk/IsError and constructs Left/Right. ToEither() does this directly.",
"assertions": [
{"id": "17.1", "text": "Uses result.ToEither() for direct conversion"},
{"id": "17.2", "text": "Explains that Ok becomes Right, Err becomes Left"},
{"id": "17.3", "text": "Does NOT manually check IsOk/IsError and construct Either"}
]
},
{
"id": 18,
"name": "either3-for-multi-type-union",
"description": "Tests knowledge of Either3+ for n-ary type unions",
"prompt": "My Go API can return three different response types depending on the request: a UserResponse, an AdminResponse, or a SystemResponse. I want type-safe handling. What should I use?",
"trap": "Without the skill, the model uses interface{}/any or defines a common interface. Either3 provides a type-safe discriminated union.",
"assertions": [
{"id": "18.1", "text": "Uses mo.Either3[UserResponse, AdminResponse, SystemResponse]"},
{"id": "18.2", "text": "Shows NewEither3Arg1, NewEither3Arg2, NewEither3Arg3 constructors"},
{"id": "18.3", "text": "Shows Match with handlers for all three types"},
{"id": "18.4", "text": "Does NOT use interface{}/any which loses type safety"},
{"id": "18.5", "text": "Mentions Either4/Either5 for 4-5 type variants"}
]
},
{
"id": 19,
"name": "option-vs-zero-value-distinction",
"description": "Tests understanding of when Option adds value vs when zero values suffice",
"prompt": "In my Go struct, I have a 'count' field (int) and a 'nickname' field (string). For 'count', zero is a valid value meaning 'no items'. For 'nickname', empty string means 'not set'. Should I use Option for both?",
"trap": "Without the skill, the model either uses Option for both or neither. Option should be used only for 'nickname' where absence is semantically different from empty.",
"assertions": [
{"id": "19.1", "text": "Recommends plain int for count (zero is a valid meaningful value)"},
{"id": "19.2", "text": "Recommends mo.Option[string] for nickname (absence differs from empty)"},
{"id": "19.3", "text": "Explains that Option is for when absence is semantically different from the zero value"},
{"id": "19.4", "text": "Does NOT recommend Option[int] for count where 0 is meaningful"}
]
},
{
"id": 20,
"name": "map-lookup-to-option",
"description": "Tests converting Go map lookups to Option using TupleToOption",
"prompt": "I have a Go map[string]User and I want to look up a key and chain transformations on the result if present. How do I bridge the map lookup with samber/mo?",
"trap": "Without the skill, the model manually checks the ok bool from map access. TupleToOption wraps the (value, bool) tuple directly.",
"assertions": [
{"id": "20.1", "text": "Uses mo.TupleToOption with the map lookup: mo.TupleToOption(m[key])"},
{"id": "20.2", "text": "Chains Map/FlatMap on the resulting Option"},
{"id": "20.3", "text": "Does NOT manually check ok bool and construct Some/None"},
{"id": "20.4", "text": "Shows the complete pattern: TupleToOption(m[key]).Map(...)"}
]
},
{
"id": 21,
"name": "result-try-catch-panics",
"description": "Tests knowledge of mo.Try for wrapping a function that may return an error OR panic, and chaining the result",
"prompt": "I'm integrating a third-party Go JSON schema validator that has two failure modes: it returns an error for invalid input, and it panics on malformed schemas. I want to call it, capture both failure modes, and then chain a transformation if validation succeeds — all without writing defer/recover boilerplate. I'm already using samber/mo in my project. Show the code.",
"trap": "Without the skill, the model writes a defer/recover wrapper function manually, then handles the error. With the skill, mo.Try captures both (T, error) returns and panics into a Result[T] in one call, enabling direct chaining with Map/FlatMap. The model often misses mo.Try entirely (using a hand-rolled wrapper) or uses it but forgets it also catches panics (thinking it only handles errors).",
"assertions": [
{"id": "21.1", "text": "Uses mo.Try (not a manual defer/recover) to call the validator"},
{"id": "21.2", "text": "Explicitly states that mo.Try catches panics AND returned errors, converting both to Err"},
{"id": "21.3", "text": "Shows chaining Map or FlatMap on the Try result for the transformation step"},
{"id": "21.4", "text": "The mo.Try callback matches the (T, error) return signature of the wrapped function"}
]
},
{
"id": 22,
"name": "state-monad-for-accumulation",
"description": "Tests knowledge of State monad for threading state",
"prompt": "I'm writing a Go parser that needs to track position as it consumes tokens from an input string. Each parsing step reads from the current position and advances it. How can I model this with samber/mo?",
"trap": "Without the skill, the model uses a mutable struct. State[S,A] threads state through pure computations.",
"assertions": [
{"id": "22.1", "text": "Uses mo.State or mo.NewState to model the stateful computation"},
{"id": "22.2", "text": "State type parameters represent the state (position/input) and result (parsed token)"},
{"id": "22.3", "text": "Shows Run(initialState) to execute and get (result, newState)"},
{"id": "22.4", "text": "The state is threaded through rather than mutated in place"}
]
},
{
"id": 23,
"name": "result-map-signature-understanding",
"description": "Tests understanding that Result.Map callback returns (T, error) not just T",
"prompt": "I want to double an integer inside a mo.Result[int] in Go. Write the Map call.",
"trap": "Without the skill, the model writes .Map(func(v int) int { return v * 2 }) which won't compile. Result.Map requires (T, error) return.",
"assertions": [
{"id": "23.1", "text": "Map callback returns (int, error), e.g., func(v int) (int, error) { return v * 2, nil }"},
{"id": "23.2", "text": "Does NOT write func(v int) int which misses the error return"},
{"id": "23.3", "text": "Alternatively uses MapValue(func(v int) int { return v * 2 }) for infallible transforms"},
{"id": "23.4", "text": "Shows awareness that Map and MapValue have different callback signatures"}
]
}
]
Advanced Types Reference
These types are less commonly used than Option/Result/Either but provide powerful abstractions for specific scenarios.
Type Hierarchy
Synchronous Asynchronous
----------- ------------
IO[T] (no error) → Task[T] (no error) → Future[T]
IOEither[T] (with error) → TaskEither[T] (with error) → Future[T]- IO wraps a synchronous side-effecting computation
- Task wraps an asynchronous side-effecting computation (returns a Future)
- Future represents a value that will be available later
- Either variants add error handling capability
Future[T] — Asynchronous Values
Represents a value that may not yet be available. Similar to JavaScript's Promise.
Constructor
future := mo.NewFuture(func(resolve func(int), reject func(error)) {
// runs asynchronously
result, err := expensiveComputation()
if err != nil {
reject(err)
} else {
resolve(result)
}
})Chaining
future.
Then(func(v int) (int, error) {
return v * 2, nil // transform on success
}).
Catch(func(err error) (int, error) {
return 0, err // handle error
}).
Finally(func(v int, err error) (int, error) {
// always runs, regardless of success/failure
log.Println("Done")
return v, err
})Collecting Results
value, err := future.Collect() // blocks until resolved
result := future.Result() // blocks, returns Result[T]
either := future.Either() // blocks, returns Either[error, T]Cancellation
future.Cancel() // terminates the future chainIO[T] — Synchronous Side Effects
Wraps a function that performs side effects. The computation is lazy — it only runs when Run() is called. IO never fails.
Variants by Parameter Count
// No parameters
io := mo.NewIO(func() string { return "hello" })
result := io.Run() // "hello"
// 1 parameter
io1 := mo.NewIO1(func(name string) string { return "hello " + name })
result := io1.Run("Alice") // "hello Alice"
// 2-5 parameters (IO2, IO3, IO4, IO5)
io2 := mo.NewIO2(func(a, b int) int { return a + b })
result := io2.Run(1, 2) // 3IOEither[T] — Synchronous Side Effects with Errors
Like IO but the computation can fail. The callback must return Either[error, R], not (R, error).
io := mo.NewIOEither(func() mo.Either[error, string] {
data, err := os.ReadFile("config.yaml")
if err != nil {
return mo.Left[error, string](err)
}
return mo.Right[error, string](string(data))
})
either := io.Run() // Either[error, string]Variants by Parameter Count
// 1 parameter
io1 := mo.NewIOEither1(func(path string) mo.Either[error, string] {
data, err := os.ReadFile(path)
if err != nil {
return mo.Left[error, string](err)
}
return mo.Right[error, string](string(data))
})
either := io1.Run("config.yaml") // Either[error, string]
// IOEither2 through IOEither5 follow the same patternTask[T] — Asynchronous Computations
Lazy async computation — Run() calls the wrapped function, which returns a *Future[T]. Never fails.
task := mo.NewTask(func() *mo.Future[int] {
return mo.NewFuture(func(resolve func(int), reject func(error)) {
time.Sleep(time.Second)
resolve(42)
})
})
future := task.Run() // executes the function, returns *Future[int]
value, err := future.Collect() // blocks until doneNote: NewTask accepts func() *Future[R] — it wraps a Future-producing function for lazy execution.
From IO
io := mo.NewIO(func() int { return 42 })
task := mo.NewTaskFromIO(io) // wrap IO as async TaskVariants by Parameter Count
task1 := mo.NewTask1(func(n int) *mo.Future[int] {
return mo.NewFuture(func(resolve func(int), reject func(error)) {
resolve(n * 2)
})
})
future := task1.Run(21) // *Future[int] resolving to 42TaskEither[T] — Async Computations with Errors
Like Task but the computation can fail. Combines Task semantics with error handling.
te := mo.NewTaskEither(func() *mo.Future[string] {
return mo.NewFuture(func(resolve func(string), reject func(error)) {
resp, err := http.Get("https://api.example.com/data")
if err != nil {
reject(err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
reject(err)
return
}
resolve(string(body))
})
})Note: Like NewTask, NewTaskEither accepts func() *Future[R]. The difference is in the methods available on the returned type — TaskEither provides Match, OrElse, ToEither, and ToTask.
Methods
te.OrElse("fallback") // blocks, returns value or fallback
te.ToEither() // blocks, returns Either[error, T]
te.ToTask("fallback") // converts to Task (uses fallback on error)
te.Match(
func(err error) mo.Either[error, string] { ... }, // on error
func(v string) mo.Either[error, string] { ... }, // on success
)State[S, A] — Stateful Computations
Represents a computation that threads state through a series of operations. The state type S flows through the computation while producing result values of type A.
Constructor
// State computation: takes state, returns (result, newState)
counter := mo.NewState(func(count int) (string, int) {
return fmt.Sprintf("count=%d", count), count + 1
})
result, newState := counter.Run(0) // ("count=0", 1)ReturnState — wrap a value without modifying state
state := mo.ReturnState[int, string]("hello")
result, s := state.Run(42) // ("hello", 42) — state unchangedState Manipulation
// Get — return current state as result
getter := mo.NewState(func(s int) (int, int) { return s, s })
// Put — replace state
putter := state.Put(100)
_, s := putter.Run(0) // (_, 100)
// Modify — transform state
modified := state.Modify(func(s int) int { return s * 2 })
_, s := modified.Run(5) // (_, 10)Chaining State Computations
State is useful for accumulating results while threading context:
// Parse tokens while tracking position
type ParseState struct {
Input string
Position int
}
parseChar := mo.NewState(func(s ParseState) (byte, ParseState) {
ch := s.Input[s.Position]
return ch, ParseState{Input: s.Input, Position: s.Position + 1}
})When to Use Advanced Types
| Type | Use when... |
|---|---|
| Future | You need async computation with chaining (Then/Catch/Finally) |
| IO | You want to defer and compose synchronous side effects |
| IOEither | Deferred side effects that can fail |
| Task | Deferred async computation (lazy Future) |
| TaskEither | Deferred async computation that can fail |
| State | Threading state through a series of pure computations |
Most Go projects only need Option, Result, and Either. The advanced types are valuable when building functional pipelines or when you want explicit control over when side effects execute.
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.
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-MapArg4 - Either5[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.
Functional Programming and Monads in Go
What is Functional Programming?
Functional programming (FP) treats computation as evaluation of mathematical functions. Core principles:
- Immutability — data doesn't change after creation; transformations produce new values
- Pure functions — same input always produces same output, no side effects
- Composition — build complex behavior by chaining simple functions
- Types as documentation — types express constraints and invariants
Go isn't a pure FP language, but Go 1.18+ generics make FP patterns practical. samber/mo brings the most battle-tested FP abstractions — monads — to Go.
What is a Monad?
A monad is a design pattern (not a class or interface) that:
1. Wraps a value in a context (Option wraps "maybe absent", Result wraps "maybe failed") 2. Chains operations that transform the wrapped value without unwrapping it 3. Handles the context automatically — if an Option is None, Map/FlatMap skip the transformation; if a Result is Err, subsequent Maps short-circuit
Think of it as a container with a policy: "I hold a value, and I know what to do when operations succeed or fail."
The Railway Metaphor
Imagine two parallel railway tracks:
- Happy track (top): data flows through transformations successfully
- Error track (bottom): once something goes wrong, the train switches to the error track and skips remaining transformations
Input → [Transform A] → [Transform B] → [Transform C] → Output
↓ (error) (skipped) (skipped)
Error track ────────────────────────────────────→ ErrorThis is exactly how Result.Map and Result.FlatMap work — errors propagate automatically without explicit if/else checks.
Why Monads Are Valuable in Go
1. Compile-Time Nil Safety (Option)
Go's type system doesn't distinguish "this pointer could be nil" from "this pointer is always valid". Option[T] makes this explicit:
// Without mo — caller must remember to check nil
func FindUser(id string) *User { ... } // might return nil
// With mo — the type TELLS you it might be absent
func FindUser(id string) mo.Option[User] { ... } // caller must handle NoneThe type signature is the documentation. No nil pointer panics at runtime — the compiler forces you to handle absence.
2. Railway-Oriented Error Handling (Result)
Go's idiomatic error handling requires checking errors at every step:
// Without mo — repetitive error checking
data, err := readFile(path)
if err != nil { return err }
config, err := parseConfig(data)
if err != nil { return err }
validated, err := validate(config)
if err != nil { return err }With Result and mo.Do, errors short-circuit through the chain:
// With mo — errors propagate automatically via Do notation
result := mo.Do(func() Config {
data := mo.TupleToResult(readFile(path)).MustGet()
config := mo.TupleToResult(parseConfig(data)).MustGet()
validated := mo.TupleToResult(validate(config)).MustGet()
return validated
})Same logic, less boilerplate. The error path is handled by the monad — any MustGet() failure short-circuits to Err.
Note: Direct .Map/.FlatMap methods cannot change the type parameter (Go methods cannot introduce new generic types). For type-changing pipelines, use sub-package result.Pipe functions or mo.Do notation as shown above.
3. Composable Pipelines
Monads compose naturally. You can build complex data transformations from simple, testable pieces:
import "github.com/samber/mo/option"
result := option.Pipe3(
getUserOption(id),
option.Map(func(u User) string { return u.Email }),
option.FlatMap(func(email string) mo.Option[string] {
if isValid(email) { return mo.Some(email) }
return mo.None[string]()
}),
option.Map(func(email string) EmailAddress { return NewEmailAddress(email) }),
)Each step is a pure function. The pipeline handles None propagation. Each step is independently testable.
The Three Core Monads
Option — Represents Absence
Problem it solves: nil pointer panics, ambiguous zero values.
| Concept | Go without mo | Go with mo |
|---|---|---|
| Value present | *User (non-nil) | mo.Some(user) |
| Value absent | *User (nil) | mo.None[User]() |
| Safe access | if u != nil { ... } | opt.OrElse(defaultUser) |
| Transform | manual nil check | opt.Map(transform) |
Use Option when: a value might legitimately be absent (nullable DB columns, optional config, cache lookups).
Don't use Option when: a zero value is meaningful (empty string is valid, 0 is a valid count).
Result — Represents Fallibility
Problem it solves: verbose error checking, lost error context in chains.
| Concept | Go without mo | Go with mo |
|---|---|---|
| Success | return value, nil | mo.Ok(value) |
| Failure | return zero, err | mo.Err[T](err) |
| Chain ops | if err != nil at each step | .Map(...) / .FlatMap(...) |
| Default | manual fallback | .OrElse(default) |
Use Result when: you're chaining multiple fallible operations and want errors to propagate automatically.
Don't use Result when: you need to inspect or modify the error at each step (standard Go error handling is more explicit).
Either — Represents Alternatives
Problem it solves: functions that legitimately return one of two types.
| Concept | Example |
|---|---|
| Cached vs fresh data | Either[CachedUser, FreshUser] |
| Sync vs async result | Either[SyncResult, AsyncResult] |
| Left vs right strategy | Either[StrategyA, StrategyB] |
Use Either when: both outcomes are valid, neither is an "error". If one side is always an error, use Result instead.
When to Use mo vs Plain Go
Use mo when:
- You're building data transformation pipelines with multiple steps
- You need type-safe nullable values (especially in JSON/DB models)
- Error handling chains become repetitive
- You want to make impossible states unrepresentable in the type system
Stick with plain Go when:
- Simple one-step operations where
if err != nilis clear enough - Performance-critical hot paths (monads add thin allocation overhead)
- Your team isn't familiar with FP concepts (readability > cleverness)
- The operation has complex error recovery at each step (explicit handling is clearer)
Option[T] API Reference
Constructors
| Function | Description |
|---|---|
mo.Some[T](value T) | Creates Option with a present value |
mo.None[T]() | Creates Option with an absent value |
mo.TupleToOption[T](value T, ok bool) | Converts (value, bool) tuple — Some if ok is true, None otherwise |
mo.EmptyableToOption[T](value T) | None if value equals its zero value, Some otherwise |
mo.PointerToOption[T](value *T) | None if pointer is nil, Some(\*value) otherwise |
Query Methods
| Method | Returns | Description |
|---|---|---|
IsPresent() / IsSome() | bool | True if value exists |
IsAbsent() / IsNone() | bool | True if value is missing |
Size() | int | 1 if present, 0 if absent |
Get() | (T, bool) | Value and presence indicator |
MustGet() | T | Value or panics — use only inside mo.Do |
Value Extraction
| Method | Returns | Description |
|---|---|---|
OrElse(fallback T) | T | Value if present, fallback otherwise |
OrEmpty() | T | Value if present, zero value otherwise |
ToPointer() | *T | Pointer to value, nil if absent |
Transformations
Map — transform the value if present
opt := mo.Some(42)
doubled := opt.Map(func(v int) (int, bool) {
return v * 2, true // (new value, keep as Some)
})
// Some(84)
// Return false to convert to None
filtered := opt.Map(func(v int) (int, bool) {
return v, v > 100 // None because 42 <= 100
})Go limitation: Option.Map takes func(T) (T, bool) — the input and output types must be the same T. The bool controls whether the result is Some or None. To change the type (e.g. Option[int] to Option[string]), use sub-package option.Map — see Pipelines Reference.
MapValue — transform without filter
opt := mo.Some(42)
doubled := opt.MapValue(func(v int) int { return v * 2 })
// Some(84) — always stays Some if input was Some, no bool neededUnlike Map, MapValue's callback returns just T (not (T, bool)), so it cannot convert to None.
MapNone — provide value when absent
opt := mo.None[int]()
filled := opt.MapNone(func() (int, bool) {
return 42, true // provide default as Some
})
// Some(42)FlatMap — chain Options (same type)
func findUser(id string) mo.Option[User] { ... }
func refreshUser(u User) mo.Option[User] { ... }
refreshed := findUser("123").FlatMap(func(u User) mo.Option[User] {
return refreshUser(u) // same type: Option[User] -> Option[User]
})Go limitation: Direct .FlatMap requires func(T) Option[T] — same input and output type. For type-changing chains (e.g. Option[User] to Option[string]), use option.FlatMap from the sub-package or mo.Do notation.
Match — handle both cases
opt.Match(
func(v int) (int, bool) {
fmt.Println("Got:", v)
return v, true // keep as Some
},
func() (int, bool) {
fmt.Println("Empty!")
return 0, false // stay as None
},
)ForEach — side effect on present value
opt.ForEach(func(v int) {
fmt.Println("Value:", v) // only executes if present
})Equality
mo.Some(42).Equal(mo.Some(42)) // true
mo.Some(42).Equal(mo.None[int]()) // false
mo.None[int]().Equal(mo.None[int]()) // trueSerialization
Option implements multiple encoding interfaces:
| Interface | Behavior |
|---|---|
json.Marshaler / json.Unmarshaler | Some(42) -> 42, None -> null |
encoding.TextMarshaler / TextUnmarshaler | Text encoding/decoding |
encoding.BinaryMarshaler / BinaryUnmarshaler | Binary encoding/decoding |
encoding/gob.GobEncoder / GobDecoder | Gob encoding/decoding |
Database Support
Option implements sql.Scanner and driver.Valuer:
type User struct {
ID int
Phone mo.Option[string] // nullable column
}
// Scanning
err := row.Scan(&u.ID, &u.Phone)
// Inserting
_, err := db.Exec("INSERT INTO users (id, phone) VALUES ($1, $2)", u.ID, u.Phone)Go 1.24+ omitzero Support
type Response struct {
Data string `json:"data"`
Extra mo.Option[string] `json:"extra,omitzero"` // omitted when None
}IsZero() returns true when the Option is None, enabling the omitzero JSON tag.
Pipeline Sub-Packages Reference
samber/mo provides sub-packages (option, result, either, either3, either4, either5) with standalone functions for type-changing transformations and composable pipelines.
Why Sub-Packages Exist
Direct methods on Option/Result/Either (.Map, .FlatMap) cannot change the type parameter because Go methods cannot introduce new type parameters. For example:
opt := mo.Some(42)
// opt.Map can return Option[int], but NOT Option[string]
// because Map's signature is: func (o Option[T]) Map(func(T) (T, bool)) Option[T]Sub-package functions solve this by being standalone generic functions:
import "github.com/samber/mo/option"
// option.Map CAN change the type: Option[int] -> Option[string]
strOpt := option.Map(func(v int) string {
return strconv.Itoa(v)
})(mo.Some(42))
// Some("42")option/ Package
Transformation Functions
| Function | Signature | Description |
|---|---|---|
option.Map | func(I) O -> func(Option[I]) Option[O] | Transform value, changing type |
option.FlatMap | func(I) Option[O] -> func(Option[I]) Option[O] | Chain with type change |
option.Match | (onValue, onNone) -> func(Option[I]) Option[O] | Branch with type change |
option.FlatMatch | (onValue, onNone) -> func(Option[I]) Option[O] | Branch returning Options |
Pipe Functions
Chain multiple transformations in a readable pipeline:
import "github.com/samber/mo/option"
result := option.Pipe3(
mo.Some(42), // Option[int]
option.Map(func(v int) string { return strconv.Itoa(v) }), // -> Option[string]
option.Map(func(s string) []byte { return []byte(s) }), // -> Option[[]byte]
option.FlatMap(func(b []byte) mo.Option[string] { // -> Option[string]
if len(b) > 0 { return mo.Some(string(b)) }
return mo.None[string]()
}),
)Available: option.Pipe1 through option.Pipe10 (1 to 10 transformation steps).
result/ Package
Transformation Functions
| Function | Signature | Description |
|---|---|---|
result.Map | func(I) O -> func(Result[I]) Result[O] | Transform success value, changing type |
result.FlatMap | func(I) Result[O] -> func(Result[I]) Result[O] | Chain with type change |
result.Match | (onValue, onError) -> func(Result[I]) Result[O] | Branch with type change |
result.FlatMatch | (onValue, onError) -> func(Result[I]) Result[O] | Branch returning Results |
Pipe Functions
import "github.com/samber/mo/result"
parsed := result.Pipe2(
mo.TupleToResult(os.ReadFile("config.yaml")), // Result[[]byte]
result.Map(func(data []byte) Config { // -> Result[Config]
var cfg Config
yaml.Unmarshal(data, &cfg)
return cfg
}),
result.FlatMap(func(cfg Config) mo.Result[ValidConfig] { // -> Result[ValidConfig]
return validateConfig(cfg)
}),
)Available: result.Pipe1 through result.Pipe10.
either/ Package
Transformation Functions
| Function | Signature | Description |
|---|---|---|
either.MapLeft | func(Lin) Lout -> func(Either[Lin, R]) Either[Lout, R] | Transform left side type |
either.MapRight | func(Rin) Rout -> func(Either[L, Rin]) Either[L, Rout] | Transform right side type |
either.FlatMapLeft | func(Lin) Either[Lout, R] -> func(Either[Lin, R]) Either[Lout, R] | Chain left with type change |
either.FlatMapRight | func(Rin) Either[L, Rout] -> func(Either[L, Rin]) Either[L, Rout] | Chain right with type change |
either.Match | (onLeft, onRight) -> func(Either[Lin, Rin]) Either[Lout, Rout] | Branch both sides |
either.Swap | func(Either[I, O]) Either[O, I] | Exchange left and right |
Pipe Functions
import "github.com/samber/mo/either"
result := either.Pipe2(
mo.Right[error, int](42),
either.MapRight(func(v int) string { return strconv.Itoa(v) }),
either.MapRight(func(s string) []byte { return []byte(s) }),
)Available: either.Pipe1 through either.Pipe10.
either3/, either4/, either5/ Packages
Each provides:
Matchwith handlers for each argument typeMapArg1,MapArg2,MapArg3(up toMapArg5for either5)Pipe1throughPipe10
When to Use Pipes vs Direct Methods
| Scenario | Use | Why |
|---|---|---|
| Same type in, same type out | Direct method (.Map) | Simpler, no import needed |
| Type changes across steps | Sub-package function | Go methods can't add type params |
| 3+ chained type transforms | Pipe3+ | Readable left-to-right flow |
| Single type transform | Sub-package function call | Pipe1 is overkill |
| Mixed same-type and cross-type | Combine both | Direct for same-type, pipe for cross-type |
Example: Combined Usage
// Start with direct method (same type)
opt := mo.Some(42).
Map(func(v int) (int, bool) { return v * 2, true }) // still Option[int]
// Then use pipe for type change
result := option.Pipe2(
opt,
option.Map(func(v int) string { return strconv.Itoa(v) }), // -> Option[string]
option.Map(func(s string) User { return User{Name: s} }), // -> Option[User]
)Result[T] API Reference
Constructors
| Function | Description |
|---|---|
mo.Ok[T](value T) | Creates a successful Result |
mo.Err[T](err error) | Creates a failed Result |
mo.Errf[T](format string, a ...any) | Creates failed Result with formatted error message |
mo.TupleToResult[T](value T, err error) | Converts Go's (T, error) tuple — Ok if err is nil, Err otherwise |
mo.Try[T](f func() (T, error)) | Executes function, wraps result — Ok on success, Err on error |
Do Notation
result := mo.Do(func() int {
a := mo.Ok(10).MustGet() // panics if Err -> caught by Do
b := mo.Ok(32).MustGet()
return a + b
})
// Ok(42)mo.Do executes a closure and catches any panic from MustGet() calls, converting them to Err. This enables imperative-style code with monadic error propagation.
Query Methods
| Method | Returns | Description |
|---|---|---|
IsOk() | bool | True if Result is successful |
IsError() | bool | True if Result is a failure |
Error() | error | Returns the error, or nil if Ok |
Get() | (T, error) | Returns value and error (Go-style) |
MustGet() | T | Returns value or panics — use only inside mo.Do |
Value Extraction
| Method | Returns | Description |
|---|---|---|
OrElse(fallback T) | T | Value if Ok, fallback if Err |
OrEmpty() | T | Value if Ok, zero value if Err |
Transformations
Map — transform successful value
result := mo.Ok(42).
Map(func(v int) (int, error) {
return v * 2, nil
})
// Ok(84)
// Errors short-circuit
result := mo.Err[int](errors.New("fail")).
Map(func(v int) (int, error) {
return v * 2, nil // never called
})
// Err("fail")Go limitation: Result.Map takes func(T) (T, error) — the input and output types must be the same T. Returning a non-nil error converts Ok to Err. To change the type (e.g. Result[[]byte] to Result[Config]), use sub-package result.Map or mo.Do notation — see Pipelines Reference.
MapValue — transform without error possibility
result := mo.Ok(42).MapValue(func(v int) int {
return v * 2
})
// Ok(84) — no error possible in the mapperMapErr — transform error state
result := mo.Err[int](errors.New("fail")).
MapErr(func(err error) (int, error) {
return 0, fmt.Errorf("wrapped: %w", err)
})
// Err("wrapped: fail")FlatMap — chain Results
func parseAge(s string) mo.Result[int] {
v, err := strconv.Atoi(s)
return mo.TupleToResult(v, err)
}
func validateAge(age int) mo.Result[int] {
if age < 0 || age > 150 {
return mo.Errf[int]("invalid age: %d", age)
}
return mo.Ok(age)
}
result := parseAge("25").FlatMap(func(age int) mo.Result[int] {
return validateAge(age)
})
// Ok(25)Match — handle both cases
result.Match(
func(v int) (int, error) {
fmt.Println("Success:", v)
return v, nil
},
func(err error) (int, error) {
fmt.Println("Error:", err)
return 0, err
},
)ForEach — side effect on success
result.ForEach(func(v int) {
fmt.Println("Got:", v) // only executes if Ok
})Conversion
either := result.ToEither() // Either[error, T]
// Ok(42) -> Right(42)
// Err(e) -> Left(e)JSON Serialization
Result marshals to JSON-RPC format:
// Ok(42) marshals to:
{"result": 42}
// Err("fail") marshals to:
{"error": {"message": "fail"}}Related skills
How it compares
Pick this over generic Go nullable-field advice when samber/mo is already a dependency and one struct must serve both sql and JSON.
FAQ
When do I use Option vs Result?
Option[T] is for values that may be absent (Some/None). Result[T] is for operations that may fail (Ok/Err). Both are monads, but Result is specialized for Go's error pattern.
Why use monads instead of just checking nil?
Monads compose. You can chain Map - FlatMap - Map - OrElse without nested if statements. And the type system prevents you from dereferencing None or Err - you must handle both cases.
Is Golang Samber Mo safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.