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.
## Table of Contents
- [Type Hierarchy](#type-hierarchy)
- [Future[T] — Asynchronous Values](#futuret--asynchronous-values)
- [Constructor](#constructor)
- [Chaining](#chaining)
- [Collecting Results](#collecting-results)
- [Cancellation](#cancellation)
- [IO[T] — Synchronous Side Effects](#iot--synchronous-side-effects)
- [Variants by Parameter Count](#variants-by-parameter-count)
- [IOEither[T] — Synchronous Side Effects with Errors](#ioeithert--synchronous-side-effects-with-errors)
- [Variants by Parameter Count](#variants-by-parameter-count-1)
- [Task[T] — Asynchronous Computations](#taskt--asynchronous-computations)
- [From IO](#from-io)
- [Variants by Parameter Count](#variants-by-parameter-count-2)
- [TaskEither[T] — Async Computations with Errors](#taskeithert--async-computations-with-errors)
- [Methods](#methods)
- [State[S, A] — Stateful Computations](#states-a--stateful-computations)
- [Constructor](#constructor-1)
- [ReturnState — wrap a value without modifying state](#returnstate--wrap-a-value-without-modifying-state)
- [State Manipulation](#state-manipulation)
- [Chaining State Computations](#chaining-state-computations)
- [When to Use Advanced Types](#when-to-use-advanced-types)
## 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.
## Table of Contents
- [Constructors](#constructors)
- [Type Checking](#type-checking)
- [Value Extraction](#value-extraction)
- [Transformations](#transformations)
- [Swap — exchange left and right](#swap--exchange-left-and-right)
- [MapLeft / MapRight — transform one side](#mapleft--mapright--transform-one-side)
- [Match — pattern matching](#match--pattern-matching)
- [ForEach — side effects](#foreach--side-effects)
- [Either vs Result](#either-vs-result)
- [Either3[T1, T2, T3] — Three-Type Union](#either3t1-t2-t3--three-type-union)
- [Constructors](#constructors-1)
- [Type Checking and Extraction](#type-checking-and-extraction)
- [Pattern Matching](#pattern-matching)
- [Transformations](#transformations-1)
- [Either4 and Either5](#either4-and-either5)
## 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
## Table of Contents
- [What is Functional Programming?](#what-is-functional-programming)
- [What is a Monad?](#what-is-a-monad)
- [The Railway Metaphor](#the-railway-metaphor)
- [Why Monads Are Valuable in Go](#why-monads-are-valuable-in-go)
- [1. Compile-Time Nil Safety (Option)](#1-compile-time-nil-safety-option)
- [2. Railway-Oriented Error Handling (Result)](#2-railway-oriented-error-handling-result)
- [3. Composable Pipelines](#3-composable-pipelines)
- [The Three Core Monads](#the-three-core-monads)
- [Option — Represents Absence](#option--represents-absence)
- [Result — Represents Fallibility](#result--represents-fallibility)
- [Either — Represents Alternatives](#either--represents-alternatives)
- [When to Use mo vs Plain Go](#when-to-use-mo-vs-plain-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
## Table of Contents
- [Constructors](#constructors)
- [Query Methods](#query-methods)
- [Value Extraction](#value-extraction)
- [Transformations](#transformations)
- [Map — transform the value if present](#map--transform-the-value-if-present)
- [MapValue — transform without filter](#mapvalue--transform-without-filter)
- [MapNone — provide value when absent](#mapnone--provide-value-when-absent)
- [FlatMap — chain Options (same type)](#flatmap--chain-options-same-type)
- [Match — handle both cases](#match--handle-both-cases)
- [ForEach — side effect on present value](#foreach--side-effect-on-present-value)
- [Equality](#equality)
- [Serialization](#serialization)
- [Database Support](#database-support)
- [Go 1.24+ omitzero Support](#go-124-omitzero-support)
## 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.
## Table of Contents
- [Why Sub-Packages Exist](#why-sub-packages-exist)
- [option/ Package](#option-package)
- [Transformation Functions](#transformation-functions)
- [Pipe Functions](#pipe-functions)
- [result/ Package](#result-package)
- [Transformation Functions](#transformation-functions-1)
- [Pipe Functions](#pipe-functions-1)
- [either/ Package](#either-package)
- [Transformation Functions](#transformation-functions-2)
- [Pipe Functions](#pipe-functions-2)
- [either3/, either4/, either5/ Packages](#either3-either4-either5-packages)
- [When to Use Pipes vs Direct Methods](#when-to-use-pipes-vs-direct-methods)
- [Example: Combined Usage](#example-combined-usage)
## 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
## Table of Contents
- [Constructors](#constructors)
- [Do Notation](#do-notation)
- [Query Methods](#query-methods)
- [Value Extraction](#value-extraction)
- [Transformations](#transformations)
- [Map — transform successful value](#map--transform-successful-value)
- [MapValue — transform without error possibility](#mapvalue--transform-without-error-possibility)
- [MapErr — transform error state](#maperr--transform-error-state)
- [FlatMap — chain Results](#flatmap--chain-results)
- [Match — handle both cases](#match--handle-both-cases)
- [ForEach — side effect on success](#foreach--side-effect-on-success)
- [Conversion](#conversion)
- [JSON Serialization](#json-serialization)
## 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"}}
```
SKILL.md
---
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. Not for nil-safety and zero-value design without this library (→ See `samber/cc-skills-golang@golang-safety` skill), nor for native error wrapping with fmt.Errorf, errors.Is and errors.As (→ See `samber/cc-skills-golang@golang-error-handling` skill)."
user-invocable: true
license: MIT
compatibility: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
metadata:
author: samber
version: "1.1.2"
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__*
paths:
- "**/*.go"
---
**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:** Reason as thoroughly as possible when designing multi-step Option/Result/Either pipelines — wrong type choice creates unnecessary wrapping/unwrapping that defeats the purpose of monads. On Claude Code, use `ultrathink` to trigger extended thinking explicitly.
# 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:**
- [pkg.go.dev/github.com/samber/mo](https://pkg.go.dev/github.com/samber/mo)
- [github.com/samber/mo](https://github.com/samber/mo)
This skill is not exhaustive — 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`), preferred 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.
```bash
go get github.com/samber/mo
```
For an introduction to functional programming concepts and why monads are valuable in Go, see [Monads Guide](./references/monads-guide.md).
## 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.
```go
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](./references/option.md).
## Result[T] — Error Handling as Values
Represents success (`Ok`) or failure (`Err`). Equivalent to `Either[error, T]` but specialized for Go's error pattern.
```go
// Wrap Go's (value, error) pattern
result := mo.TupleToResult(os.ReadFile("config.yaml"))
// Same-type transform — errors short-circuit automatically
upper := mo.Ok("hello").Map(func(s string) (string, error) {
return strings.ToUpper(s), nil
})
// Ok("HELLO")
// Extract with fallback
val := upper.OrElse("default")
```
**Go limitation:** Direct methods (`.Map`, `.FlatMap`) cannot change the type parameter — `Result[T].Map` returns `Result[T]`, not `Result[U]`. Go methods cannot introduce new type parameters. For type-changing transforms (e.g. `Result[[]byte]` to `Result[Config]`), use sub-package functions or `mo.Do`:
```go
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](./references/result.md).
## 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.
```go
// API that returns either cached data or fresh data
func fetchUser(id string) mo.Either[CachedUser, FreshUser] {
if cached, ok := cache.Get(id); ok {
return mo.Left[CachedUser, FreshUser](cached)
}
return mo.Right[CachedUser, FreshUser](db.Fetch(id))
}
// Pattern match
result := fetchUser("user-123")
result.Match(
func(cached CachedUser) mo.Either[CachedUser, FreshUser] { /* use cached */ },
func(fresh FreshUser) mo.Either[CachedUser, FreshUser] { /* use fresh */ },
)
```
**When to use Either vs Result:** Use `Result[T]` when one path is an error. Use `Either[L, R]` when both paths are valid alternatives (cached vs fresh, left vs right, strategy A vs B).
`Either3[T1, T2, T3]`, `Either4`, and `Either5` extend this to 3-5 type variants.
For full API reference, see [Either Reference](./references/either.md).
## Do Notation — Imperative Style with Monadic Safety
`mo.Do` wraps imperative code in a `Result`, catching panics from `MustGet()` calls:
```go
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:
```go
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:
```go
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:
```go
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](./references/pipelines.md).
## Common Patterns
### JSON API responses with Option
```go
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
```go
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
```go
// Convert map lookup to Option
func MapGet[K comparable, V any](m map[K]V, key K) mo.Option[V] {
return mo.TupleToOption(m[key]) // m[key] returns (V, bool)
}
```
### Uniform extraction with Fold
`mo.Fold` works uniformly across Option, Result, and Either via the `Foldable` interface:
```go
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](./references/advanced-types.md).
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