5.2 KiB
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:
Matchwith handlers for each argument typeMapArg1,MapArg2,MapArg3(up toMapArg5for either5)Pipe1throughPipe10
When to Use Pipes vs Direct Methods
| Scenario | Use | Why |
|---|---|---|
| Same type in, same type out | Direct method (.Map) |
Simpler, no import needed |
| Type changes across steps | Sub-package function | Go methods can't add type params |
| 3+ chained type transforms | Pipe3+ |
Readable left-to-right flow |
| Single type transform | Sub-package function call | Pipe1 is overkill |
| Mixed same-type and cross-type | Combine both | Direct for same-type, pipe for cross-type |
Example: Combined Usage
// Start with direct method (same type)
opt := mo.Some(42).
Map(func(v int) (int, bool) { return v * 2, true }) // still Option[int]
// Then use pipe for type change
result := option.Pipe2(
opt,
option.Map(func(v int) string { return strconv.Itoa(v) }), // -> Option[string]
option.Map(func(s string) User { return User{Name: s} }), // -> Option[User]
)