返回 Skills
samber/cc-skills-golang· MIT 内容可用

golang-samber-mo

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.

安装

与 skills.sh 相同的 Command / Prompt 安装方式


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.7" 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 Bash(godig:) Bash(gopls:) LSP mcp__gopls__

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. 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

TypePurposeThink of it as...
Option[T]Value that may be absentRust's Option, Java's Optional
Result[T]Operation that may failRust's Result<T, E>, replaces (T, error)
Either[L, R]Value of one of two typesScala's Either, TypeScript discriminated union
EitherX[L, R]Value of one of X typesScala's Either, TypeScript discriminated union
Future[T]Async value not yet availableJavaScript Promise
IO[T]Lazy synchronous side effectHaskell's IO
Task[T]Lazy async computationfp-ts Task
State[S, A]Stateful computationHaskell'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 MustGetMustGet 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 valuesOption[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid value
  5. Chain, don't nestresult.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-lo skill for functional collection transforms (Map, Filter, Reduce on slices) that compose with mo types
  • -> See samber/cc-skills-golang@golang-error-handling skill for idiomatic Go error handling patterns
  • -> See samber/cc-skills-golang@golang-safety skill for nil-safety and defensive Go coding
  • -> See samber/cc-skills-golang@golang-database skill for database access patterns
  • -> See samber/cc-skills-golang@golang-design-patterns skill for functional options and other Go patterns

附带文件

evals/evals.json
[
  {
    "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"}
    ]
  }
]
references/advanced-types.md
# 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

```go
future := mo.NewFuture(func(resolve func(int), reject func(error)) {
    // runs asynchronously
    result, err := expensiveComputation()
    if err != nil {
        reject(err)
    } else {
        resolve(result)
    }
})
```

### Chaining

```go
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

```go
value, err := future.Collect()    // blocks until resolved
result := future.Result()          // blocks, returns Result[T]
either := future.Either()          // blocks, returns Either[error, T]
```

### Cancellation

```go
future.Cancel()  // terminates the future chain
```

## IO[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

```go
// 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)  // 3
```

## IOEither[T] — Synchronous Side Effects with Errors

Like IO but the computation can fail. The callback must return `Either[error, R]`, not `(R, error)`.

```go
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

```go
// 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 pattern
```

## Task[T] — Asynchronous Computations

Lazy async computation — `Run()` calls the wrapped function, which returns a `*Future[T]`. Never fails.

```go
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 done
```

Note: `NewTask` accepts `func() *Future[R]` — it wraps a Future-producing function for lazy execution.

### From IO

```go
io := mo.NewIO(func() int { return 42 })
task := mo.NewTaskFromIO(io)  // wrap IO as async Task
```

### Variants by Parameter Count

```go
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 42
```

## TaskEither[T] — Async Computations with Errors

Like Task but the computation can fail. Combines Task semantics with error handling.

```go
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

```go
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

```go
// 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

```go
state := mo.ReturnState[int, string]("hello")
result, s := state.Run(42)  // ("hello", 42) — state unchanged
```

### State Manipulation

```go
// 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:

```go
// 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.
references/either.md
# 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

```go
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]`:

```go
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](./pipelines.md).

### Match — pattern matching

```go
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

```go
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

```go
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 variant
```

### Type Checking and Extraction

```go
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

```go
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:

```go
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]**: `NewEither4Arg1` through `NewEither4Arg4`, `IsArg1`-`IsArg4`, `MapArg1`-`MapArg4`
- **Either5[T1, T2, T3, T4, T5]**: `NewEither5Arg1` through `NewEither5Arg5`, `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.
references/monads-guide.md
# 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 ────────────────────────────────────→ Error
```

This 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:

```go
// 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 None
```

The 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:

```go
// 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:

```go
// 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:

```go
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 != nil` is 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)
references/option.md
# 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

```go
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](./pipelines.md).

### MapValue — transform without filter

```go
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 needed
```

Unlike Map, MapValue's callback returns just `T` (not `(T, bool)`), so it cannot convert to None.

### MapNone — provide value when absent

```go
opt := mo.None[int]()
filled := opt.MapNone(func() (int, bool) {
    return 42, true  // provide default as Some
})
// Some(42)
```

### FlatMap — chain Options (same type)

```go
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

```go
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

```go
opt.ForEach(func(v int) {
    fmt.Println("Value:", v)  // only executes if present
})
```

## Equality

```go
mo.Some(42).Equal(mo.Some(42))  // true
mo.Some(42).Equal(mo.None[int]())  // false
mo.None[int]().Equal(mo.None[int]())  // true
```

## Serialization

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`:

```go
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

```go
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.
references/pipelines.md
# 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:

```go
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:

```go
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:

```go
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

```go
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

```go
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:

- `Match` with handlers for each argument type
- `MapArg1`, `MapArg2`, `MapArg3` (up to `MapArg5` for either5)
- `Pipe1` through `Pipe10`

## 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

```go
// 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]
)
```
references/result.md
# 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

```go
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

```go
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](./pipelines.md).

### MapValue — transform without error possibility

```go
result := mo.Ok(42).MapValue(func(v int) int {
    return v * 2
})
// Ok(84) — no error possible in the mapper
```

### MapErr — transform error state

```go
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

```go
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

```go
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

```go
result.ForEach(func(v int) {
    fmt.Println("Got:", v)  // only executes if Ok
})
```

## Conversion

```go
either := result.ToEither()  // Either[error, T]
// Ok(42) -> Right(42)
// Err(e) -> Left(e)
```

## JSON Serialization

Result marshals to JSON-RPC format:

```go
// Ok(42) marshals to:
{"result": 42}

// Err("fail") marshals to:
{"error": {"message": "fail"}}
```
    golang-samber-mo | Prompt Minder