Files
teamai-test/.teamai/skills/common/golang-samber-mo/references/pipelines.md
T

5.2 KiB

Pipeline Sub-Packages Reference

samber/mo provides sub-packages (option, result, either, either3, either4, either5) with standalone functions for type-changing transformations and composable pipelines.

Why Sub-Packages Exist

Direct methods on Option/Result/Either (.Map, .FlatMap) cannot change the type parameter because Go methods cannot introduce new type parameters. For example:

opt := mo.Some(42)
// opt.Map can return Option[int], but NOT Option[string]
// because Map's signature is: func (o Option[T]) Map(func(T) (T, bool)) Option[T]

Sub-package functions solve this by being standalone generic functions:

import "github.com/samber/mo/option"

// option.Map CAN change the type: Option[int] -> Option[string]
strOpt := option.Map(func(v int) string {
    return strconv.Itoa(v)
})(mo.Some(42))
// Some("42")

option/ Package

Transformation Functions

Function Signature Description
option.Map func(I) O -> func(Option[I]) Option[O] Transform value, changing type
option.FlatMap func(I) Option[O] -> func(Option[I]) Option[O] Chain with type change
option.Match (onValue, onNone) -> func(Option[I]) Option[O] Branch with type change
option.FlatMatch (onValue, onNone) -> func(Option[I]) Option[O] Branch returning Options

Pipe Functions

Chain multiple transformations in a readable pipeline:

import "github.com/samber/mo/option"

result := option.Pipe3(
    mo.Some(42),                                                          // Option[int]
    option.Map(func(v int) string { return strconv.Itoa(v) }),            // -> Option[string]
    option.Map(func(s string) []byte { return []byte(s) }),               // -> Option[[]byte]
    option.FlatMap(func(b []byte) mo.Option[string] {                     // -> Option[string]
        if len(b) > 0 { return mo.Some(string(b)) }
        return mo.None[string]()
    }),
)

Available: option.Pipe1 through option.Pipe10 (1 to 10 transformation steps).

result/ Package

Transformation Functions

Function Signature Description
result.Map func(I) O -> func(Result[I]) Result[O] Transform success value, changing type
result.FlatMap func(I) Result[O] -> func(Result[I]) Result[O] Chain with type change
result.Match (onValue, onError) -> func(Result[I]) Result[O] Branch with type change
result.FlatMatch (onValue, onError) -> func(Result[I]) Result[O] Branch returning Results

Pipe Functions

import "github.com/samber/mo/result"

parsed := result.Pipe2(
    mo.TupleToResult(os.ReadFile("config.yaml")),                         // Result[[]byte]
    result.Map(func(data []byte) Config {                                 // -> Result[Config]
        var cfg Config
        yaml.Unmarshal(data, &cfg)
        return cfg
    }),
    result.FlatMap(func(cfg Config) mo.Result[ValidConfig] {              // -> Result[ValidConfig]
        return validateConfig(cfg)
    }),
)

Available: result.Pipe1 through result.Pipe10.

either/ Package

Transformation Functions

Function Signature Description
either.MapLeft func(Lin) Lout -> func(Either[Lin, R]) Either[Lout, R] Transform left side type
either.MapRight func(Rin) Rout -> func(Either[L, Rin]) Either[L, Rout] Transform right side type
either.FlatMapLeft func(Lin) Either[Lout, R] -> func(Either[Lin, R]) Either[Lout, R] Chain left with type change
either.FlatMapRight func(Rin) Either[L, Rout] -> func(Either[L, Rin]) Either[L, Rout] Chain right with type change
either.Match (onLeft, onRight) -> func(Either[Lin, Rin]) Either[Lout, Rout] Branch both sides
either.Swap func(Either[I, O]) Either[O, I] Exchange left and right

Pipe Functions

import "github.com/samber/mo/either"

result := either.Pipe2(
    mo.Right[error, int](42),
    either.MapRight(func(v int) string { return strconv.Itoa(v) }),
    either.MapRight(func(s string) []byte { return []byte(s) }),
)

Available: either.Pipe1 through either.Pipe10.

either3/, either4/, either5/ Packages

Each provides:

  • 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

// 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]
)