[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1,258 @@
# 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.
@@ -0,0 +1,151 @@
# 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.
@@ -0,0 +1,163 @@
# 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)
@@ -0,0 +1,151 @@
# 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.
@@ -0,0 +1,147 @@
# 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]
)
```
@@ -0,0 +1,145 @@
# 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"}}
```