Documentation
¶
Overview ¶
Package readerresult provides logging utilities for the ReaderResult monad, which combines the Reader monad (for dependency injection via context.Context) with the Result monad (for error handling).
The logging functions in this package allow you to log Result values (both successes and errors) while preserving the functional composition style.
Package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error ¶
Package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error.
Side Effects and Context ¶
IMPORTANT: In contrast to the functional readerresult package (readerresult.ReaderResult[R, A]), this context/readerresult package has side effects by design because it depends on context.Context, which is inherently effectful:
- context.Context can be cancelled (ctx.Done() channel)
- context.Context has deadlines and timeouts (ctx.Deadline())
- context.Context carries request-scoped values (ctx.Value())
- context.Context propagates cancellation signals across goroutines
This means that ReaderResult[A] = func(context.Context) (A, error) represents an EFFECTFUL computation, not a pure function. The computation's behavior can change based on the context's state (cancelled, timed out, etc.), making it fundamentally different from a pure Reader monad.
Comparison of packages:
- readerresult.ReaderResult[R, A] = func(R) Result[A] - PURE (R can be any type, no side effects)
- idiomatic/readerresult.ReaderResult[R, A] = func(R) (A, error) - EFFECTFUL (also uses context.Context)
- context/readerresult.ReaderResult[A] = func(context.Context) (A, error) - EFFECTFUL (uses context.Context)
Use this package (context/readerresult) when you need:
- Cancellation support for long-running operations
- Timeout/deadline handling
- Request-scoped values (tracing IDs, user context, etc.)
- Integration with Go's standard context-aware APIs
- Idiomatic Go error handling with (value, error) tuples
Use the functional readerresult package when you need:
- Pure dependency injection without side effects
- Testable computations with simple state/config objects
- Functional composition without context propagation
- Generic environment types (not limited to context.Context)
Pure vs Effectful Functions ¶
This package distinguishes between pure (side-effect free) and effectful (side-effectful) functions:
EFFECTFUL FUNCTIONS (depend on context.Context):
- ReaderResult[A]: func(context.Context) (A, error) - Effectful computation that needs context
- These functions are effectful because context.Context is effectful (can be cancelled, has deadlines, carries values)
- Use for: operations that need cancellation, timeouts, context values, or any context-dependent behavior
- Examples: database queries, HTTP requests, operations that respect cancellation
PURE FUNCTIONS (side-effect free):
- func(State) (Value, error) - Pure computation that only depends on state, not context
- func(State) Value - Pure transformation without errors
- These functions are pure because they only read from their input state and don't depend on external context
- Use for: parsing, validation, calculations, data transformations that don't need context
- Examples: JSON parsing, input validation, mathematical computations
The package provides different bind operations for each:
- Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])
- BindResultK: For pure functions with errors (State -> (Value, error))
- Let: For pure functions without errors (State -> Value)
- BindReaderK: For context-dependent pure functions (State -> Reader[Context, Value])
- BindEitherK: For pure Result/Either values (State -> Result[Value])
Index ¶
- func Ap[A, B any](fa ReaderResult[A]) func(ReaderResult[func(A) B]) ReaderResult[B]
- func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderResult[A]) ReaderResult[B]
- func ChainOptionK[A, B any](onNone func() error) func(option.Kleisli[A, B]) Operator[A, B]
- func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) Kleisli[T2, A]
- func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) Kleisli[T3, A]
- func From0[A any](f func(context.Context) (A, error)) func() ReaderResult[A]
- func From2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderResult[A]
- func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderResult[A]
- func Read[A any](r context.Context) func(ReaderResult[A]) Result[A]
- func ReadEither[A any](r Result[context.Context]) func(ReaderResult[A]) Result[A]
- func ReadIO[A any](r IO[context.Context]) func(ReaderResult[A]) IOResult[A]
- func ReadIOEither[A any](r IOResult[context.Context]) func(ReaderResult[A]) IOResult[A]
- func ReadIOResult[A any](r IOResult[context.Context]) func(ReaderResult[A]) IOResult[A]
- func ReadResult[A any](r Result[context.Context]) func(ReaderResult[A]) Result[A]
- func SequenceReader[R, A any](ma ReaderResult[Reader[R, A]]) reader.Kleisli[context.Context, R, Result[A]]
- func TraverseReader[R, A, B any](f reader.Kleisli[R, A, B]) func(ReaderResult[A]) Kleisli[R, B]
- func Uncurry1[T1, A any](f Kleisli[T1, A]) func(context.Context, T1) (A, error)
- func Uncurry2[T1, T2, A any](f func(T1) Kleisli[T2, A]) func(context.Context, T1, T2) (A, error)
- func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) Kleisli[T3, A]) func(context.Context, T1, T2, T3) (A, error)
- type Either
- type Endomorphism
- type IO
- type IOResult
- type Kleisli
- func ApS[S1, S2, T any](setter func(T) func(S1) S2, fa ReaderResult[T]) Kleisli[ReaderResult[S1], S2]
- func ApSL[S, T any](lens Lens[S, T], fa ReaderResult[T]) Kleisli[ReaderResult[S], S]
- func Bind[S1, S2, T any](setter func(T) func(S1) S2, f Kleisli[S1, T]) Kleisli[ReaderResult[S1], S2]
- func BindL[S, T any](lens Lens[S, T], f Kleisli[T, T]) Kleisli[ReaderResult[S], S]
- func Curry1[T1, A any](f func(context.Context, T1) (A, error)) Kleisli[T1, A]
- func From1[T1, A any](f func(context.Context, T1) (A, error)) Kleisli[T1, A]
- func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) Kleisli[A, A]
- func Let[S1, S2, T any](setter func(T) func(S1) S2, f func(S1) T) Kleisli[ReaderResult[S1], S2]
- func LetL[S, T any](lens Lens[S, T], f Endomorphism[T]) Kleisli[ReaderResult[S], S]
- func LetTo[S1, S2, T any](setter func(T) func(S1) S2, b T) Kleisli[ReaderResult[S1], S2]
- func LetToL[S, T any](lens Lens[S, T], b T) Kleisli[ReaderResult[S], S]
- func OrElse[A any](onLeft Kleisli[error, A]) Kleisli[ReaderResult[A], A]
- func SLog[A any](message string) Kleisli[Result[A], A]
- func SLogWithCallback[A any](logLevel slog.Level, cb func(context.Context) *slog.Logger, message string) Kleisli[Result[A], A]
- func TailRec[A, B any](f Kleisli[A, Trampoline[A, B]]) Kleisli[A, B]
- func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B]
- func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderResult[B]) Kleisli[[]A, []B]
- func WithContextK[A, B any](f Kleisli[A, B]) Kleisli[A, B]
- type Lens
- type Operator
- func BindTo[S1, T any](setter func(T) S1) Operator[T, S1]
- func BindToP[S1, T any](setter Prism[S1, T]) Operator[T, S1]
- func Chain[A, B any](f Kleisli[A, B]) Operator[A, B]
- func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A]
- func ChainFirstIOK[A, B any](f io.Kleisli[A, B]) Operator[A, A]
- func ChainFirstLeft[A, B any](f Kleisli[error, B]) Operator[A, A]
- func ChainFirstLeftIOK[A, B any](f io.Kleisli[error, B]) Operator[A, A]
- func ChainIOEitherK[A, B any](f ioresult.Kleisli[A, B]) Operator[A, B]
- func ChainIOK[A, B any](f io.Kleisli[A, B]) Operator[A, B]
- func ChainIOResultK[A, B any](f ioresult.Kleisli[A, B]) Operator[A, B]
- func ChainTo[A, B any](b ReaderResult[B]) Operator[A, B]
- func Contramap[A any](f func(context.Context) (context.Context, context.CancelFunc)) Operator[A, A]
- func FilterOrElse[A any](pred Predicate[A], onFalse func(A) error) Operator[A, A]
- func Flap[B, A any](a A) Operator[func(A) B, B]
- func Local[A any](f func(context.Context) (context.Context, context.CancelFunc)) Operator[A, A]
- func Map[A, B any](f func(A) B) Operator[A, B]
- func MapTo[A, B any](b B) Operator[A, B]
- func Promap[A, B any](f func(context.Context) (context.Context, context.CancelFunc), g func(A) B) Operator[A, B]
- func TapIOK[A, B any](f io.Kleisli[A, B]) Operator[A, A]
- func TapLeftIOK[A, B any](f io.Kleisli[error, B]) Operator[A, A]
- func TapSLog[A any](message string) Operator[A, A]
- type Option
- type Predicate
- type Prism
- type Reader
- type ReaderResult
- func Ask() ReaderResult[context.Context]
- func Curry0[A any](f func(context.Context) (A, error)) ReaderResult[A]
- func Do[S any](empty S) ReaderResult[S]
- func FromEither[A any](e Either[A]) ReaderResult[A]
- func FromIO[A any](t io.IO[A]) ReaderResult[A]
- func FromIOResult[A any](t ioresult.IOResult[A]) ReaderResult[A]
- func FromReader[A any](r Reader[context.Context, A]) ReaderResult[A]
- func Left[A any](l error) ReaderResult[A]
- func MonadAp[A, B any](fab ReaderResult[func(A) B], fa ReaderResult[A]) ReaderResult[B]
- func MonadChain[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[B]
- func MonadChainEitherK[A, B any](ma ReaderResult[A], f func(A) Either[B]) ReaderResult[B]
- func MonadChainFirst[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[A]
- func MonadChainFirstIOK[A, B any](ma ReaderResult[A], f io.Kleisli[A, B]) ReaderResult[A]
- func MonadChainIOK[A, B any](ma ReaderResult[A], f io.Kleisli[A, B]) ReaderResult[B]
- func MonadChainTo[A, B any](ma ReaderResult[A], b ReaderResult[B]) ReaderResult[B]
- func MonadFlap[B, A any](fab ReaderResult[func(A) B], a A) ReaderResult[B]
- func MonadMap[A, B any](fa ReaderResult[A], f func(A) B) ReaderResult[B]
- func MonadMapTo[A, B any](ma ReaderResult[A], b B) ReaderResult[B]
- func MonadTapIOK[A, B any](ma ReaderResult[A], f io.Kleisli[A, B]) ReaderResult[A]
- func Of[A any](a A) ReaderResult[A]
- func Retrying[A any](policy R.RetryPolicy, action Kleisli[R.RetryStatus, A], ...) ReaderResult[A]
- func Right[A any](r A) ReaderResult[A]
- func SequenceArray[A any](ma []ReaderResult[A]) ReaderResult[[]A]
- func SequenceT1[A any](a ReaderResult[A]) ReaderResult[tuple.Tuple1[A]]
- func SequenceT2[A, B any](a ReaderResult[A], b ReaderResult[B]) ReaderResult[tuple.Tuple2[A, B]]
- func SequenceT3[A, B, C any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C]) ReaderResult[tuple.Tuple3[A, B, C]]
- func SequenceT4[A, B, C, D any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], d ReaderResult[D]) ReaderResult[tuple.Tuple4[A, B, C, D]]
- func WithContext[A any](ma ReaderResult[A]) ReaderResult[A]
- type Result
- type Trampoline
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Ap ¶
func Ap[A, B any](fa ReaderResult[A]) func(ReaderResult[func(A) B]) ReaderResult[B]
Ap creates a function that applies a ReaderResult containing a function to a ReaderResult containing a value. This is the curried version where the value ReaderResult is provided first.
Type Parameters:
- A: The input type
- B: The output type
Parameters:
- fa: The ReaderResult containing the value
Returns:
- A function that takes a ReaderResult containing a function and returns the result
Example:
faRR := readerresult.Of(42)
applyTo42 := readerresult.Ap[int, string](faRR)
fabRR := readerresult.Of(func(x int) string {
return fmt.Sprintf("value: %d", x)
})
result := applyTo42(fabRR)(t.Context()) // Right("value: 42")
func ChainEitherK ¶
func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderResult[A]) ReaderResult[B]
ChainEitherK creates an operator that sequences a ReaderResult with a function that returns an Either. This is the curried version where the function is provided first.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the output Either
Parameters:
- f: The function that takes the success value and returns an Either
Returns:
- An Operator that sequences the computations
Example:
validate := readerresult.ChainEitherK(func(x int) result.Result[int] {
if x > 0 {
return result.Of(x)
}
return result.Error[int](errors.New("must be positive"))
})
rr := readerresult.Of(42)
result := validate(rr)(t.Context()) // Right(42)
func ChainOptionK ¶
ChainOptionK creates an operator that sequences a ReaderResult with a function that returns an Option. If the Option is None, the onNone function is called to generate an error.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the output Option
Parameters:
- onNone: The function to generate an error when the Option is None
Returns:
- A function that takes an Option Kleisli and returns an Operator
Example:
chainOpt := readerresult.ChainOptionK[int, string](func() error {
return errors.New("value not found")
})
optKleisli := func(x int) option.Option[string] {
if x > 0 {
return option.Some(fmt.Sprintf("value: %d", x))
}
return option.None[string]()
}
operator := chainOpt(optKleisli)
result := operator(readerresult.Of(42))(t.Context()) // Right("value: 42")
func Curry2 ¶
Curry2 converts a Go function with context and two parameters into a curried function. This enables partial application and functional composition of two-parameter functions.
Type Parameters:
- T1: The type of the first parameter
- T2: The type of the second parameter
- A: The return type of the function
Parameters:
- f: A function that takes a context and two parameters, returning a value and error
Returns:
- A curried function that takes T1 and returns a Kleisli arrow for T2
Example:
// Idiomatic Go function
updateUser := func(ctx context.Context, id int, name string) (User, error) {
if ctx.Err() != nil {
return User{}, ctx.Err()
}
return User{ID: id, Name: name}, nil
}
// Convert to curried form
updateUserCurried := readerresult.Curry2(updateUser)
// Partial application
updateUser123 := updateUserCurried(123)
// Use in a pipeline
pipeline := F.Pipe1(
readerresult.Of("Bob"),
readerresult.Chain(updateUser123),
)
result := pipeline(t.Context()) // Right(User{ID: 123, Name: "Bob"})
func Curry3 ¶
func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) Kleisli[T3, A]
Curry3 converts a Go function with context and three parameters into a curried function. This enables partial application and functional composition of three-parameter functions.
Type Parameters:
- T1: The type of the first parameter
- T2: The type of the second parameter
- T3: The type of the third parameter
- A: The return type of the function
Parameters:
- f: A function that takes a context and three parameters, returning a value and error
Returns:
- A curried function that takes T1, T2, and returns a Kleisli arrow for T3
Example:
// Idiomatic Go function
createOrder := func(ctx context.Context, userID int, productID int, quantity int) (Order, error) {
if ctx.Err() != nil {
return Order{}, ctx.Err()
}
return Order{UserID: userID, ProductID: productID, Quantity: quantity}, nil
}
// Convert to curried form
createOrderCurried := readerresult.Curry3(createOrder)
// Partial application
createOrderForUser := createOrderCurried(123)
createOrderForProduct := createOrderForUser(456)
// Use in a pipeline
pipeline := F.Pipe1(
readerresult.Of(2),
readerresult.Chain(createOrderForProduct),
)
result := pipeline(t.Context()) // Right(Order{UserID: 123, ProductID: 456, Quantity: 2})
func Read ¶
Read executes a ReaderResult by providing it with a context.Context value. This function "runs" the ReaderResult computation with the given context.
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- r: The context.Context to provide to the ReaderResult
Returns:
- A function that takes a ReaderResult and returns its Result
Example:
rr := readerresult.Of(42) ctx := t.Context() runWithCtx := readerresult.Read[int](ctx) result := runWithCtx(rr) // Right(42)
func ReadEither ¶ added in v2.1.10
ReadEither executes a ReaderResult by providing it with a Result[context.Context]. If the Result contains an error, that error is returned immediately. If the Result contains a context, the ReaderResult is executed with that context.
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- r: The Result[context.Context] to provide to the ReaderResult
Returns:
- A function that takes a ReaderResult and returns its Result
Example:
rr := readerresult.Of(42) ctxResult := result.Of[error](t.Context()) runWithCtxResult := readerresult.ReadEither[int](ctxResult) result := runWithCtxResult(rr) // Right(42)
func ReadIO ¶ added in v2.1.14
ReadIO executes a ReaderResult by providing it with a context obtained from an IO computation.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This allows the context itself to be obtained through an IO operation.
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- r: The IO computation that produces a context.Context
Returns:
- A function that takes a ReaderResult and returns an IOResult
Example:
getCtx := func() context.Context { return t.Context() }
rr := readerresult.Of(42)
runWithIO := readerresult.ReadIO[int](getCtx)
ioResult := runWithIO(rr)
result := ioResult() // Right(42)
func ReadIOEither ¶ added in v2.1.14
ReadIOEither executes a ReaderResult by providing it with a context obtained from an IOResult computation. If the IOResult contains an error, that error is returned immediately.
IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations with error handling. This allows the context itself to be obtained through an IO operation that may fail.
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- r: The IOResult computation that produces a context.Context
Returns:
- A function that takes a ReaderResult and returns an IOResult
Example:
getCtx := func() result.Result[context.Context] {
return result.Of[error](t.Context())
}
rr := readerresult.Of(42)
runWithIOResult := readerresult.ReadIOEither[int](getCtx)
ioResult := runWithIOResult(rr)
result := ioResult() // Right(42)
func ReadIOResult ¶ added in v2.1.14
ReadIOResult is an alias for ReadIOEither. It executes a ReaderResult by providing it with a context obtained from an IOResult computation.
IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations with error handling.
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- r: The IOResult computation that produces a context.Context
Returns:
- A function that takes a ReaderResult and returns an IOResult
Example:
getCtx := func() result.Result[context.Context] {
return result.Of[error](t.Context())
}
rr := readerresult.Of(42)
runWithIOResult := readerresult.ReadIOResult[int](getCtx)
ioResult := runWithIOResult(rr)
result := ioResult() // Right(42)
func ReadResult ¶ added in v2.1.10
ReadResult executes a ReaderResult by providing it with a Result[context.Context]. This is an alias for ReadEither.
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- r: The Result[context.Context] to provide to the ReaderResult
Returns:
- A function that takes a ReaderResult and returns its Result
Example:
rr := readerresult.Of(42) ctxResult := result.Of[error](t.Context()) runWithCtxResult := readerresult.ReadResult[int](ctxResult) result := runWithCtxResult(rr) // Right(42)
func SequenceReader ¶
func SequenceReader[R, A any](ma ReaderResult[Reader[R, A]]) reader.Kleisli[context.Context, R, Result[A]]
SequenceReader swaps the order of environment parameters when the inner computation is a Reader.
This function is specialized for the context.Context-based ReaderResult monad. It takes a ReaderResult that produces a Reader and returns a reader.Kleisli that produces Results. The context.Context is implicitly used as the outer environment type.
Type Parameters:
- R: The inner environment type (becomes outer after flip)
- A: The success value type
Parameters:
- ma: A ReaderResult that takes context.Context and may produce a Reader[R, A]
Returns:
- A reader.Kleisli[context.Context, R, Result[A]], which is func(context.Context) func(R) Result[A]
The function preserves error handling from the outer ReaderResult layer. If the outer computation fails, the error is propagated to the inner Result.
Note: This is an inline wrapper around readerresult.SequenceReader, specialized for context.Context as the outer environment type.
Example:
type Database struct {
ConnectionString string
}
// Original: takes context, may fail, produces Reader[Database, string]
original := func(ctx context.Context) result.Result[reader.Reader[Database, string]] {
if ctx.Err() != nil {
return result.Error[reader.Reader[Database, string]](ctx.Err())
}
return result.Ok[error](func(db Database) string {
return fmt.Sprintf("Query on %s", db.ConnectionString)
})
}
// Sequenced: takes context first, then Database
sequenced := SequenceReader(original)
ctx := t.Context()
db := Database{ConnectionString: "localhost:5432"}
// Apply context first to get a function that takes database
dbReader := sequenced(ctx)
// Then apply database to get the final result
result := dbReader(db)
// result is Result[string]
Use Cases:
- Dependency injection: Flip parameter order to inject context first, then dependencies
- Testing: Separate context handling from business logic for easier testing
- Composition: Enable point-free style by fixing the context parameter first
func TraverseReader ¶
TraverseReader transforms a value using a Reader function and swaps environment parameter order.
This function combines mapping and parameter flipping in a single operation. It takes a Reader function (pure computation without error handling) and returns a function that: 1. Maps a ReaderResult[A] to ReaderResult[B] using the provided Reader function 2. Flips the parameter order so R comes before context.Context
Type Parameters:
- R: The inner environment type (becomes outer after flip)
- A: The input value type
- B: The output value type
Parameters:
- f: A reader.Kleisli[R, A, B], which is func(R) func(A) B - a pure Reader function
Returns:
- A function that takes ReaderResult[A] and returns Kleisli[R, B]
- Kleisli[R, B] is func(R) ReaderResult[B], which is func(R) func(context.Context) Result[B]
The function preserves error handling from the input ReaderResult. If the input computation fails, the error is propagated without applying the transformation function.
Note: This is a wrapper around readerresult.TraverseReader, specialized for context.Context.
Example:
type Config struct {
MaxRetries int
}
// A pure Reader function that depends on Config
formatMessage := func(cfg Config) func(int) string {
return func(value int) string {
return fmt.Sprintf("Value: %d, MaxRetries: %d", value, cfg.MaxRetries)
}
}
// Original computation that may fail
computation := func(ctx context.Context) result.Result[int] {
if ctx.Err() != nil {
return result.Error[int](ctx.Err())
}
return result.Ok[error](42)
}
// Create a traversal that applies formatMessage and flips parameters
traverse := TraverseReader[Config, int, string](formatMessage)
// Apply to the computation
flipped := traverse(computation)
// Now we can provide Config first, then context
cfg := Config{MaxRetries: 3}
ctx := t.Context()
result := flipped(cfg)(ctx)
// result is Result[string] containing "Value: 42, MaxRetries: 3"
Use Cases:
- Dependency injection: Inject configuration/dependencies before context
- Testing: Separate pure business logic from context handling
- Composition: Build pipelines where dependencies are fixed before execution
- Point-free style: Enable partial application by fixing dependencies first
func Uncurry1 ¶
Uncurry1 converts a Kleisli arrow back into an idiomatic Go function with context as the first parameter. This is useful for interfacing with code that expects standard Go function signatures.
Type Parameters:
- T1: The type of the parameter
- A: The return type
Parameters:
- f: A Kleisli arrow
Returns:
- A Go function with context as the first parameter
Example:
// Kleisli arrow
getUserKleisli := func(id int) readerresult.ReaderResult[User] {
return func(ctx context.Context) result.Result[User] {
if ctx.Err() != nil {
return result.Error[User](ctx.Err())
}
return result.Of(User{ID: id, Name: "Alice"})
}
}
// Convert back to idiomatic Go function
getUserByID := readerresult.Uncurry1(getUserKleisli)
// Use as a normal Go function
user, err := getUserByID(t.Context(), 123)
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name) // "Alice"
func Uncurry2 ¶
Uncurry2 converts a curried function back into an idiomatic Go function with context as the first parameter. This is useful for interfacing with code that expects standard Go function signatures.
Type Parameters:
- T1: The type of the first parameter
- T2: The type of the second parameter
- A: The return type
Parameters:
- f: A curried function
Returns:
- A Go function with context as the first parameter
Example:
// Curried function
updateUserCurried := func(id int) func(name string) readerresult.ReaderResult[User] {
return func(name string) readerresult.ReaderResult[User] {
return func(ctx context.Context) result.Result[User] {
if ctx.Err() != nil {
return result.Error[User](ctx.Err())
}
return result.Of(User{ID: id, Name: name})
}
}
}
// Convert back to idiomatic Go function
updateUser := readerresult.Uncurry2(updateUserCurried)
// Use as a normal Go function
user, err := updateUser(t.Context(), 123, "Bob")
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name) // "Bob"
func Uncurry3 ¶
func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) Kleisli[T3, A]) func(context.Context, T1, T2, T3) (A, error)
Uncurry3 converts a curried function back into an idiomatic Go function with context as the first parameter. This is useful for interfacing with code that expects standard Go function signatures.
Type Parameters:
- T1: The type of the first parameter
- T2: The type of the second parameter
- T3: The type of the third parameter
- A: The return type
Parameters:
- f: A curried function
Returns:
- A Go function with context as the first parameter
Example:
// Curried function
createOrderCurried := func(userID int) func(productID int) func(quantity int) readerresult.ReaderResult[Order] {
return func(productID int) func(quantity int) readerresult.ReaderResult[Order] {
return func(quantity int) readerresult.ReaderResult[Order] {
return func(ctx context.Context) result.Result[Order] {
if ctx.Err() != nil {
return result.Error[Order](ctx.Err())
}
return result.Of(Order{UserID: userID, ProductID: productID, Quantity: quantity})
}
}
}
}
// Convert back to idiomatic Go function
createOrder := readerresult.Uncurry3(createOrderCurried)
// Use as a normal Go function
order, err := createOrder(t.Context(), 123, 456, 2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Order: User=%d, Product=%d, Qty=%d\n", order.UserID, order.ProductID, order.Quantity)
Types ¶
type Either ¶
Either represents a value that can be either a Left (error) or Right (success). This is specialized to use error as the Left type. This is an alias for either.Either[error, A].
Type Parameters:
- A: The type of the Right (success) value
Example:
success := either.Right[error, int](42) // Right(42)
failure := either.Left[int](errors.New("failed")) // Left(error)
type Endomorphism ¶
type Endomorphism[A any] = endomorphism.Endomorphism[A]
Endomorphism represents a function that transforms a value to the same type. This is an alias for endomorphism.Endomorphism[A].
Type Parameters:
- A: The type of the value
Signature:
type Endomorphism[A any] = func(A) A
Example:
increment := func(x int) int { return x + 1 }
// increment is an Endomorphism[int]
type IO ¶ added in v2.1.14
IO represents a side-effectful computation that produces a value of type A. This is an alias for io.IO[A].
IMPORTANT: IO operations have side effects (file I/O, network calls, etc.). Combining IO with ReaderResult makes sense because ReaderResult is already effectful due to its dependency on context.Context.
Type Parameters:
- A: The type of the value produced by the IO operation
Signature:
type IO[A any] = func() A
Example:
readConfig := func() Config {
// Side effect: read from file
data, _ := os.ReadFile("config.json")
return parseConfig(data)
}
// readConfig is an IO[Config]
type IOResult ¶ added in v2.1.14
IOResult represents a side-effectful computation that can fail with an error. This combines IO (side effects) with Result (error handling). This is an alias for ioresult.IOResult[A].
IMPORTANT: IOResult operations have side effects and can fail. Combining IOResult with ReaderResult makes sense because both are effectful.
Type Parameters:
- A: The type of the success value
Signature:
type IOResult[A any] = func() Result[A]
Example:
readConfig := func() result.Result[Config] {
// Side effect: read from file
data, err := os.ReadFile("config.json")
if err != nil {
return result.Error[Config](err)
}
return result.Of(parseConfig(data))
}
// readConfig is an IOResult[Config]
type Kleisli ¶
type Kleisli[A, B any] = reader.Reader[A, ReaderResult[B]]
Kleisli represents a function that takes a value of type A and returns a ReaderResult[B]. This is the fundamental building block for composing ReaderResult computations.
Type Parameters:
- A: The input type
- B: The output type (wrapped in ReaderResult)
Signature:
type Kleisli[A, B any] = func(A) ReaderResult[B]
Example:
getUserByID := func(id int) readerresult.ReaderResult[User] {
return func(ctx context.Context) result.Result[User] {
// Fetch user from database
return result.Of(User{ID: id, Name: "Alice"})
}
}
// getUserByID is a Kleisli[int, User]
func ApS ¶
func ApS[S1, S2, T any]( setter func(T) func(S1) S2, fa ReaderResult[T], ) Kleisli[ReaderResult[S1], S2]
ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently (using Applicative rather than Monad). This allows independent EFFECTFUL computations to be combined without one depending on the result of the other.
IMPORTANT: ApS is for EFFECTFUL FUNCTIONS that depend on context.Context. The ReaderResult parameter is effectful because it depends on context.Context.
Unlike Bind, which sequences operations, ApS can be used when operations are independent and can conceptually run in parallel.
Example:
type State struct {
UserID string
TenantID string
}
// These operations are independent and can be combined with ApS
getUserID := func(ctx context.Context) either.Either[error, string] {
return either.Right[error](ctx.Value("userID").(string))
}
getTenantID := func(ctx context.Context) either.Either[error, string] {
return either.Right[error](ctx.Value("tenantID").(string))
}
result := F.Pipe2(
readereither.Do(State{}),
readereither.ApS(
func(uid string) func(State) State {
return func(s State) State { s.UserID = uid; return s }
},
getUserID,
),
readereither.ApS(
func(tid string) func(State) State {
return func(s State) State { s.TenantID = tid; return s }
},
getTenantID,
),
)
func ApSL ¶
func ApSL[S, T any]( lens Lens[S, T], fa ReaderResult[T], ) Kleisli[ReaderResult[S], S]
ApSL is a variant of ApS that uses a lens to focus on a specific field in the state. Instead of providing a setter function, you provide a lens that knows how to get and set the field. This is more convenient when working with nested structures.
Parameters:
- lens: A lens that focuses on a field of type T within state S
- fa: A ReaderResult computation that produces a value of type T
Returns:
- A function that transforms ReaderResult[S] to ReaderResult[S] by setting the focused field
Example:
type Person struct {
Name string
Age int
}
ageLens := lens.MakeLens(
func(p Person) int { return p.Age },
func(p Person, a int) Person { p.Age = a; return p },
)
getAge := func(ctx context.Context) either.Either[error, int] {
return either.Right[error](30)
}
result := F.Pipe1(
readereither.Do(Person{Name: "Alice", Age: 25}),
readereither.ApSL(ageLens, getAge),
)
func Bind ¶
func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f Kleisli[S1, T], ) Kleisli[ReaderResult[S1], S2]
Bind attaches the result of an EFFECTFUL computation to a context [S1] to produce a context [S2]. This enables sequential composition where each step can depend on the results of previous steps and access the context.Context from the environment.
IMPORTANT: Bind is for EFFECTFUL FUNCTIONS that depend on context.Context. The function parameter takes state and returns a ReaderResult[T], which is effectful because it depends on context.Context (can be cancelled, has deadlines, carries values).
For PURE FUNCTIONS (side-effect free), use:
- BindResultK: For pure functions with errors (State -> (Value, error))
- Let: For pure functions without errors (State -> Value)
The setter function takes the result of the computation and returns a function that updates the context from S1 to S2.
Example:
type State struct {
UserID string
TenantID string
}
result := F.Pipe2(
readereither.Do(State{}),
readereither.Bind(
func(uid string) func(State) State {
return func(s State) State { s.UserID = uid; return s }
},
func(s State) readereither.ReaderResult[string] {
return func(ctx context.Context) either.Either[error, string] {
if uid, ok := ctx.Value("userID").(string); ok {
return either.Right[error](uid)
}
return either.Left[string](errors.New("no userID"))
}
},
),
readereither.Bind(
func(tid string) func(State) State {
return func(s State) State { s.TenantID = tid; return s }
},
func(s State) readereither.ReaderResult[string] {
// This can access s.UserID from the previous step
return func(ctx context.Context) either.Either[error, string] {
return either.Right[error]("tenant-" + s.UserID)
}
},
),
)
func BindL ¶
func BindL[S, T any]( lens Lens[S, T], f Kleisli[T, T], ) Kleisli[ReaderResult[S], S]
BindL is a variant of Bind that uses a lens to focus on a specific field in the state. It combines the lens-based field access with monadic composition for EFFECTFUL computations.
IMPORTANT: BindL is for EFFECTFUL FUNCTIONS that depend on context.Context. The function parameter returns a ReaderResult, which is effectful.
It allows you to: 1. Extract a field value using the lens 2. Use that value in an effectful computation that may fail 3. Update the field with the result
Parameters:
- lens: A lens that focuses on a field of type T within state S
- f: A function that takes the current field value and returns a ReaderResult computation
Returns:
- A function that transforms ReaderResult[S] to ReaderResult[S]
Example:
type Counter struct {
Value int
}
valueLens := lens.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
increment := func(v int) readereither.ReaderResult[int] {
return func(ctx context.Context) either.Either[error, int] {
if v >= 100 {
return either.Left[int](errors.New("value too large"))
}
return either.Right[error](v + 1)
}
}
result := F.Pipe1(
readereither.Of[error](Counter{Value: 42}),
readereither.BindL(valueLens, increment),
)
func Curry1 ¶
Curry1 converts a Go function with context and one parameter into a Kleisli arrow. This enables functional composition of single-parameter functions.
Type Parameters:
- T1: The type of the first parameter
- A: The return type of the function
Parameters:
- f: A function that takes a context and one parameter, returning a value and error
Returns:
- A Kleisli arrow that can be composed with other ReaderResult operations
Example:
// Idiomatic Go function
getUserByID := func(ctx context.Context, id int) (User, error) {
if ctx.Err() != nil {
return User{}, ctx.Err()
}
return User{ID: id, Name: "Alice"}, nil
}
// Convert to Kleisli for functional composition
getUserKleisli := readerresult.Curry1(getUserByID)
// Use in a pipeline
pipeline := F.Pipe1(
readerresult.Of(123),
readerresult.Chain(getUserKleisli),
)
result := pipeline(t.Context()) // Right(User{ID: 123, Name: "Alice"})
func FromPredicate ¶
FromPredicate creates a Kleisli arrow that validates a value using a predicate. If the predicate returns true, the value is wrapped in Right. If the predicate returns false, the onFalse function is called to generate an error.
Type Parameters:
- A: The type of the value to validate
Parameters:
- pred: The predicate function to test the value
- onFalse: The function to generate an error when the predicate fails
Returns:
- A Kleisli arrow that validates the value
Example:
isPositive := readerresult.FromPredicate(
func(x int) bool { return x > 0 },
func(x int) error { return fmt.Errorf("%d is not positive", x) },
)
result1 := isPositive(42)(t.Context()) // Right(42)
result2 := isPositive(-1)(t.Context()) // Left(error("-1 is not positive"))
func Let ¶
func Let[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) T, ) Kleisli[ReaderResult[S1], S2]
Let attaches the result of a PURE computation to a context [S1] to produce a context [S2].
IMPORTANT: Let is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context. The function parameter takes state and returns a value directly, with no errors or effects.
For EFFECTFUL FUNCTIONS (that need context.Context), use:
- Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])
For PURE FUNCTIONS with error handling, use:
- BindResultK: For pure functions with errors (State -> (Value, error))
func LetL ¶
func LetL[S, T any]( lens Lens[S, T], f Endomorphism[T], ) Kleisli[ReaderResult[S], S]
LetL is a variant of Let that uses a lens to focus on a specific field in the state. It applies a PURE transformation to the focused field without any effects.
IMPORTANT: LetL is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context. The function parameter is a pure endomorphism (T -> T) with no errors or effects.
Parameters:
- lens: A lens that focuses on a field of type T within state S
- f: A pure function that transforms the field value
Returns:
- A function that transforms ReaderResult[S] to ReaderResult[S]
Example:
type Counter struct {
Value int
}
valueLens := lens.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
double := func(v int) int { return v * 2 }
result := F.Pipe1(
readereither.Of[error](Counter{Value: 21}),
readereither.LetL(valueLens, double),
)
// result when executed will be Right(Counter{Value: 42})
func LetTo ¶
func LetTo[S1, S2, T any]( setter func(T) func(S1) S2, b T, ) Kleisli[ReaderResult[S1], S2]
LetTo attaches a constant value to a context [S1] to produce a context [S2]. This is a PURE operation (side-effect free) that simply sets a field to a constant value.
func LetToL ¶
func LetToL[S, T any]( lens Lens[S, T], b T, ) Kleisli[ReaderResult[S], S]
LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state. It sets the focused field to a constant value. This is a PURE operation (side-effect free).
Parameters:
- lens: A lens that focuses on a field of type T within state S
- b: The constant value to set
Returns:
- A function that transforms ReaderResult[S] to ReaderResult[S]
Example:
type Config struct {
Debug bool
Timeout int
}
debugLens := lens.MakeLens(
func(c Config) bool { return c.Debug },
func(c Config, d bool) Config { c.Debug = d; return c },
)
result := F.Pipe1(
readereither.Of[error](Config{Debug: true, Timeout: 30}),
readereither.LetToL(debugLens, false),
)
// result when executed will be Right(Config{Debug: false, Timeout: 30})
func OrElse ¶
OrElse recovers from a Left (error) by providing an alternative computation with access to context.Context. If the ReaderResult is Right, it returns the value unchanged. If the ReaderResult is Left, it applies the provided function to the error value, which returns a new ReaderResult that replaces the original.
This is useful for error recovery, fallback logic, or chaining alternative computations that need access to the context (for cancellation, deadlines, or values).
Example:
// Recover with context-aware fallback
recover := readerresult.OrElse(func(err error) readerresult.ReaderResult[int] {
if err.Error() == "not found" {
return func(ctx context.Context) result.Result[int] {
// Could check ctx.Err() here
return result.Of(42)
}
}
return readerresult.Left[int](err)
})
OrElse recovers from a Left (error) by providing an alternative computation. If the ReaderResult is Right, it returns the value unchanged. If the ReaderResult is Left, it applies the provided function to the error value, which returns a new ReaderResult that replaces the original.
This is useful for error recovery, fallback logic, or chaining alternative computations in the context of Reader computations with context.Context.
Example:
// Recover from specific errors with fallback values
recover := readerresult.OrElse(func(err error) readerresult.ReaderResult[int] {
if err.Error() == "not found" {
return readerresult.Of[int](0) // default value
}
return readerresult.Left[int](err) // propagate other errors
})
result := recover(readerresult.Left[int](errors.New("not found")))(ctx) // Right(0)
result := recover(readerresult.Of(42))(ctx) // Right(42) - unchanged
func SLog ¶
SLog creates a Kleisli arrow that logs a Result value at INFO level using the logger from the context. This is a convenience function that uses SLogWithCallback with default settings.
The Result value is logged and then returned unchanged, making this function suitable for use in functional pipelines for debugging or monitoring purposes.
This function logs both successful values and errors:
- Success values are logged with the key "value"
- Error values are logged with the key "error"
Type Parameters:
- A: The type of the success value in the Result
Parameters:
- message: The log message to display
Returns:
- A Kleisli arrow that takes a Result[A] and returns a ReaderResult[A] The returned ReaderResult, when executed with a context, logs the Result at INFO level and returns it unchanged
Example - Logging a successful computation:
ctx := t.Context()
// Simple value logging
res := result.Of(42)
logged := SLog[int]("Processing number")(res)(ctx)
// Logs: level=INFO msg="Processing number" value=42
// logged == result.Of(42)
Example - Logging in a pipeline:
type User struct {
ID int
Name string
}
fetchUser := func(id int) result.Result[User] {
return result.Of(User{ID: id, Name: "Alice"})
}
processUser := func(user User) result.Result[string] {
return result.Of(fmt.Sprintf("Processed: %s", user.Name))
}
ctx := t.Context()
// Log at each step
userResult := fetchUser(123)
logged1 := SLog[User]("Fetched user")(userResult)(ctx)
// Logs: level=INFO msg="Fetched user" value={ID:123 Name:Alice}
processed := result.Chain(processUser)(logged1)
logged2 := SLog[string]("Processed user")(processed)(ctx)
// Logs: level=INFO msg="Processed user" value="Processed: Alice"
Example - Logging errors:
err := errors.New("database connection failed")
errResult := result.Left[User](err)
logged := SLog[User]("Database operation")(errResult)(ctx)
// Logs: level=INFO msg="Database operation" error="database connection failed"
// logged still contains the error
Example - Using with context logger:
// Set up a custom logger in the context
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
ctx := logging.WithLogger(logger)(t.Context())
res := result.Of("important data")
logged := SLog[string]("Critical operation")(res)(ctx)
// Uses the logger from context to log the message
Note: The function uses logging.GetLoggerFromContext to retrieve the logger, which falls back to the global logger if no logger is found in the context.
func SLogWithCallback ¶
func SLogWithCallback[A any]( logLevel slog.Level, cb func(context.Context) *slog.Logger, message string) Kleisli[Result[A], A]
SLogWithCallback creates a Kleisli arrow that logs a Result value using a custom logger callback and log level. The Result value is logged and then returned unchanged, making this function suitable for use in functional pipelines.
This function logs both successful values and errors:
- Success values are logged with the key "value"
- Error values are logged with the key "error"
The logging is performed as a side effect while preserving the Result value, allowing it to be used in the middle of a computation pipeline without interrupting the flow.
Type Parameters:
- A: The type of the success value in the Result
Parameters:
- logLevel: The slog.Level at which to log (e.g., LevelInfo, LevelDebug, LevelError)
- cb: A callback function that retrieves a *slog.Logger from the context
- message: The log message to display
Returns:
- A Kleisli arrow that takes a Result[A] and returns a ReaderResult[A] The returned ReaderResult, when executed with a context, logs the Result and returns it unchanged
Example:
type User struct {
ID int
Name string
}
// Custom logger callback
getLogger := func(ctx context.Context) *slog.Logger {
return slog.Default()
}
// Create a logging function for debug level
logDebug := SLogWithCallback[User](slog.LevelDebug, getLogger, "User data")
// Use in a pipeline
ctx := t.Context()
user := result.Of(User{ID: 123, Name: "Alice"})
logged := logDebug(user)(ctx) // Logs: level=DEBUG msg="User data" value={ID:123 Name:Alice}
// logged still contains the User value
Example with error:
err := errors.New("user not found")
userResult := result.Left[User](err)
logged := logDebug(userResult)(ctx) // Logs: level=DEBUG msg="User data" error="user not found"
// logged still contains the error
func TailRec ¶
func TailRec[A, B any](f Kleisli[A, Trampoline[A, B]]) Kleisli[A, B]
TailRec implements tail-recursive computation for ReaderResult with context cancellation support.
TailRec takes a Kleisli function that returns Trampoline[A, B] and converts it into a stack-safe, tail-recursive computation. The function repeatedly applies the Kleisli until it produces a Land value.
The implementation includes a short-circuit mechanism that checks for context cancellation on each iteration. If the context is canceled (ctx.Err() != nil), the computation immediately returns a Left result containing the context's cause error, preventing unnecessary computation.
Type Parameters:
- A: The input type for the recursive step
- B: The final result type
Parameters:
- f: A Kleisli function that takes an A and returns a ReaderResult containing Trampoline[A, B]. When the result is Bounce(a), recursion continues with the new value 'a'. When the result is Land(b), recursion terminates with the final value 'b'.
Returns:
- A Kleisli function that performs the tail-recursive computation in a stack-safe manner.
Behavior:
- On each iteration, checks if the context has been canceled (short circuit)
- If canceled, returns result.Left[B](context.Cause(ctx))
- If the step returns Left[B](error), propagates the error
- If the step returns Right[A](Bounce(a)), continues recursion with new value 'a'
- If the step returns Right[A](Land(b)), terminates with success value 'b'
Example - Factorial computation with context:
type State struct {
n int
acc int
}
factorialStep := func(state State) ReaderResult[tailrec.Trampoline[State, int]] {
return func(ctx context.Context) result.Result[tailrec.Trampoline[State, int]] {
if state.n <= 0 {
return result.Of(tailrec.Land[State](state.acc))
}
return result.Of(tailrec.Bounce[int](State{state.n - 1, state.acc * state.n}))
}
}
factorial := TailRec(factorialStep)
result := factorial(State{5, 1})(ctx) // Returns result.Of(120)
Example - Context cancellation:
ctx, cancel := context.WithCancel(t.Context()) cancel() // Cancel immediately computation := TailRec(someStep) result := computation(initialValue)(ctx) // Returns result.Left[B](context.Cause(ctx)) without executing any steps
func TraverseArray ¶
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B]
TraverseArray transforms an array
func TraverseArrayWithIndex ¶
TraverseArrayWithIndex transforms an array
func WithContextK ¶
func WithContextK[A, B any](f Kleisli[A, B]) Kleisli[A, B]
WithContextK wraps a Kleisli arrow with context cancellation checking. This is a higher-order function that takes a Kleisli arrow and returns a new Kleisli arrow that checks for context cancellation before executing.
IMPORTANT: This function composes the Kleisli arrow with WithContext, ensuring that the resulting ReaderResult checks for cancellation before execution. This is particularly useful when building pipelines of Kleisli arrows where you want cancellation checking at each step.
Type Parameters:
- A: The input type of the Kleisli arrow
- B: The output type of the Kleisli arrow
Parameters:
- f: The Kleisli arrow to wrap with cancellation checking
Returns:
- A new Kleisli arrow that checks for cancellation before executing f
Example:
// Define a Kleisli arrow
processUser := func(id int) readerresult.ReaderResult[User] {
return func(ctx context.Context) result.Result[User] {
// Expensive database operation
return fetchUserFromDB(ctx, id)
}
}
// Wrap with cancellation checking
safeProcessUser := readerresult.WithContextK(processUser)
// Use in a pipeline
pipeline := F.Pipe1(
readerresult.Of(123),
readerresult.Chain(safeProcessUser),
)
// If context is cancelled, processUser never executes
ctx, cancel := context.WithCancel(context.Background())
cancel()
result := pipeline(ctx) // Left(context.Canceled)
Example with multiple steps:
getUserK := readerresult.WithContextK(func(id int) readerresult.ReaderResult[User] {
return func(ctx context.Context) result.Result[User] {
return fetchUser(ctx, id)
}
})
getOrdersK := readerresult.WithContextK(func(user User) readerresult.ReaderResult[[]Order] {
return func(ctx context.Context) result.Result[[]Order] {
return fetchOrders(ctx, user.ID)
}
})
// Each step checks for cancellation
pipeline := F.Pipe2(
readerresult.Of(123),
readerresult.Chain(getUserK),
readerresult.Chain(getOrdersK),
)
// If context is cancelled at any point, remaining steps don't execute
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
result := pipeline(ctx)
Use cases:
- Building cancellation-aware pipelines
- Ensuring each step in a chain respects cancellation
- Implementing timeout-aware multi-step operations
- Preventing cascading failures in long pipelines
type Lens ¶
Lens is an optic that focuses on a part of a data structure that is always present. This is an alias for lens.Lens[S, T].
Type Parameters:
- S: The source type
- T: The target type
Example:
// A lens that focuses on the Name field of a User
nameLens := lens.Lens[User, string]{...}
type Operator ¶
type Operator[A, B any] = Kleisli[ReaderResult[A], B]
Operator represents a function that transforms one ReaderResult into another. This is a specialized Kleisli where the input is itself a ReaderResult.
Type Parameters:
- A: The input ReaderResult's success type
- B: The output ReaderResult's success type
Signature:
type Operator[A, B any] = func(ReaderResult[A]) ReaderResult[B]
Example:
mapToString := readerresult.Map(func(x int) string {
return fmt.Sprintf("value: %d", x)
})
// mapToString is an Operator[int, string]
func BindTo ¶
func BindTo[S1, T any]( setter func(T) S1, ) Operator[T, S1]
BindTo initializes a new state [S1] from a value [T]
func Chain ¶
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B]
Chain creates an operator that sequences two ReaderResult computations. This is the curried version where the Kleisli function is provided first.
Type Parameters:
- A: The success type of the first ReaderResult
- B: The success type of the second ReaderResult
Parameters:
- f: The Kleisli function that takes the success value and returns a new ReaderResult
Returns:
- An Operator that sequences the computations
Example:
toUpper := readerresult.Chain(func(s string) readerresult.ReaderResult[string] {
return readerresult.Of(strings.ToUpper(s))
})
rr := readerresult.Of("hello")
result := toUpper(rr)(t.Context()) // Right("HELLO")
func ChainFirst ¶
func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A]
ChainFirst creates an operator that sequences two ReaderResult computations, executing the second for its side effects but returning the value from the first. This is the curried version where the Kleisli function is provided first.
IMPORTANT: Combining with IO operations makes sense because ReaderResult already has side effects due to context.Context (cancellation, deadlines, values). ChainFirst executes both computations for their side effects, which is natural when working with effectful computations.
Type Parameters:
- A: The success type of the first ReaderResult (returned value)
- B: The success type of the second ReaderResult (discarded)
Parameters:
- f: The Kleisli function that takes the success value and returns a second ReaderResult
Returns:
- An Operator that executes both computations but returns the first's value
Example:
logValue := readerresult.ChainFirst(func(x int) readerresult.ReaderResult[string] {
return func(ctx context.Context) result.Result[string] {
fmt.Printf("Value: %d\n", x)
return result.Of("logged")
}
})
result := logValue(readerresult.Of(42))(t.Context()) // Prints "Value: 42", returns Right(42)
func ChainFirstIOK ¶ added in v2.1.14
ChainFirstIOK creates an operator that sequences a ReaderResult with an IO computation for its side effects, but returns the original value. This is the curried version.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This function executes the IO operation for its side effects while preserving the original value.
Type Parameters:
- A: The success type of the ReaderResult (returned value)
- B: The success type of the IO computation (discarded)
Parameters:
- f: The IO Kleisli function for side effects
Returns:
- An Operator that executes both but returns the original value
Example:
logIO := readerresult.ChainFirstIOK(func(x int) func() string {
return func() string {
fmt.Printf("Processing: %d\n", x)
return "logged"
}
})
result := logIO(readerresult.Of(42))(t.Context()) // Prints "Processing: 42", returns Right(42)
func ChainFirstLeft ¶ added in v2.1.14
ChainFirstLeft executes a computation on the Left (error) value for its side effects, but preserves the original error. This is useful for error logging or metrics.
Type Parameters:
- A: The success type of the ReaderResult
- B: The success type of the error handler (discarded)
Parameters:
- f: The Kleisli function that handles the error
Returns:
- An Operator that executes the error handler but preserves the original error
Example:
logError := readerresult.ChainFirstLeft[int](func(err error) readerresult.ReaderResult[string] {
return func(ctx context.Context) result.Result[string] {
fmt.Printf("Error occurred: %v\n", err)
return result.Of("logged")
}
})
rr := readerresult.Left[int](errors.New("failed"))
result := logError(rr)(t.Context()) // Prints "Error occurred: failed", returns Left(error("failed"))
func ChainFirstLeftIOK ¶ added in v2.1.14
ChainFirstLeftIOK executes an IO computation on the Left (error) value for its side effects, but preserves the original error. This is useful for error logging or metrics with IO operations.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This allows error handling with side effects like logging to external systems.
Type Parameters:
- A: The success type of the ReaderResult
- B: The success type of the IO error handler (discarded)
Parameters:
- f: The IO Kleisli function that handles the error
Returns:
- An Operator that executes the IO error handler but preserves the original error
Example:
logErrorIO := readerresult.ChainFirstLeftIOK[int](func(err error) func() string {
return func() string {
fmt.Printf("Error: %v\n", err)
return "logged"
}
})
rr := readerresult.Left[int](errors.New("failed"))
result := logErrorIO(rr)(t.Context()) // Prints "Error: failed", returns Left(error("failed"))
func ChainIOEitherK ¶ added in v2.1.14
ChainIOEitherK creates an operator that sequences a ReaderResult with an IOResult computation.
IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context, and IOResult has side effects from IO operations with error handling. This combination provides context-aware error handling with IO operations.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the IOResult computation
Parameters:
- f: The IOResult Kleisli function
Returns:
- An Operator that sequences the computations
Example:
ioResultOp := readerresult.ChainIOEitherK(func(x int) func() result.Result[string] {
return func() result.Result[string] {
if x > 0 {
return result.Of(fmt.Sprintf("positive: %d", x))
}
return result.Error[string](errors.New("not positive"))
}
})
result := ioResultOp(readerresult.Of(42))(t.Context()) // Right("positive: 42")
func ChainIOK ¶ added in v2.1.14
ChainIOK creates an operator that sequences a ReaderResult with an IO computation. This is the curried version where the IO Kleisli function is provided first.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context (cancellation, deadlines, values), and IO has side effects from IO operations. This combination allows context-aware error handling with IO operations.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the IO computation
Parameters:
- f: The IO Kleisli function that takes the success value and returns an IO computation
Returns:
- An Operator that sequences the computations
Example:
logIO := readerresult.ChainIOK(func(x int) func() string {
return func() string {
fmt.Printf("Value: %d\n", x)
return "logged"
}
})
result := logIO(readerresult.Of(42))(t.Context()) // Prints "Value: 42", returns Right("logged")
func ChainIOResultK ¶ added in v2.1.14
ChainIOResultK is an alias for ChainIOEitherK. It creates an operator that sequences a ReaderResult with an IOResult computation.
IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations. This provides context-aware error handling with IO operations.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the IOResult computation
Parameters:
- f: The IOResult Kleisli function
Returns:
- An Operator that sequences the computations
Example:
ioResultOp := readerresult.ChainIOResultK(func(x int) func() result.Result[string] {
return func() result.Result[string] {
return result.Of(fmt.Sprintf("value: %d", x))
}
})
result := ioResultOp(readerresult.Of(42))(t.Context()) // Right("value: 42")
func ChainTo ¶
func ChainTo[A, B any](b ReaderResult[B]) Operator[A, B]
ChainTo creates an operator that sequences two ReaderResult computations where the second ignores the first's success value. This is the curried version where the second ReaderResult is provided first, returning a function that can be applied to any first ReaderResult.
IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, ChainTo WILL execute the first ReaderResult to allow any side effects to occur, then discard the success result and execute the second ReaderResult with the same context. If the first computation fails, the error is returned immediately without executing the second computation.
Type Parameters:
- A: The success type of the first ReaderResult (will be discarded if successful)
- B: The success type of the second ReaderResult
Parameters:
- b: The second ReaderResult to execute after the first succeeds
Returns:
- An Operator that executes the first ReaderResult, then b if successful
Example:
logEnd := func(ctx context.Context) result.Result[string] {
fmt.Println("ending")
return result.Of("done")
}
thenLogEnd := readerresult.ChainTo[int, string](logEnd)
logStart := func(ctx context.Context) result.Result[int] {
fmt.Println("starting")
return result.Of(1)
}
pipeline := thenLogEnd(logStart)
result := pipeline(t.Context()) // Prints "starting" then "ending", returns Right("done")
Example - In a functional pipeline:
step1 := func(ctx context.Context) result.Result[int] {
fmt.Println("step 1")
return result.Of(1)
}
step2 := func(ctx context.Context) result.Result[string] {
fmt.Println("step 2")
return result.Of("complete")
}
pipeline := F.Pipe1(
step1,
readerresult.ChainTo[int, string](step2),
)
output := pipeline(t.Context()) // Prints "step 1" then "step 2", returns Right("complete")
func Contramap ¶ added in v2.1.6
Contramap changes the context during the execution of a ReaderResult. This is the contravariant functor operation that transforms the input context.
See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor
Contramap is an alias for Local and is useful for adapting a ReaderResult to work with a modified context by providing a function that transforms the context.
Type Parameters:
- A: The success type (unchanged)
Parameters:
- f: Function to transform the context, returning a new context and CancelFunc
Returns:
- An Operator that takes a ReaderResult[A] and returns a ReaderResult[A]
func FilterOrElse ¶ added in v2.1.0
FilterOrElse filters a ReaderResult value based on a predicate. This is a convenience wrapper around readerresult.FilterOrElse that fixes the context type to context.Context.
If the predicate returns true for the Right value, it passes through unchanged. If the predicate returns false, it transforms the Right value into a Left (error) using onFalse. Left values are passed through unchanged.
Parameters:
- pred: A predicate function that tests the Right value
- onFalse: A function that converts the failing value into an error
Returns:
- An Operator that filters ReaderResult values based on the predicate
Example:
// Validate that a number is positive
isPositive := N.MoreThan(0)
onNegative := func(n int) error { return fmt.Errorf("%d is not positive", n) }
filter := readerresult.FilterOrElse(isPositive, onNegative)
result := filter(readerresult.Right(42))(t.Context())
func Flap ¶
func Flap[B, A any](a A) Operator[func(A) B, B]
Flap creates an operator that applies a value to a ReaderResult containing a function. This is the curried version where the value is provided first. Flap is the reverse of Ap - instead of applying a function to a value, it applies a value to a function.
Type Parameters:
- B: The output type
- A: The input type
Parameters:
- a: The value to apply to the function
Returns:
- An Operator that applies the value to a ReaderResult containing a function
Example:
applyTo42 := readerresult.Flap[string](42)
fabRR := readerresult.Of(func(x int) string {
return fmt.Sprintf("value: %d", x)
})
result := applyTo42(fabRR)(t.Context()) // Right("value: 42")
func Local ¶ added in v2.1.6
Local changes the context during the execution of a ReaderResult. This allows you to modify the context before passing it to a ReaderResult computation.
See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor
Local is particularly useful for:
- Adding values to the context
- Setting timeouts or deadlines
- Modifying context metadata
The function f returns both a new context and a CancelFunc. The CancelFunc is automatically called (via defer) after the ReaderResult computation completes to ensure proper cleanup.
Type Parameters:
- A: The result type (unchanged)
Parameters:
- f: Function to transform the context, returning a new context and CancelFunc
Returns:
- An Operator that takes a ReaderResult[A] and returns a ReaderResult[A]
func Map ¶
func Map[A, B any](f func(A) B) Operator[A, B]
Map creates an operator that applies a function to the success value of a ReaderResult. This is the curried version where the function is provided first.
Type Parameters:
- A: The input success type
- B: The output success type
Parameters:
- f: The function to apply to the success value
Returns:
- An Operator that applies the function to a ReaderResult
Example:
toString := readerresult.Map(func(x int) string {
return fmt.Sprintf("value: %d", x)
})
rr := readerresult.Of(42)
result := toString(rr)(t.Context()) // Right("value: 42")
func MapTo ¶
func MapTo[A, B any](b B) Operator[A, B]
MapTo creates an operator that executes a ReaderResult computation, discards its success value, and returns a constant value. This is the curried version where the constant value is provided first, returning a function that can be applied to any ReaderResult.
IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, MapTo WILL execute the input ReaderResult to allow any side effects to occur, then discard the success result and return the constant value. If the computation fails, the error is preserved.
Type Parameters:
- A: The success type of the input ReaderResult (will be discarded if successful)
- B: The type of the constant value to return on success
Parameters:
- b: The constant value to return on success
Returns:
- An Operator that executes a ReaderResult[A], preserves errors, but replaces success with b
Example:
logStep := func(ctx context.Context) result.Result[int] {
fmt.Println("step executed")
return result.Of(42)
}
toDone := readerresult.MapTo[int, string]("done")
pipeline := toDone(logStep)
result := pipeline(t.Context()) // Prints "step executed", returns Right("done")
Example - In a functional pipeline:
step1 := func(ctx context.Context) result.Result[int] {
fmt.Println("processing")
return result.Of(1)
}
pipeline := F.Pipe1(
step1,
readerresult.MapTo[int, string]("complete"),
)
output := pipeline(t.Context()) // Prints "processing", returns Right("complete")
func Promap ¶ added in v2.1.6
func Promap[A, B any](f func(context.Context) (context.Context, context.CancelFunc), g func(A) B) Operator[A, B]
Promap is the profunctor map operation that transforms both the input and output of a context-based ReaderResult. It applies f to the input context (contravariantly) and g to the output value (covariantly).
See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor
This operation allows you to:
- Modify the context before passing it to the ReaderResult (via f)
- Transform the success value after the computation completes (via g)
The function f returns both a new context and a CancelFunc that should be called to release resources. The error type is fixed as error and remains unchanged through the transformation.
Type Parameters:
- A: The original success type produced by the ReaderResult
- B: The new output success type
Parameters:
- f: Function to transform the input context (contravariant)
- g: Function to transform the output success value from A to B (covariant)
Returns:
- An Operator that takes a ReaderResult[A] and returns a ReaderResult[B]
func TapIOK ¶ added in v2.1.14
TapIOK is an alias for ChainFirstIOK. It creates an operator that sequences a ReaderResult with an IO computation for its side effects, but returns the original value.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. Tap executes the IO operation for its side effects while preserving the original value.
Type Parameters:
- A: The success type of the ReaderResult (returned value)
- B: The success type of the IO computation (discarded)
Parameters:
- f: The IO Kleisli function for side effects
Returns:
- An Operator that executes both but returns the original value
Example:
tapLog := readerresult.TapIOK(func(x int) func() string {
return func() string {
fmt.Printf("Tapping: %d\n", x)
return "logged"
}
})
result := tapLog(readerresult.Of(42))(t.Context()) // Prints "Tapping: 42", returns Right(42)
func TapLeftIOK ¶ added in v2.1.14
TapLeftIOK is an alias for ChainFirstLeftIOK. It executes an IO computation on the Left (error) value for its side effects, but preserves the original error.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. Tap allows error handling with side effects while preserving the error.
Type Parameters:
- A: The success type of the ReaderResult
- B: The success type of the IO error handler (discarded)
Parameters:
- f: The IO Kleisli function that handles the error
Returns:
- An Operator that executes the IO error handler but preserves the original error
Example:
tapErrorIO := readerresult.TapLeftIOK[int](func(err error) func() string {
return func() string {
fmt.Printf("Tapping error: %v\n", err)
return "logged"
}
})
rr := readerresult.Left[int](errors.New("failed"))
result := tapErrorIO(rr)(t.Context()) // Prints "Tapping error: failed", returns Left(error("failed"))
type Option ¶
Option represents an optional value that may or may not be present. This is an alias for option.Option[A].
Type Parameters:
- A: The type of the value that may be present
Example:
opt := option.Some(42) // Option[int] with value none := option.None[int]() // Option[int] without value
type Predicate ¶ added in v2.1.0
Predicate represents a function that tests a value and returns a boolean. This is an alias for predicate.Predicate[A].
Type Parameters:
- A: The type of the value to test
Signature:
type Predicate[A any] = func(A) bool
Example:
isPositive := func(x int) bool { return x > 0 }
// isPositive is a Predicate[int]
type Prism ¶
Prism is an optic that focuses on a part of a data structure that may or may not be present. This is an alias for prism.Prism[S, T].
Type Parameters:
- S: The source type
- T: The target type
Example:
// A prism that extracts an int from a string if it's a valid number
intPrism := prism.Prism[string, int]{...}
type Reader ¶
Reader represents a computation that depends on an environment R to produce a value A. This is an alias for reader.Reader[R, A].
Type Parameters:
- R: The type of the environment/context
- A: The type of the produced value
Example:
type Config struct { Port int }
getPort := func(cfg Config) int { return cfg.Port }
// getPort is a Reader[Config, int]
type ReaderResult ¶
type ReaderResult[A any] = readereither.ReaderEither[context.Context, error, A]
ReaderResult is a specialization of the Reader monad for the typical Go scenario. It represents an effectful computation that:
- Depends on context.Context (for cancellation, deadlines, values)
- Can fail with an error
- Produces a value of type A on success
IMPORTANT: This is an EFFECTFUL type because context.Context is effectful. The computation's behavior can change based on context state (cancelled, timed out, etc.).
Type Parameters:
- A: The type of the success value
Signature:
type ReaderResult[A any] = func(context.Context) Result[A]
Example:
getUserByID := func(ctx context.Context) result.Result[User] {
if ctx.Err() != nil {
return result.Error[User](ctx.Err())
}
// Fetch user from database
return result.Of(User{ID: 123, Name: "Alice"})
}
// getUserByID is a ReaderResult[User]
func Ask ¶
Ask returns a ReaderResult that provides access to the context.Context. This allows you to read the context within a ReaderResult computation.
Returns:
- A ReaderResult that returns the context.Context as its success value
Example:
rr := readerresult.Ask()
result := rr(t.Context()) // Right(t.Context())
// Use in a chain to access context
pipeline := F.Pipe2(
readerresult.Ask(),
readerresult.Chain(func(ctx context.Context) readerresult.ReaderResult[string] {
if deadline, ok := ctx.Deadline(); ok {
return readerresult.Of(fmt.Sprintf("deadline: %v", deadline))
}
return readerresult.Of("no deadline")
}),
)
func Curry0 ¶
Curry0 converts a Go function with context and no additional parameters into a ReaderResult. This is useful for adapting context-aware functions to the ReaderResult monad.
Type Parameters:
- A: The return type of the function
Parameters:
- f: A function that takes a context and returns a value and error
Returns:
- A ReaderResult that wraps the function
Example:
// Idiomatic Go function
getConfig := func(ctx context.Context) (Config, error) {
// Check context cancellation
if ctx.Err() != nil {
return Config{}, ctx.Err()
}
return Config{Value: 42}, nil
}
// Convert to ReaderResult for functional composition
configRR := readerresult.Curry0(getConfig)
result := configRR(t.Context()) // Right(Config{Value: 42})
func Do ¶
func Do[S any]( empty S, ) ReaderResult[S]
Do creates an empty context of type [S] to be used with the Bind operation. This is the starting point for do-notation style composition.
Example:
type State struct {
UserID string
TenantID string
}
result := readereither.Do(State{})
func FromEither ¶
func FromEither[A any](e Either[A]) ReaderResult[A]
FromEither lifts an Either value into a ReaderResult. The Either value is returned as-is, ignoring the context.
Type Parameters:
- A: The success type of the Either value
Parameters:
- e: The Either value to lift
Returns:
- A ReaderResult that returns the Either value
Example:
either := result.Of[error](42) rr := readerresult.FromEither(either) result := rr(t.Context()) // Right(42)
func FromIO ¶ added in v2.1.14
FromIO lifts a pure IO computation into a ReaderResult. The IO computation is executed when the ReaderResult is run, ignoring the context.
IMPORTANT: While IO represents a side-effectful computation, combining it with ReaderResult makes sense because ReaderResult already has side effects due to its context.Context dependency. The context can be cancelled, has deadlines, and carries values - all side effects. Therefore, adding IO operations (which also have side effects) is a natural fit.
Type Parameters:
- A: The success type of the IO computation
Parameters:
- t: The IO computation to lift
Returns:
- A ReaderResult that executes the IO computation and wraps the result in Right
Example:
ioOp := func() int { return 42 }
rr := readerresult.FromIO(ioOp)
result := rr(t.Context()) // Right(42)
func FromIOResult ¶ added in v2.1.14
FromIOResult lifts an IOResult computation into a ReaderResult. The IOResult computation is executed when the ReaderResult is run, ignoring the context.
IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context (cancellation, deadlines, values), and IOResult has side effects from IO operations. This combination allows you to work with context-aware error handling while performing IO operations.
Type Parameters:
- A: The success type of the IOResult computation
Parameters:
- t: The IOResult computation to lift
Returns:
- A ReaderResult that executes the IOResult computation
Example:
ioResultOp := func() result.Result[int] {
return result.Of(42)
}
rr := readerresult.FromIOResult(ioResultOp)
result := rr(t.Context()) // Right(42)
func FromReader ¶
FromReader lifts a Reader computation into a ReaderResult. The Reader computation receives the context and its result is wrapped in Right.
Type Parameters:
- A: The success type of the Reader computation
Parameters:
- r: The Reader computation to lift
Returns:
- A ReaderResult that executes the Reader and wraps the result in Right
Example:
reader := func(ctx context.Context) int {
return 42
}
rr := readerresult.FromReader(reader)
result := rr(t.Context()) // Right(42)
func Left ¶
Left creates a ReaderResult that always returns a Left (error) value.
Type Parameters:
- A: The success type (not used, as this always returns an error)
Parameters:
- l: The error value to return
Returns:
- A ReaderResult that always returns Left(l)
Example:
rr := readerresult.Left[int](errors.New("failed"))
result := rr(t.Context()) // Left(error("failed"))
func MonadAp ¶
func MonadAp[A, B any](fab ReaderResult[func(A) B], fa ReaderResult[A]) ReaderResult[B]
MonadAp applies a ReaderResult containing a function to a ReaderResult containing a value. This is the monadic version that takes both ReaderResults as parameters.
Type Parameters:
- A: The input type
- B: The output type
Parameters:
- fab: The ReaderResult containing the function
- fa: The ReaderResult containing the value
Returns:
- A ReaderResult with the function applied to the value
Example:
fabRR := readerresult.Of(func(x int) string {
return fmt.Sprintf("value: %d", x)
})
faRR := readerresult.Of(42)
result := readerresult.MonadAp(fabRR, faRR)(t.Context()) // Right("value: 42")
func MonadChain ¶
func MonadChain[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[B]
MonadChain sequences two ReaderResult computations, passing the success value from the first to the second. This is the monadic version that takes both the ReaderResult and the Kleisli function as parameters.
Type Parameters:
- A: The success type of the first ReaderResult
- B: The success type of the second ReaderResult
Parameters:
- ma: The first ReaderResult to execute
- f: The Kleisli function that takes the success value and returns a new ReaderResult
Returns:
- A ReaderResult that sequences both computations
Example:
rr := readerresult.Of(42)
chained := readerresult.MonadChain(rr, func(x int) readerresult.ReaderResult[string] {
return readerresult.Of(fmt.Sprintf("value: %d", x))
})
result := chained(t.Context()) // Right("value: 42")
func MonadChainEitherK ¶
func MonadChainEitherK[A, B any](ma ReaderResult[A], f func(A) Either[B]) ReaderResult[B]
MonadChainEitherK sequences a ReaderResult with a function that returns an Either. This is the monadic version that takes both the ReaderResult and the function as parameters.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the output Either
Parameters:
- ma: The ReaderResult to execute
- f: The function that takes the success value and returns an Either
Returns:
- A ReaderResult that sequences both computations
Example:
rr := readerresult.Of(42)
chained := readerresult.MonadChainEitherK(rr, func(x int) result.Result[string] {
if x > 0 {
return result.Of(fmt.Sprintf("positive: %d", x))
}
return result.Error[string](errors.New("not positive"))
})
result := chained(t.Context()) // Right("positive: 42")
func MonadChainFirst ¶
func MonadChainFirst[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[A]
MonadChainFirst sequences two ReaderResult computations, executing the second for its side effects but returning the value from the first. This is the monadic version that takes both the ReaderResult and the Kleisli function as parameters.
IMPORTANT: Combining with IO operations makes sense because ReaderResult already has side effects due to context.Context (cancellation, deadlines, values). ChainFirst executes both computations for their side effects, which is natural when working with effectful computations.
Type Parameters:
- A: The success type of the first ReaderResult (returned value)
- B: The success type of the second ReaderResult (discarded)
Parameters:
- ma: The first ReaderResult to execute
- f: The Kleisli function that takes the success value and returns a second ReaderResult
Returns:
- A ReaderResult that executes both computations but returns the first's value
Example:
rr := readerresult.Of(42)
withLogging := readerresult.MonadChainFirst(rr, func(x int) readerresult.ReaderResult[string] {
return func(ctx context.Context) result.Result[string] {
fmt.Printf("Value: %d\n", x)
return result.Of("logged")
}
})
result := withLogging(t.Context()) // Prints "Value: 42", returns Right(42)
func MonadChainFirstIOK ¶ added in v2.1.14
MonadChainFirstIOK sequences a ReaderResult with an IO computation for its side effects, but returns the original value. This is the monadic version.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This function executes the IO operation for its side effects (like logging or metrics) while preserving the original value, which is natural when working with effectful computations.
Type Parameters:
- A: The success type of the ReaderResult (returned value)
- B: The success type of the IO computation (discarded)
Parameters:
- ma: The ReaderResult to execute
- f: The IO Kleisli function for side effects
Returns:
- A ReaderResult that executes both but returns the original value
Example:
rr := readerresult.Of(42)
withLog := readerresult.MonadChainFirstIOK(rr, func(x int) func() string {
return func() string {
fmt.Printf("Processing: %d\n", x)
return "logged"
}
})
result := withLog(t.Context()) // Prints "Processing: 42", returns Right(42)
func MonadChainIOK ¶ added in v2.1.14
MonadChainIOK sequences a ReaderResult with an IO computation, lifting the IO into ReaderResult. This is the monadic version that takes both the ReaderResult and the IO Kleisli function as parameters.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context (cancellation, deadlines, values), and IO has side effects from IO operations. This combination allows context-aware error handling with IO operations.
Type Parameters:
- A: The success type of the input ReaderResult
- B: The success type of the IO computation
Parameters:
- ma: The ReaderResult to execute
- f: The IO Kleisli function that takes the success value and returns an IO computation
Returns:
- A ReaderResult that sequences both computations
Example:
rr := readerresult.Of(42)
withIO := readerresult.MonadChainIOK(rr, func(x int) func() string {
return func() string {
fmt.Printf("Value: %d\n", x)
return "done"
}
})
result := withIO(t.Context()) // Prints "Value: 42", returns Right("done")
func MonadChainTo ¶
func MonadChainTo[A, B any](ma ReaderResult[A], b ReaderResult[B]) ReaderResult[B]
MonadChainTo sequences two ReaderResult computations where the second ignores the first's success value. This is the monadic version that takes both ReaderResults as parameters.
IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, MonadChainTo WILL execute the first ReaderResult to allow any side effects to occur, then discard the success result and execute the second ReaderResult with the same context. If the first computation fails, the error is returned immediately without executing the second computation.
Type Parameters:
- A: The success type of the first ReaderResult (will be discarded if successful)
- B: The success type of the second ReaderResult
Parameters:
- ma: The first ReaderResult to execute (side effects will occur, success value discarded)
- b: The second ReaderResult to execute if ma succeeds
Returns:
- A ReaderResult that executes ma, then b if ma succeeds, returning b's result
Example:
logStart := func(ctx context.Context) result.Result[int] {
fmt.Println("starting")
return result.Of(1)
}
logEnd := func(ctx context.Context) result.Result[string] {
fmt.Println("ending")
return result.Of("done")
}
r := readerresult.MonadChainTo(logStart, logEnd)
result := r(t.Context()) // Prints "starting" then "ending", returns Right("done")
func MonadFlap ¶
func MonadFlap[B, A any](fab ReaderResult[func(A) B], a A) ReaderResult[B]
MonadFlap applies a value to a ReaderResult containing a function. This is the monadic version that takes both the ReaderResult and the value as parameters. Flap is the reverse of Ap - instead of applying a function to a value, it applies a value to a function.
Type Parameters:
- B: The output type
- A: The input type
Parameters:
- fab: The ReaderResult containing the function
- a: The value to apply to the function
Returns:
- A ReaderResult with the value applied to the function
Example:
fabRR := readerresult.Of(func(x int) string {
return fmt.Sprintf("value: %d", x)
})
result := readerresult.MonadFlap(fabRR, 42)(t.Context()) // Right("value: 42")
func MonadMap ¶
func MonadMap[A, B any](fa ReaderResult[A], f func(A) B) ReaderResult[B]
MonadMap applies a function to the success value of a ReaderResult. This is the monadic version that takes both the ReaderResult and the function as parameters.
Type Parameters:
- A: The input success type
- B: The output success type
Parameters:
- fa: The ReaderResult to map over
- f: The function to apply to the success value
Returns:
- A ReaderResult with the function applied to the success value
Example:
rr := readerresult.Of(42)
mapped := readerresult.MonadMap(rr, func(x int) string {
return fmt.Sprintf("value: %d", x)
})
result := mapped(t.Context()) // Right("value: 42")
func MonadMapTo ¶
func MonadMapTo[A, B any](ma ReaderResult[A], b B) ReaderResult[B]
MonadMapTo executes a ReaderResult computation, discards its success value, and returns a constant value. This is the monadic version that takes both the ReaderResult and the constant value as parameters.
IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, MonadMapTo WILL execute the original ReaderResult to allow any side effects to occur, then discard the success result and return the constant value. If the original computation fails, the error is preserved.
Type Parameters:
- A: The success type of the first ReaderResult (will be discarded if successful)
- B: The type of the constant value to return on success
Parameters:
- ma: The ReaderResult to execute (side effects will occur, success value discarded)
- b: The constant value to return if ma succeeds
Returns:
- A ReaderResult that executes ma, preserves errors, but replaces success values with b
Example:
type Config struct { Counter int }
increment := func(ctx context.Context) result.Result[int] {
// Side effect: log the operation
fmt.Println("incrementing")
return result.Of(5)
}
r := readerresult.MonadMapTo(increment, "done")
result := r(t.Context()) // Prints "incrementing", returns Right("done")
func MonadTapIOK ¶ added in v2.1.14
MonadTapIOK is an alias for MonadChainFirstIOK. It sequences a ReaderResult with an IO computation for its side effects, but returns the original value.
IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. Tap executes the IO operation for its side effects while preserving the original value.
Type Parameters:
- A: The success type of the ReaderResult (returned value)
- B: The success type of the IO computation (discarded)
Parameters:
- ma: The ReaderResult to execute
- f: The IO Kleisli function for side effects
Returns:
- A ReaderResult that executes both but returns the original value
Example:
rr := readerresult.Of(42)
withLog := readerresult.MonadTapIOK(rr, func(x int) func() string {
return func() string {
fmt.Printf("Tapping: %d\n", x)
return "logged"
}
})
result := withLog(t.Context()) // Prints "Tapping: 42", returns Right(42)
func Of ¶
func Of[A any](a A) ReaderResult[A]
Of creates a ReaderResult that always returns a Right (success) value. This is the pointed functor constructor.
Type Parameters:
- A: The success type
Parameters:
- a: The success value to return
Returns:
- A ReaderResult that always returns Right(a)
Example:
rr := readerresult.Of(42) result := rr(t.Context()) // Right(42)
func Retrying ¶
func Retrying[A any]( policy R.RetryPolicy, action Kleisli[R.RetryStatus, A], check Predicate[Result[A]], ) ReaderResult[A]
Retrying retries a ReaderResult computation according to a retry policy with context awareness.
This function implements a retry mechanism for operations that depend on a context.Context and can fail (Result). It respects context cancellation, meaning that if the context is cancelled during retry delays, the operation will stop immediately and return the cancellation error.
The retry loop will continue until one of the following occurs:
- The action succeeds and the check function returns false (no retry needed)
- The retry policy returns None (retry limit reached)
- The check function returns false (indicating success or a non-retryable failure)
- The context is cancelled (returns context.Canceled or context.DeadlineExceeded)
Type Parameters:
- A: The type of the success value
Parameters:
policy: A RetryPolicy that determines when and how long to wait between retries. The policy receives a RetryStatus on each iteration and returns an optional delay. If it returns None, retrying stops. Common policies include LimitRetries, ExponentialBackoff, and CapDelay from the retry package.
action: A Kleisli arrow that takes a RetryStatus and returns a ReaderResult[A]. This function is called on each retry attempt and receives information about the current retry state (iteration number, cumulative delay, etc.). The action depends on a context.Context and produces a Result[A].
check: A predicate function that examines the Result[A] and returns true if the operation should be retried, or false if it should stop. This allows you to distinguish between retryable failures (e.g., network timeouts) and permanent failures (e.g., invalid input).
Returns:
- A ReaderResult[A] that, when executed with a context, will perform the retry logic with context cancellation support and return the final result.
Example:
// Create a retry policy: exponential backoff with a cap, limited to 5 retries
policy := M.Concat(
retry.LimitRetries(5),
retry.CapDelay(10*time.Second, retry.ExponentialBackoff(100*time.Millisecond)),
)(retry.Monoid)
// Action that fetches data
fetchData := func(status retry.RetryStatus) ReaderResult[string] {
return func(ctx context.Context) Result[string] {
if ctx.Err() != nil {
return result.Left[string](ctx.Err())
}
if status.IterNumber < 3 {
return result.Left[string](fmt.Errorf("temporary error"))
}
return result.Of("success")
}
}
// Check function: retry on any error except context cancellation
shouldRetry := func(r Result[string]) bool {
return result.IsLeft(r) && !errors.Is(result.GetLeft(r), context.Canceled)
}
// Create the retrying computation
retryingFetch := Retrying(policy, fetchData, shouldRetry)
// Execute with a cancellable context
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
finalResult := retryingFetch(ctx)
func Right ¶
func Right[A any](r A) ReaderResult[A]
Right creates a ReaderResult that always returns a Right (success) value. This is an alias for Of.
Type Parameters:
- A: The success type
Parameters:
- r: The success value to return
Returns:
- A ReaderResult that always returns Right(r)
Example:
rr := readerresult.Right(42) result := rr(t.Context()) // Right(42)
func SequenceArray ¶
func SequenceArray[A any](ma []ReaderResult[A]) ReaderResult[[]A]
SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceT1 ¶
SequenceT1 converts a single ReaderResult into a ReaderResult containing a 1-tuple. This is primarily useful for consistency in generic code or when you need to wrap a single value in a tuple structure.
Type Parameters:
- A: The type of the value in the ReaderResult
Parameters:
- a: The ReaderResult to wrap in a tuple
Returns:
- A ReaderResult containing a Tuple1 with the value from the input
Example:
rr := readerresult.Of(42)
tupled := readerresult.SequenceT1(rr)
result := tupled(context.Background())
// result is Right(Tuple1{F1: 42})
func SequenceT2 ¶
SequenceT2 combines two ReaderResults into a single ReaderResult containing a 2-tuple. Both ReaderResults are executed with the same context. If either fails, the entire sequence fails.
Type Parameters:
- A: The type of the first value
- B: The type of the second value
Parameters:
- a: The first ReaderResult
- b: The second ReaderResult
Returns:
- A ReaderResult containing a Tuple2 with both values
Example:
getName := readerresult.Of("Alice")
getAge := readerresult.Of(30)
combined := readerresult.SequenceT2(getName, getAge)
result := combined(context.Background())
// result is Right(Tuple2{F1: "Alice", F2: 30})
Example with error:
getName := readerresult.Of("Alice")
getAge := readerresult.Left[int](errors.New("age not found"))
combined := readerresult.SequenceT2(getName, getAge)
result := combined(context.Background())
// result is Left(error("age not found"))
func SequenceT3 ¶
func SequenceT3[A, B, C any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C]) ReaderResult[tuple.Tuple3[A, B, C]]
SequenceT3 combines three ReaderResults into a single ReaderResult containing a 3-tuple. All ReaderResults are executed sequentially with the same context. If any fails, the entire sequence fails immediately.
Type Parameters:
- A: The type of the first value
- B: The type of the second value
- C: The type of the third value
Parameters:
- a: The first ReaderResult
- b: The second ReaderResult
- c: The third ReaderResult
Returns:
- A ReaderResult containing a Tuple3 with all three values
Example:
getUserID := readerresult.Of(123)
getUserName := readerresult.Of("Alice")
getUserEmail := readerresult.Of("alice@example.com")
combined := readerresult.SequenceT3(getUserID, getUserName, getUserEmail)
result := combined(context.Background())
// result is Right(Tuple3{F1: 123, F2: "Alice", F3: "alice@example.com"})
Example with context-aware operations:
fetchUser := func(ctx context.Context) result.Result[string] {
if ctx.Err() != nil {
return result.Error[string](ctx.Err())
}
return result.Of("Alice")
}
fetchAge := func(ctx context.Context) result.Result[int] {
return result.Of(30)
}
fetchCity := func(ctx context.Context) result.Result[string] {
return result.Of("NYC")
}
combined := readerresult.SequenceT3(fetchUser, fetchAge, fetchCity)
result := combined(context.Background())
// result is Right(Tuple3{F1: "Alice", F2: 30, F3: "NYC"})
func SequenceT4 ¶
func SequenceT4[A, B, C, D any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], d ReaderResult[D]) ReaderResult[tuple.Tuple4[A, B, C, D]]
SequenceT4 combines four ReaderResults into a single ReaderResult containing a 4-tuple. All ReaderResults are executed sequentially with the same context. If any fails, the entire sequence fails immediately without executing the remaining ones.
Type Parameters:
- A: The type of the first value
- B: The type of the second value
- C: The type of the third value
- D: The type of the fourth value
Parameters:
- a: The first ReaderResult
- b: The second ReaderResult
- c: The third ReaderResult
- d: The fourth ReaderResult
Returns:
- A ReaderResult containing a Tuple4 with all four values
Example:
getID := readerresult.Of(123)
getName := readerresult.Of("Alice")
getEmail := readerresult.Of("alice@example.com")
getAge := readerresult.Of(30)
combined := readerresult.SequenceT4(getID, getName, getEmail, getAge)
result := combined(context.Background())
// result is Right(Tuple4{F1: 123, F2: "Alice", F3: "alice@example.com", F4: 30})
Example with early failure:
getID := readerresult.Of(123)
getName := readerresult.Left[string](errors.New("name not found"))
getEmail := readerresult.Of("alice@example.com") // Not executed
getAge := readerresult.Of(30) // Not executed
combined := readerresult.SequenceT4(getID, getName, getEmail, getAge)
result := combined(context.Background())
// result is Left(error("name not found"))
// getEmail and getAge are never executed due to early failure
Example building a complex structure:
type UserProfile struct {
ID int
Name string
Email string
Age int
}
fetchUserData := readerresult.SequenceT4(
fetchUserID(userID),
fetchUserName(userID),
fetchUserEmail(userID),
fetchUserAge(userID),
)
buildProfile := readerresult.Map(func(t tuple.Tuple4[int, string, string, int]) UserProfile {
return UserProfile{
ID: t.F1,
Name: t.F2,
Email: t.F3,
Age: t.F4,
}
})
userProfile := F.Pipe1(fetchUserData, buildProfile)
result := userProfile(context.Background())
func WithContext ¶
func WithContext[A any](ma ReaderResult[A]) ReaderResult[A]
WithContext wraps an existing ReaderResult and performs a context check for cancellation before delegating to the wrapped computation. This provides early cancellation detection, allowing computations to fail fast when the context has been cancelled or has exceeded its deadline.
IMPORTANT: This function checks for context cancellation BEFORE executing the wrapped ReaderResult. If the context is already cancelled or has exceeded its deadline, the computation returns immediately with the cancellation error without executing the wrapped ReaderResult.
The function uses context.Cause(ctx) to extract the cancellation reason, which may be:
- context.Canceled: The context was explicitly cancelled
- context.DeadlineExceeded: The context's deadline was exceeded
- A custom error: If the context was cancelled with a cause (Go 1.20+)
Type Parameters:
- A: The success type of the ReaderResult
Parameters:
- ma: The ReaderResult to wrap with cancellation checking
Returns:
- A ReaderResult that checks for cancellation before executing ma
Example:
// Create a long-running computation
longComputation := func(ctx context.Context) result.Result[int] {
time.Sleep(5 * time.Second)
return result.Of(42)
}
// Wrap with cancellation check
safeLongComputation := readerresult.WithContext(longComputation)
// Cancel the context before execution
ctx, cancel := context.WithCancel(context.Background())
cancel()
// The computation returns immediately with cancellation error
result := safeLongComputation(ctx)
// result is Left(context.Canceled) - longComputation never executes
Example with timeout:
fetchData := func(ctx context.Context) result.Result[string] {
// Simulate slow operation
time.Sleep(2 * time.Second)
return result.Of("data")
}
safeFetch := readerresult.WithContext(fetchData)
// Context with 1 second timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
time.Sleep(1500 * time.Millisecond) // Wait for timeout
result := safeFetch(ctx)
// result is Left(context.DeadlineExceeded) - fetchData never executes
Use cases:
- Wrapping expensive computations to enable early cancellation
- Preventing unnecessary work when context is already cancelled
- Implementing timeout-aware operations
- Building cancellation-aware pipelines
type Result ¶
Result represents a computation that can either succeed with a value or fail with an error. This is an alias for result.Result[A], which is equivalent to Either[error, A].
Type Parameters:
- A: The type of the success value
Example:
success := result.Of[error](42) // Right(42)
failure := result.Error[int](errors.New("failed")) // Left(error)
type Trampoline ¶
type Trampoline[A, B any] = tailrec.Trampoline[A, B]
Trampoline represents a computation that can be executed in a stack-safe manner using tail recursion elimination. This is an alias for tailrec.Trampoline[A, B].
Type Parameters:
- A: The input type
- B: The output type
Example:
// A tail-recursive factorial computation
factorial := tailrec.Trampoline[int, int]{...}
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error
|
package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error |