Documentation
¶
Overview ¶
Package readerresult provides a ReaderResult monad specialized for context.Context.
A ReaderResult[A] represents an effectful computation that:
- Takes a context.Context as input
- May fail with an error (Result aspect, which is Either[error, A])
- Returns a value of type A on success
The type is defined as: ReaderResult[A any] = func(context.Context) (A, error)
This is equivalent to Reader[context.Context, Result[A]] or Reader[context.Context, Either[error, A]], but specialized to always use context.Context as the environment type.
Effectful Computations with Context ¶
ReaderResult is particularly well-suited for representing effectful computations in Go. An effectful computation is one that:
- Performs side effects (I/O, network calls, database operations, etc.)
- May fail with an error
- Requires contextual information (cancellation, deadlines, request-scoped values)
By using context.Context as the fixed environment type, ReaderResult[A] provides:
- Cancellation propagation - operations can be cancelled via context
- Deadline/timeout handling - operations respect context deadlines
- Request-scoped values - access to request metadata, trace IDs, etc.
- Functional composition - chain effectful operations while maintaining context
- Error handling - explicit error propagation through the Result type
This pattern is idiomatic in Go, where functions performing I/O conventionally accept context.Context as their first parameter: func(ctx context.Context, ...) (Result, error). ReaderResult preserves this convention while enabling functional composition.
Example of an effectful computation:
// An effectful operation that queries a database
func fetchUser(ctx context.Context, id int) (User, error) {
// ctx provides cancellation, deadlines, and request context
row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
var user User
err := row.Scan(&user.ID, &user.Name)
return user, err
}
// Lift into ReaderResult for functional composition
getUser := readerresult.Curry1(fetchUser)
// Compose multiple effectful operations
pipeline := F.Pipe2(
getUser(42), // ReaderResult[User]
readerresult.Chain(func(user User) readerresult.ReaderResult[[]Post] {
return getPosts(user.ID) // Another effectful operation
}),
)
// Execute with a context (e.g., from an HTTP request)
ctx := r.Context() // HTTP request context
posts, err := pipeline(ctx)
Use Cases ¶
ReaderResult is particularly useful for:
- Effectful computations with context - operations that perform I/O and need cancellation/deadlines
- Functional error handling - compose operations that depend on context and may error
- Testing - easily mock context-dependent operations
- HTTP handlers - chain request processing operations with proper context propagation
Composition ¶
ReaderResult provides several ways to compose computations:
- Map - transform successful values
- Chain (FlatMap) - sequence dependent operations
- Ap - combine independent computations
- Do-notation - imperative-style composition with Bind
Do-Notation Example ¶
type State struct {
User User
Posts []Post
}
result := F.Pipe2(
readerresult.Do(State{}),
readerresult.Bind(
func(user User) func(State) State {
return func(s State) State { s.User = user; return s }
},
func(s State) readerresult.ReaderResult[User] {
return getUser(42)
},
),
readerresult.Bind(
func(posts []Post) func(State) State {
return func(s State) State { s.Posts = posts; return s }
},
func(s State) readerresult.ReaderResult[[]Post] {
return getPosts(s.User.ID)
},
),
)
Currying Functions with Context ¶
The Curry functions enable partial application of function parameters while deferring the context.Context parameter until execution time.
When you curry a function like func(context.Context, T1, T2) (A, error), the context.Context becomes the last argument to be applied, even though it appears first in the original function signature. This is intentional and follows Go's context-first convention while enabling functional composition patterns.
Why context.Context is the last curried argument:
- In Go, context conventionally comes first: func(ctx context.Context, params...) (Result, error)
- In curried form: Curry2(f)(param1)(param2) returns ReaderResult[A]
- The ReaderResult is then applied to ctx: Curry2(f)(param1)(param2)(ctx)
- This allows partial application of business parameters before providing the context
Example with database operations:
// Database operations following Go conventions (context first)
func fetchUser(ctx context.Context, db *sql.DB, id int) (User, error) {
row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
var user User
err := row.Scan(&user.ID, &user.Name)
return user, err
}
func updateUser(ctx context.Context, db *sql.DB, id int, name string) (User, error) {
_, err := db.ExecContext(ctx, "UPDATE users SET name = ? WHERE id = ?", name, id)
if err != nil {
return User{}, err
}
return fetchUser(ctx, db, id)
}
// Curry these into composable operations
getUser := readerresult.Curry2(fetchUser)
updateUserName := readerresult.Curry3(updateUser)
// Compose operations with partial application
pipeline := F.Pipe2(
getUser(db)(42), // ReaderResult[User] - db and id applied, waiting for ctx
readerresult.Chain(func(user User) readerresult.ReaderResult[User] {
newName := user.Name + " (updated)"
return updateUserName(db)(user.ID)(newName) // Waiting for ctx
}),
)
// Execute by providing the context
ctx := context.Background()
updatedUser, err := pipeline(ctx)
The key insight is that currying creates a chain where:
- Business parameters are applied first: getUser(db)(42)
- This returns a ReaderResult[User] that waits for the context
- Multiple operations can be composed before providing the context
- Finally, the context is provided to execute everything: pipeline(ctx)
This pattern is particularly useful for:
- Creating reusable operation pipelines independent of specific contexts
- Testing with different contexts (with timeouts, cancellation, etc.)
- Composing operations that share the same context
- Deferring context creation until execution time
Error Handling ¶
ReaderResult provides several functions for error handling:
- Left/Right - create failed/successful values
- GetOrElse - provide a default value for errors
- OrElse - recover from errors with an alternative computation
- Fold - handle both success and failure cases
- ChainEitherK - lift result.Result computations into ReaderResult
Relationship to Other Monads ¶
ReaderResult is related to several other monads in this library:
- Reader[context.Context, A] - ReaderResult without error handling
- Result[A] (Either[error, A]) - error handling without context dependency
- IOResult[A] - similar to ReaderResult but without explicit context parameter
- ReaderIOResult[R, A] - generic version that allows custom environment type R
Performance Note ¶
ReaderResult is a zero-cost abstraction - it compiles to a simple function type with no runtime overhead beyond the underlying computation.
Index ¶
- func ApResultS[S1, S2, T any](setter func(T) func(S1) S2) func(T, error) Operator[S1, S2]
- func BindToEither[S1, T any](setter func(T) S1) func(Result[T]) ReaderResult[S1]
- func BindToReader[S1, T any](setter func(T) S1) func(Reader[context.Context, T]) ReaderResult[S1]
- func BindToResult[S1, T any](setter func(T) S1) func(T, error) ReaderResult[S1]
- func ChainOptionK[A, B any](onNone Lazy[error]) func(option.Kleisli[A, B]) Operator[A, B]
- func Curry1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderResult[A]
- func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) func(T2) ReaderResult[A]
- func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderResult[A]
- func Fold[A, B any](onLeft reader.Kleisli[context.Context, error, B], ...) func(ReaderResult[A]) Reader[context.Context, B]
- func From0[A any](f func(context.Context) (A, error)) func() ReaderResult[A]
- func From1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) 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 GetOrElse[A any](onLeft reader.Kleisli[context.Context, error, A]) func(ReaderResult[A]) Reader[context.Context, A]
- func Read[A any](ctx context.Context) func(ReaderResult[A]) (A, error)
- func ToReaderResult[A any](r ReaderResult[A]) RS.ReaderResult[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 func(T1) ReaderResult[A]) func(context.Context, T1) (A, error)
- func Uncurry2[T1, T2, A any](f func(T1) func(T2) ReaderResult[A]) func(context.Context, T1, T2) (A, error)
- func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderResult[A]) func(context.Context, T1, T2, T3) (A, error)
- type Either
- type Endomorphism
- type Kleisli
- func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) Kleisli[A, A]
- func SequenceReader[R, A any](ma ReaderResult[Reader[R, A]]) Kleisli[R, A]
- 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 WithCloser[B any, A io.Closer](onCreate Lazy[ReaderResult[A]]) Kleisli[Kleisli[A, B], B]
- func WithContextK[A, B any](f Kleisli[A, B]) Kleisli[A, B]
- func WithResource[B, A, ANY any](onCreate Lazy[ReaderResult[A]], onRelease Kleisli[A, ANY]) Kleisli[Kleisli[A, B], B]
- type Lazy
- type Lens
- type Monoid
- type Operator
- func Alt[A any](second Lazy[ReaderResult[A]]) Operator[A, A]
- func Ap[B, A any](fa ReaderResult[A]) Operator[func(A) B, B]
- func ApEitherS[S1, S2, T any](setter func(T) func(S1) S2, fa Result[T]) Operator[S1, S2]
- func ApReaderS[S1, S2, T any](setter func(T) func(S1) S2, fa Reader[context.Context, T]) Operator[S1, S2]
- func ApS[S1, S2, T any](setter func(T) func(S1) S2, fa ReaderResult[T]) Operator[S1, S2]
- func ApSL[S, T any](lens Lens[S, T], fa ReaderResult[T]) Operator[S, S]
- func BiMap[A, B any](f Endomorphism[error], g func(A) B) Operator[A, B]
- func Bind[S1, S2, T any](setter func(T) func(S1) S2, f Kleisli[S1, T]) Operator[S1, S2]
- func BindEitherK[S1, S2, T any](setter func(T) func(S1) S2, f RES.Kleisli[S1, T]) Operator[S1, S2]
- func BindL[S, T any](lens Lens[S, T], f Kleisli[T, T]) Operator[S, S]
- func BindReaderK[S1, S2, T any](setter func(T) func(S1) S2, f reader.Kleisli[context.Context, S1, T]) Operator[S1, S2]
- func BindResultK[S1, S2, T any](setter func(T) func(S1) S2, f result.Kleisli[S1, T]) Operator[S1, S2]
- 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 ChainEitherK[A, B any](f RES.Kleisli[A, B]) Operator[A, B]
- func ChainReaderK[A, B any](f result.Kleisli[A, B]) Operator[A, B]
- func Flap[B, A any](a A) Operator[func(A) B, B]
- func Let[S1, S2, T any](setter func(T) func(S1) S2, f func(S1) T) Operator[S1, S2]
- func LetL[S, T any](lens Lens[S, T], f Endomorphism[T]) Operator[S, S]
- func LetTo[S1, S2, T any](setter func(T) func(S1) S2, b T) Operator[S1, S2]
- func LetToL[S, T any](lens Lens[S, T], b T) Operator[S, S]
- 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 MapLeft[A any](f Endomorphism[error]) Operator[A, A]
- func OrElse[A any](onLeft Kleisli[error, A]) Operator[A, A]
- func OrLeft[A any](onLeft reader.Kleisli[context.Context, error, error]) Operator[A, A]
- func WithDeadline[A any](deadline time.Time) Operator[A, A]
- func WithTimeout[A any](timeout time.Duration) Operator[A, A]
- type Option
- type Prism
- type Reader
- type ReaderResult
- func Ask() ReaderResult[context.Context]
- func Asks[A any](r Reader[context.Context, A]) ReaderResult[A]
- func Bracket[A, B, ANY any](acquire Lazy[ReaderResult[A]], use Kleisli[A, B], ...) ReaderResult[B]
- func Curry0[A any](f func(context.Context) (A, error)) ReaderResult[A]
- func Do[S any](empty S) ReaderResult[S]
- func Flatten[A any](mma ReaderResult[ReaderResult[A]]) ReaderResult[A]
- func FromEither[A any](e Result[A]) ReaderResult[A]
- func FromReader[A any](r Reader[context.Context, A]) ReaderResult[A]
- func FromReaderResult[A any](r RS.ReaderResult[A]) ReaderResult[A]
- func FromResult[A any](a A, err error) ReaderResult[A]
- func Left[A any](err error) ReaderResult[A]
- func LeftReader[A, R any](l Reader[context.Context, error]) ReaderResult[A]
- func MonadAlt[A any](first ReaderResult[A], second Lazy[ReaderResult[A]]) ReaderResult[A]
- func MonadAp[B, A any](fab ReaderResult[func(A) B], fa ReaderResult[A]) ReaderResult[B]
- func MonadBiMap[A, B any](fa ReaderResult[A], f Endomorphism[error], g func(A) B) 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 RES.Kleisli[A, B]) ReaderResult[B]
- func MonadChainReaderK[A, B any](ma ReaderResult[A], f result.Kleisli[A, B]) ReaderResult[B]
- func MonadFlap[A, B 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 MonadMapLeft[A any](fa ReaderResult[A], f Endomorphism[error]) ReaderResult[A]
- func MonadTraverseArray[A, B any](as []A, f Kleisli[A, B]) ReaderResult[[]B]
- 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](a A) ReaderResult[A]
- func RightReader[A any](rdr Reader[context.Context, A]) ReaderResult[A]
- func SequenceArray[A any](ma []ReaderResult[A]) ReaderResult[[]A]
- func SequenceT1[A any](a ReaderResult[A]) ReaderResult[T.Tuple1[A]]
- func SequenceT2[A, B any](a ReaderResult[A], b ReaderResult[B]) ReaderResult[T.Tuple2[A, B]]
- func SequenceT3[A, B, C any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C]) ReaderResult[T.Tuple3[A, B, C]]
- func SequenceT4[A, B, C, D any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], d ReaderResult[D]) ReaderResult[T.Tuple4[A, B, C, D]]
- func WithContext[A any](ma ReaderResult[A]) ReaderResult[A]
- type Result
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApResultS ¶
ApResultS applies an idiomatic Go tuple (value, error) in applicative style.
IMPORTANT: The (Value, error) tuple is PURE (side-effect free) - it doesn't depend on context. Use this for pure Go error-handling results.
Example ¶
ExampleApResultS demonstrates applying an idiomatic Go tuple (value, error) in applicative style, combining it with the current state in a do-notation chain.
// Simulate an idiomatic Go function result
value, err := "Hello, World!", error(nil)
result := F.Pipe1(
Do(MessageState{}),
func(rr ReaderResult[MessageState]) ReaderResult[MessageState] {
return F.Pipe1(
rr,
ApResultS(
func(msg string) Endomorphism[MessageState] {
return func(s MessageState) MessageState {
s.Message = msg
return s
}
},
)(value, err),
)
},
)
state, resultErr := result(context.Background())
fmt.Printf("Message: %s, Error: %v\n", state.Message, resultErr)
Output: Message: Hello, World!, Error: <nil>
func BindToEither ¶
func BindToEither[ S1, T any]( setter func(T) S1, ) func(Result[T]) ReaderResult[S1]
BindToEither converts a Result (Either) into a ReaderResult and binds it to create an initial state.
IMPORTANT: Result[T] is PURE (side-effect free) - it doesn't depend on context. Use this to lift pure error-handling values into the ReaderResult context.
Example ¶
ExampleBindToEither demonstrates converting a Result (Either) into a ReaderResult and binding it to create an initial state.
// A Result value
resultValue := RES.Of(100)
result := F.Pipe1(
resultValue,
BindToEither(func(v int) ValueState {
return ValueState{Value: v}
}),
)
state, err := result(context.Background())
fmt.Printf("Value: %d, Error: %v\n", state.Value, err)
Output: Value: 100, Error: <nil>
func BindToReader ¶
func BindToReader[ S1, T any]( setter func(T) S1, ) func(Reader[context.Context, T]) ReaderResult[S1]
BindToReader converts a Reader computation into a ReaderResult and binds it to create an initial state.
IMPORTANT: Reader[Context, T] is EFFECTFUL because it depends on context.Context. Use this when you have a context-dependent computation that cannot fail.
Example ¶
ExampleBindToReader demonstrates converting a Reader computation into a ReaderResult and binding it to create an initial state.
// A Reader that extracts request ID from context
getRequestID := func(ctx context.Context) string {
if val := ctx.Value("requestID"); val != nil {
return val.(string)
}
return "unknown"
}
result := F.Pipe1(
getRequestID,
BindToReader(func(id string) RequestState {
return RequestState{RequestID: id}
}),
)
ctx := context.WithValue(context.Background(), "requestID", "req-123")
state, err := result(ctx)
fmt.Printf("Request ID: %s, Error: %v\n", state.RequestID, err)
Output: Request ID: req-123, Error: <nil>
func BindToResult ¶
BindToResult converts an idiomatic Go tuple (value, error) into a ReaderResult and binds it to create an initial state.
IMPORTANT: The (Value, error) tuple is PURE (side-effect free) - it doesn't depend on context. Use this to lift pure Go error-handling results into the ReaderResult context.
Example ¶
ExampleBindToResult demonstrates converting an idiomatic Go tuple (value, error) into a ReaderResult and binding it to create an initial state.
// Simulate an idiomatic Go function result
value, err := "success", error(nil)
result := F.Pipe1(
BindToResult(func(v string) ResultState {
return ResultState{Result: v}
}),
func(f func(string, error) ReaderResult[ResultState]) ReaderResult[ResultState] {
return f(value, err)
},
)
state, resultErr := result(context.Background())
fmt.Printf("Result: %s, Error: %v\n", state.Result, resultErr)
Output: Result: success, Error: <nil>
func ChainOptionK ¶
func Curry1 ¶
Curry1 converts a function with one parameter into a curried ReaderResult-returning function.
The context.Context parameter is handled by the ReaderResult, allowing you to partially apply the business parameter before providing the context.
Type Parameters:
- T1: The first parameter type
- A: The return value type
Parameters:
- f: A function that takes (context.Context, T1) and returns (A, error)
Returns:
- A curried function that takes T1 and returns ReaderResult[A]
Example:
func getUser(ctx context.Context, id int) (User, error) {
// ... implementation
return user, nil
}
getUserRR := readerresult.Curry1(getUser)
rr := getUserRR(42) // Partially applied
user, err := rr(ctx) // Execute with context
func Curry2 ¶
func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) func(T2) ReaderResult[A]
Curry2 converts a function with two parameters into a curried ReaderResult-returning function.
The context.Context parameter is handled by the ReaderResult, allowing you to partially apply the business parameters before providing the context.
Type Parameters:
- T1: The first parameter type
- T2: The second parameter type
- A: The return value type
Parameters:
- f: A function that takes (context.Context, T1, T2) and returns (A, error)
Returns:
- A curried function that takes T1, then T2, and returns ReaderResult[A]
Example:
func updateUser(ctx context.Context, id int, name string) (User, error) {
// ... implementation
return user, nil
}
updateUserRR := readerresult.Curry2(updateUser)
rr := updateUserRR(42)("Alice") // Partially applied
user, err := rr(ctx) // Execute with context
func Curry3 ¶
func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderResult[A]
Curry3 converts a function with three parameters into a curried ReaderResult-returning function.
The context.Context parameter is handled by the ReaderResult, allowing you to partially apply the business parameters before providing the context.
Type Parameters:
- T1: The first parameter type
- T2: The second parameter type
- T3: The third parameter type
- A: The return value type
Parameters:
- f: A function that takes (context.Context, T1, T2, T3) and returns (A, error)
Returns:
- A curried function that takes T1, then T2, then T3, and returns ReaderResult[A]
Example:
func createPost(ctx context.Context, userID int, title string, body string) (Post, error) {
// ... implementation
return post, nil
}
createPostRR := readerresult.Curry3(createPost)
rr := createPostRR(42)("Title")("Body") // Partially applied
post, err := rr(ctx) // Execute with context
func From0 ¶
From0 converts a context-taking function into a thunk that returns a ReaderResult.
Unlike Curry0 which returns a ReaderResult directly, From0 returns a function that when called produces a ReaderResult. This is useful for lazy evaluation.
Type Parameters:
- A: The return value type
Parameters:
- f: A function that takes context.Context and returns (A, error)
Returns:
- A thunk (function with no parameters) that returns ReaderResult[A]
Example:
func getConfig(ctx context.Context) (Config, error) {
return Config{Port: 8080}, nil
}
thunk := readerresult.From0(getConfig)
rr := thunk() // Create the ReaderResult
config, err := rr(ctx) // Execute it
func From1 ¶
From1 converts a function with one parameter into an uncurried ReaderResult-returning function.
Unlike Curry1 which returns a curried function, From1 returns a function that takes all parameters at once (except context). This is more convenient for direct calls.
Type Parameters:
- T1: The parameter type
- A: The return value type
Parameters:
- f: A function that takes (context.Context, T1) and returns (A, error)
Returns:
- A function that takes T1 and returns ReaderResult[A]
Example:
func getUser(ctx context.Context, id int) (User, error) {
return User{ID: id}, nil
}
getUserRR := readerresult.From1(getUser)
rr := getUserRR(42)
user, err := rr(ctx)
func From2 ¶
From2 converts a function with two parameters into an uncurried ReaderResult-returning function.
Type Parameters:
- T1: The first parameter type
- T2: The second parameter type
- A: The return value type
Parameters:
- f: A function that takes (context.Context, T1, T2) and returns (A, error)
Returns:
- A function that takes (T1, T2) and returns ReaderResult[A]
Example:
func updateUser(ctx context.Context, id int, name string) (User, error) {
return User{ID: id, Name: name}, nil
}
updateUserRR := readerresult.From2(updateUser)
rr := updateUserRR(42, "Alice")
user, err := rr(ctx)
func From3 ¶
func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderResult[A]
From3 converts a function with three parameters into an uncurried ReaderResult-returning function.
Type Parameters:
- T1: The first parameter type
- T2: The second parameter type
- T3: The third parameter type
- A: The return value type
Parameters:
- f: A function that takes (context.Context, T1, T2, T3) and returns (A, error)
Returns:
- A function that takes (T1, T2, T3) and returns ReaderResult[A]
Example:
func createPost(ctx context.Context, userID int, title, body string) (Post, error) {
return Post{UserID: userID, Title: title, Body: body}, nil
}
createPostRR := readerresult.From3(createPost)
rr := createPostRR(42, "Title", "Body")
post, err := rr(ctx)
func Read ¶
Read executes a ReaderResult by providing it with a context.Context.
This is the elimination form for ReaderResult - it "runs" the computation by supplying the required environment, producing a (value, error) tuple.
Type Parameters:
- A: The result value type
Parameters:
- ctx: The context.Context environment to provide
Returns:
- A function that executes a ReaderResult[A] and returns (A, error)
func ToReaderResult ¶
func ToReaderResult[A any](r ReaderResult[A]) RS.ReaderResult[A]
func TraverseReader ¶
TraverseReader combines SequenceReader with a Kleisli arrow transformation.
It takes a Reader Kleisli arrow (a function from A to Reader[R, B]) and returns a function that transforms ReaderResult[A] into a Kleisli arrow from context.Context and R to B. This is useful for transforming values within a ReaderResult while introducing an additional Reader dependency.
Type Parameters:
- R: The Reader's environment type
- A: The input type
- B: The output type
Parameters:
- f: A Kleisli arrow that transforms A into Reader[R, B]
Returns:
- A function that transforms ReaderResult[A] into a Kleisli arrow from context.Context and R to B
Example:
type Config struct {
Multiplier int
}
// A Kleisli arrow that uses Config to transform int to string
formatWithConfig := func(n int) reader.Reader[Config, string] {
return func(cfg Config) string {
return fmt.Sprintf("Value: %d", n * cfg.Multiplier)
}
}
// Create a ReaderResult[int]
getValue := readerresult.Of[int](42)
// Traverse: transform the int using the Reader Kleisli arrow
traversed := readerresult.TraverseReader[Config](formatWithConfig)(getValue)
result, err := traversed(ctx)(Config{Multiplier: 2})
// result == "Value: 84"
func Uncurry1 ¶
Uncurry1 converts a curried ReaderResult function back to a standard Go function.
This is the inverse of Curry1, useful when you need to call curried functions in a traditional Go style.
Type Parameters:
- T1: The parameter type
- A: The return value type
Parameters:
- f: A curried function that takes T1 and returns ReaderResult[A]
Returns:
- A function that takes (context.Context, T1) and returns (A, error)
Example:
curriedFn := func(id int) readerresult.ReaderResult[User] { ... }
normalFn := readerresult.Uncurry1(curriedFn)
user, err := normalFn(ctx, 42)
func Uncurry2 ¶
func Uncurry2[T1, T2, A any](f func(T1) func(T2) ReaderResult[A]) func(context.Context, T1, T2) (A, error)
Uncurry2 converts a curried ReaderResult function with two parameters back to a standard Go function.
This is the inverse of Curry2.
Type Parameters:
- T1: The first parameter type
- T2: The second parameter type
- A: The return value type
Parameters:
- f: A curried function that takes T1, then T2, and returns ReaderResult[A]
Returns:
- A function that takes (context.Context, T1, T2) and returns (A, error)
func Uncurry3 ¶
func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderResult[A]) func(context.Context, T1, T2, T3) (A, error)
Uncurry3 converts a curried ReaderResult function with three parameters back to a standard Go function.
This is the inverse of Curry3.
Type Parameters:
- T1: The first parameter type
- T2: The second parameter type
- T3: The third parameter type
- A: The return value type
Parameters:
- f: A curried function that takes T1, then T2, then T3, and returns ReaderResult[A]
Returns:
- A function that takes (context.Context, T1, T2, T3) and returns (A, error)
Types ¶
type Endomorphism ¶
type Endomorphism[A any] = endomorphism.Endomorphism[A]
Endomorphism represents a function from type A to type A.
type Kleisli ¶
type Kleisli[A, B any] = Reader[A, ReaderResult[B]]
Kleisli represents a Kleisli arrow from A to ReaderResult[B]. It's a function that takes a value of type A and returns a computation that produces B or an error in a context.
func FromPredicate ¶
func SequenceReader ¶
func SequenceReader[R, A any](ma ReaderResult[Reader[R, A]]) Kleisli[R, A]
SequenceReader swaps the order of nested environment parameters when the inner type is a Reader.
It transforms ReaderResult[Reader[R, A]] into a function that takes context.Context first, then R, and returns (A, error). This is useful when you have a ReaderResult computation that produces a Reader, and you want to sequence the environment dependencies.
Type Parameters:
- R: The inner Reader's environment type
- A: The final result type
Parameters:
- ma: A ReaderResult that produces a Reader[R, A]
Returns:
- A Kleisli arrow that takes context.Context and R to produce (A, error)
Example:
type Config struct {
DatabaseURL string
}
// Returns a ReaderResult that produces a Reader
getDBReader := func(ctx context.Context) (reader.Reader[Config, string], error) {
return func(cfg Config) string {
return cfg.DatabaseURL
}, nil
}
// Sequence the environments: context.Context -> Config -> string
sequenced := readerresult.SequenceReader[Config, string](getDBReader)
result, err := sequenced(ctx)(config)
func TraverseArray ¶
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B]
TraverseArray applies a ReaderResult-returning function to each element of an array, collecting the results. If any element fails, the entire operation fails with the first error.
Example:
parseUser := func(id int) readerresult.ReaderResult[DB, User] { ... }
ids := []int{1, 2, 3}
result := readerresult.TraverseArray[DB](parseUser)(ids)
// result(db) returns ([]User, nil) with all users or (nil, error) on first error
func TraverseArrayWithIndex ¶
TraverseArrayWithIndex is like TraverseArray but the function also receives the element's index. This is useful when the transformation depends on the position in the array.
Example:
processItem := func(idx int, item string) readerresult.ReaderResult[Config, int] {
return readerresult.Of[Config](idx + len(item))
}
items := []string{"a", "bb", "ccc"}
result := readerresult.TraverseArrayWithIndex[Config](processItem)(items)
func WithCloser ¶
WithCloser creates a higher-order function for managing resources that implement io.Closer.
This is a specialized version of WithResource that automatically handles cleanup for any resource implementing the io.Closer interface (such as files, network connections, HTTP response bodies, etc.). It eliminates the need to manually specify the release function, making it more convenient for common Go resources.
The function automatically calls Close() on the resource when the operation completes, regardless of success or failure. This ensures proper resource cleanup following Go's standard io.Closer pattern.
Type Parameters:
- B: The type of the result produced by using the resource
- A: The type of the resource, which must implement io.Closer
Parameters:
- onCreate: Lazy computation that creates/acquires the io.Closer resource
Returns:
- A Kleisli arrow that takes a resource-using function and returns a ReaderResult[B] with automatic Close() cleanup
Example - File operations:
import (
"context"
"os"
"io"
)
// Create a reusable file manager
withFile := readerresult.WithCloser(
func() readerresult.ReaderResult[*os.File] {
return func(ctx context.Context) (*os.File, error) {
return os.Open("data.txt")
}
},
)
// Use with different operations - Close() is automatic
readContent := withFile(func(file *os.File) readerresult.ReaderResult[string] {
return func(ctx context.Context) (string, error) {
data, err := io.ReadAll(file)
return string(data), err
}
})
getSize := withFile(func(file *os.File) readerresult.ReaderResult[int64] {
return func(ctx context.Context) (int64, error) {
info, err := file.Stat()
if err != nil {
return 0, err
}
return info.Size(), nil
}
})
content, err := readContent(context.Background())
size, err := getSize(context.Background())
Example - HTTP response body:
import "net/http"
withResponse := readerresult.WithCloser(
func() readerresult.ReaderResult[*http.Response] {
return func(ctx context.Context) (*http.Response, error) {
return http.Get("https://api.example.com/data")
}
},
)
// Body is automatically closed after use
parseJSON := withResponse(func(resp *http.Response) readerresult.ReaderResult[Data] {
return func(ctx context.Context) (Data, error) {
var data Data
err := json.NewDecoder(resp.Body).Decode(&data)
return data, err
}
})
Example - Multiple file operations:
// Read from one file, write to another
copyFile := func(src, dst string) readerresult.ReaderResult[int64] {
withSrc := readerresult.WithCloser(
func() readerresult.ReaderResult[*os.File] {
return func(ctx context.Context) (*os.File, error) {
return os.Open(src)
}
},
)
withDst := readerresult.WithCloser(
func() readerresult.ReaderResult[*os.File] {
return func(ctx context.Context) (*os.File, error) {
return os.Create(dst)
}
},
)
return withSrc(func(srcFile *os.File) readerresult.ReaderResult[int64] {
return withDst(func(dstFile *os.File) readerresult.ReaderResult[int64] {
return func(ctx context.Context) (int64, error) {
return io.Copy(dstFile, srcFile)
}
})
})
}
Example - Network connection:
import "net"
withConn := readerresult.WithCloser(
func() readerresult.ReaderResult[net.Conn] {
return func(ctx context.Context) (net.Conn, error) {
return net.Dial("tcp", "localhost:8080")
}
},
)
sendData := withConn(func(conn net.Conn) readerresult.ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return conn.Write([]byte("Hello, World!"))
}
})
Note: WithCloser is a convenience wrapper around WithResource that automatically provides the Close() cleanup function. For resources that don't implement io.Closer or require custom cleanup logic, use WithResource or Bracket instead.
func WithContextK ¶
func WithContextK[A, B any](f Kleisli[A, B]) Kleisli[A, B]
WithContextK wraps a Kleisli arrow (a function that returns a ReaderResult) with context cancellation checking.
This is the Kleisli arrow version of WithContext. It takes a function A -> ReaderResult[B] and returns a new function that performs the same transformation but with an added context cancellation check before executing the resulting ReaderResult.
A Kleisli arrow is a function that takes a value and returns a monadic computation. In this case, Kleisli[A, B] = func(A) ReaderResult[B], which represents a function from A to a context-dependent computation that may fail.
WithContextK is particularly useful when composing operations with Chain/Bind, as it ensures that each step in the composition checks for cancellation before proceeding.
Parameters:
- f: A Kleisli arrow (function from A to ReaderResult[B])
Returns:
- A new Kleisli arrow that wraps the result of f with context cancellation checking
Example:
// A function that fetches user details
fetchUserDetails := func(userID int) readerresult.ReaderResult[UserDetails] {
return func(ctx context.Context) (UserDetails, error) {
// Fetch from database...
return details, nil
}
}
// Wrap it to ensure cancellation is checked before each execution
safeFetchDetails := readerresult.WithContextK(fetchUserDetails)
// Use in a composition chain
pipeline := F.Pipe2(
getUser(42),
readerresult.Chain(safeFetchDetails), // Checks cancellation before fetching details
)
// If context is cancelled between getUser and fetchUserDetails,
// the details fetch will not execute
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
result, err := pipeline(ctx)
Use Cases:
- Adding cancellation checks to composed operations
- Ensuring long-running pipelines respect context cancellation
- Wrapping third-party functions that don't check context themselves
- Creating fail-fast behavior in complex operation chains
func WithResource ¶
func WithResource[B, A, ANY any]( onCreate Lazy[ReaderResult[A]], onRelease Kleisli[A, ANY], ) Kleisli[Kleisli[A, B], B]
WithResource creates a higher-order function for resource management with automatic cleanup.
This function provides a more composable alternative to Bracket by creating a function that takes a resource-using function and automatically handles resource acquisition and release. This is particularly useful when you want to reuse the same resource management pattern with different operations.
The pattern is:
- Create a resource manager with onCreate and onRelease
- Apply it to different use functions as needed
- Each application ensures proper resource cleanup
This is useful for:
- Creating reusable resource management patterns
- Building resource pools or factories
- Composing resource-dependent operations
- Abstracting resource lifecycle management
Type Parameters:
- B: The type of the result produced by using the resource
- A: The type of the acquired resource
- ANY: The type returned by the release action (typically ignored)
Parameters:
- onCreate: Lazy computation that creates/acquires the resource
- onRelease: Function that releases the resource (receives the resource)
Returns:
- A Kleisli arrow that takes a resource-using function and returns a ReaderResult[B] with automatic resource management
Example - Reusable database connection manager:
import (
"context"
"database/sql"
)
// Create a reusable DB connection manager
withDB := readerresult.WithResource(
// onCreate: Acquire connection
func() readerresult.ReaderResult[*sql.DB] {
return func(ctx context.Context) (*sql.DB, error) {
return sql.Open("postgres", connString)
}
},
// onRelease: Close connection
func(db *sql.DB) readerresult.ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, db.Close()
}
},
)
// Use the manager with different operations
getUsers := withDB(func(db *sql.DB) readerresult.ReaderResult[[]User] {
return func(ctx context.Context) ([]User, error) {
return queryUsers(ctx, db)
}
})
getOrders := withDB(func(db *sql.DB) readerresult.ReaderResult[[]Order] {
return func(ctx context.Context) ([]Order, error) {
return queryOrders(ctx, db)
}
})
// Both operations automatically manage the connection
users, err := getUsers(context.Background())
orders, err := getOrders(context.Background())
Example - File operations manager:
withFile := readerresult.WithResource(
func() readerresult.ReaderResult[*os.File] {
return func(ctx context.Context) (*os.File, error) {
return os.Open("config.json")
}
},
func(file *os.File) readerresult.ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, file.Close()
}
},
)
// Different operations on the same file
readConfig := withFile(func(file *os.File) readerresult.ReaderResult[Config] {
return func(ctx context.Context) (Config, error) {
return parseConfig(file)
}
})
validateConfig := withFile(func(file *os.File) readerresult.ReaderResult[bool] {
return func(ctx context.Context) (bool, error) {
return validateConfigFile(file)
}
})
Example - Composing with other operations:
import F "github.com/IBM/fp-go/v2/function"
// Create a pipeline with automatic resource management
processData := F.Pipe2(
loadData,
withDB(func(db *sql.DB) readerresult.ReaderResult[Result] {
return saveToDatabase(db)
}),
readerresult.Map(formatResult),
)
type Lens ¶
Lens represents an optic that focuses on a field of type A within a structure of type S.
type Monoid ¶
type Monoid[A any] = monoid.Monoid[ReaderResult[A]]
Monoid represents a monoid structure for ReaderResult values.
func AltMonoid ¶
func AltMonoid[A any](zero Lazy[ReaderResult[A]]) Monoid[A]
AltMonoid creates a Monoid for ReaderResult using Alt semantics with a custom zero.
The Alt semantics means that the monoid operation tries the first computation, and if it fails, tries the second one. The provided zero is used as the empty element.
Type Parameters:
- A: The value type
Parameters:
- zero: A lazy ReaderResult[A] to use as the empty element
Returns:
- A Monoid[ReaderResult[A]] with Alt semantics
Example:
zero := func() readerresult.ReaderResult[int] {
return readerresult.Left[int](errors.New("empty"))
}
rrMonoid := readerresult.AltMonoid(zero)
rr1 := readerresult.Left[int](errors.New("failed"))
rr2 := readerresult.Right(42)
combined := rrMonoid.Concat(rr1, rr2)
value, err := combined(ctx) // Returns (42, nil) - uses second on first failure
func AlternativeMonoid ¶
AlternativeMonoid creates a Monoid for ReaderResult using the Alternative semantics.
The Alternative semantics means that the monoid operation tries the first computation, and if it fails, tries the second one. The empty element is a computation that always fails. The inner values are combined using the provided monoid when both computations succeed.
Type Parameters:
- A: The value type
Parameters:
- m: A Monoid[A] for combining successful values
Returns:
- A Monoid[ReaderResult[A]] with Alternative semantics
Example:
import "github.com/IBM/fp-go/v2/monoid" // Monoid for integers with addition intMonoid := monoid.MonoidSum[int]() rrMonoid := readerresult.AlternativeMonoid(intMonoid) rr1 := readerresult.Right(10) rr2 := readerresult.Right(20) combined := rrMonoid.Concat(rr1, rr2) value, err := combined(ctx) // Returns (30, nil)
func ApplicativeMonoid ¶
ApplicativeMonoid creates a Monoid for ReaderResult using Applicative semantics.
The Applicative semantics means that both computations are executed independently, and their results are combined using the provided monoid. If either fails, the entire operation fails.
Type Parameters:
- A: The value type
Parameters:
- m: A Monoid[A] for combining successful values
Returns:
- A Monoid[ReaderResult[A]] with Applicative semantics
Example:
import "github.com/IBM/fp-go/v2/monoid" // Monoid for integers with addition intMonoid := monoid.MonoidSum[int]() rrMonoid := readerresult.ApplicativeMonoid(intMonoid) rr1 := readerresult.Right(10) rr2 := readerresult.Right(20) combined := rrMonoid.Concat(rr1, rr2) value, err := combined(ctx) // Returns (30, nil)
type Operator ¶
type Operator[A, B any] = Kleisli[ReaderResult[A], B]
Operator represents a Kleisli arrow that operates on ReaderResult values. It transforms a ReaderResult[A] into a ReaderResult[B], useful for composing context-aware operations.
func Ap ¶
func Ap[B, A any](fa ReaderResult[A]) Operator[func(A) B, B]
Ap is the curried version of MonadAp, useful for function composition.
It fixes the value argument and returns an Operator that can be applied to a ReaderResult containing a function. This is particularly useful in pipelines where you want to apply a fixed value to various functions.
Type Parameters:
- B: The result type after applying the function
- A: The input type to the function
Parameters:
- fa: A ReaderResult containing a value of type A
Returns:
- An Operator that applies the value to a function wrapped in ReaderResult
Example:
import F "github.com/IBM/fp-go/v2/function"
value := readerresult.Right(32)
addTen := readerresult.Right(N.Add(10))
result := F.Pipe1(
addTen,
readerresult.Ap[int](value),
)
output, err := result(ctx) // Returns (42, nil)
func ApEitherS ¶
func ApEitherS[ S1, S2, T any]( setter func(T) func(S1) S2, fa Result[T], ) Operator[S1, S2]
ApEitherS applies a Result (Either) in applicative style, combining it with the current state.
IMPORTANT: Result[T] is PURE (side-effect free) - it doesn't depend on context. Use this for pure error-handling values.
Example ¶
ExampleApEitherS demonstrates applying a Result (Either) in applicative style, combining it with the current state in a do-notation chain.
// A Result value
scoreResult := RES.Of(95)
result := F.Pipe1(
Do(ScoreState{}),
ApEitherS(
func(score int) Endomorphism[ScoreState] {
return func(s ScoreState) ScoreState {
s.Score = score
return s
}
},
scoreResult,
),
)
state, err := result(context.Background())
fmt.Printf("Score: %d, Error: %v\n", state.Score, err)
Output: Score: 95, Error: <nil>
func ApReaderS ¶
func ApReaderS[ S1, S2, T any]( setter func(T) func(S1) S2, fa Reader[context.Context, T], ) Operator[S1, S2]
ApReaderS applies a Reader computation in applicative style, combining it with the current state.
IMPORTANT: Reader[Context, T] is EFFECTFUL because it depends on context.Context. Use this for context-dependent operations that cannot fail.
Example ¶
ExampleApReaderS demonstrates applying a Reader computation in applicative style, combining it with the current state in a do-notation chain.
// A Reader that gets environment from context
getEnv := func(ctx context.Context) string {
if val := ctx.Value("env"); val != nil {
return val.(string)
}
return "development"
}
result := F.Pipe1(
Do(EnvState{}),
ApReaderS(
func(env string) Endomorphism[EnvState] {
return func(s EnvState) EnvState {
s.Environment = env
return s
}
},
getEnv,
),
)
ctx := context.WithValue(context.Background(), "env", "staging")
state, err := result(ctx)
fmt.Printf("Environment: %s, Error: %v\n", state.Environment, err)
Output: Environment: staging, Error: <nil>
func ApS ¶
func ApS[S1, S2, T any]( setter func(T) func(S1) S2, fa ReaderResult[T], ) Operator[S1, S2]
ApS attaches a value to a context using applicative style.
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.
Type Parameters:
- S1: The input state type
- S2: The output state type
- T: The type of value produced by the computation
Parameters:
- setter: A function that takes the computation result and returns a state updater
- fa: An effectful ReaderResult computation
Returns:
- An Operator that transforms ReaderResult[S1] to ReaderResult[S2]
func ApSL ¶
func ApSL[S, T any]( lens Lens[S, T], fa ReaderResult[T], ) Operator[S, S]
ApSL is a variant of ApS that uses a lens to focus on a specific field in the state.
IMPORTANT: ApSL is for EFFECTFUL FUNCTIONS that depend on context.Context. The ReaderResult parameter is effectful because it depends on context.Context.
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.
Type Parameters:
- S: The state type
- T: The type of the field to update
Parameters:
- lens: A lens that focuses on a field of type T within state S
- fa: An effectful ReaderResult computation that produces a value of type T
Returns:
- An Operator that transforms ReaderResult[S] to ReaderResult[S]
func BiMap ¶
func BiMap[A, B any](f Endomorphism[error], g func(A) B) Operator[A, B]
func Bind ¶
func Bind[S1, S2, T any]( setter func(T) func(S1) S2, f Kleisli[S1, T], ) Operator[S1, S2]
Bind sequences an EFFECTFUL ReaderResult computation and updates the state with its result.
IMPORTANT: Bind is for EFFECTFUL FUNCTIONS that depend on context.Context. The Kleisli parameter (State -> ReaderResult[T]) is effectful because ReaderResult 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)
This is the core operation for do-notation, allowing you to chain computations where each step can depend on the accumulated state and update it with new values.
Type Parameters:
- S1: The input state type
- S2: The output state type
- T: The type of value produced by the computation
Parameters:
- setter: A function that takes the computation result and returns a state updater
- f: A Kleisli arrow that produces the next effectful computation based on current state
Returns:
- An Operator that transforms ReaderResult[S1] to ReaderResult[S2]
Example ¶
ExampleBind demonstrates sequencing a ReaderResult computation and updating the state with its result. This is the core operation for do-notation, allowing you to chain computations where each step can depend on the accumulated state and update it with new values.
Step-by-step breakdown:
1. Setup lenses for accessing nested state fields:
userLenses: Provides lenses for User fields (ID, Name)
stateLenses: Provides lenses for State fields (User, Posts, FullName, Status)
userIdLens: A composed lens that focuses on state.User.ID Created by composing stateLenses.User with userLenses.ID
2. Do(State{}) - Initialize the do-notation chain with an empty State. This creates the initial ReaderResult that will accumulate data through subsequent operations.
3. ApSL(stateLenses.User, getUser(42)) - Fetch user and store in state.User field. ApSL (Applicative Set Lens) executes the getUser(42) ReaderResult computation and uses the lens to set the result into state.User. After this step: state.User = User{ID: 42, Name: "Alice"}
4. Bind(stateLenses.Posts.Set, F.Flow2(userIdLens.Get, getPosts)) - Fetch posts based on the user ID from state and store them in state.Posts.
Breaking down the Bind operation: a) First parameter: stateLenses.Posts.Set - A setter function that will update the Posts field in the state with the result of the computation.
b) Second parameter: F.Flow2(userIdLens.Get, getPosts) - A composed function that:
Takes the current state as input
Extracts the user ID using userIdLens.Get (gets state.User.ID)
Passes the user ID to getPosts, which returns a ReaderResult[[]Post]
The result is then set into state.Posts using the setter
After this step: state.Posts = [{ID: 1, UserID: 42, ...}, {ID: 2, UserID: 42, ...}]
5. result(context.Background()) - Execute the entire computation chain. This runs all the ReaderResult operations in sequence, threading the context through each step and accumulating the state.
Key concepts demonstrated: - Lens composition: Building complex accessors from simple ones - Sequential effects: Each step can depend on previous results - State accumulation: Building up a complex state object step by step - Context threading: The context.Context flows through all operations - Error handling: Any error in the chain short-circuits execution
userLenses := MakeUserLenses()
stateLenses := MakeStateLenses()
userIdLens := F.Pipe1(
stateLenses.User,
lens.Compose[State](userLenses.ID),
)
result := F.Pipe2(
Do(State{}),
ApSL(
stateLenses.User,
getUser(42),
),
Bind(
stateLenses.Posts.Set,
F.Flow2(
userIdLens.Get,
getPosts,
),
),
)
state, err := result(context.Background())
fmt.Printf("User: %s, Posts: %d, Error: %v\n", state.User.Name, len(state.Posts), err)
Output: User: Alice, Posts: 2, Error: <nil>
func BindEitherK ¶
func BindEitherK[S1, S2, T any]( setter func(T) func(S1) S2, f RES.Kleisli[S1, T], ) Operator[S1, S2]
BindEitherK binds a Result (Either) computation into the do-notation chain.
IMPORTANT: This is for PURE FUNCTIONS (side-effect free) that return Result[T]. The function (State -> Result[T]) is pure - it only depends on state, not context. Use this for pure error-handling logic that doesn't need context.
Example ¶
ExampleBindEitherK demonstrates binding a Result (Either) computation into a ReaderResult do-notation chain. This is useful for integrating pure error-handling logic that doesn't need context.
numberStateLenses := MakeNumberStateLenses()
// A pure function that returns a Result
parseNumber := func(s NumberState) RES.Result[int] {
return RES.Of(42)
}
result := F.Pipe1(
Do(NumberState{}),
BindEitherK(
numberStateLenses.Number.Set,
parseNumber,
),
)
state, err := result(context.Background())
fmt.Printf("Number: %d, Error: %v\n", state.Number, err)
Output: Number: 42, Error: <nil>
func BindL ¶
func BindL[S, T any]( lens Lens[S, T], f Kleisli[T, T], ) Operator[S, S]
BindL is a variant of Bind that uses a lens to focus on a specific field in the state.
IMPORTANT: BindL is for EFFECTFUL FUNCTIONS that depend on context.Context. The Kleisli parameter returns a ReaderResult, which is effectful.
It combines lens-based field access with monadic composition, allowing 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
Type Parameters:
- S: The state type
- T: The type of the field to update
Parameters:
- lens: A lens that focuses on a field of type T within state S
- f: An effectful Kleisli arrow that transforms the field value
Returns:
- An Operator that transforms ReaderResult[S] to ReaderResult[S]
func BindReaderK ¶
func BindReaderK[S1, S2, T any]( setter func(T) func(S1) S2, f reader.Kleisli[context.Context, S1, T], ) Operator[S1, S2]
BindReaderK binds a Reader computation (context-dependent but error-free) into the do-notation chain.
IMPORTANT: This is for functions that depend on context.Context but don't return errors. The Reader[Context, T] is effectful because it depends on context.Context. Use this when you need context values but the operation cannot fail.
Example ¶
ExampleBindReaderK demonstrates binding a Reader computation (context-dependent but error-free) into a ReaderResult do-notation chain.
configStateLenses := MakeConfigStateLenses()
// A Reader that extracts a value from context
getConfig := func(ctx context.Context) string {
if val := ctx.Value("config"); val != nil {
return val.(string)
}
return "default"
}
result := F.Pipe1(
Do(ConfigState{}),
BindReaderK(configStateLenses.Config.Set,
func(s ConfigState) Reader[context.Context, string] {
return getConfig
},
),
)
ctx := context.WithValue(context.Background(), "config", "production")
state, err := result(ctx)
fmt.Printf("Config: %s, Error: %v\n", state.Config, err)
Output: Config: production, Error: <nil>
func BindResultK ¶
func BindResultK[S1, S2, T any]( setter func(T) func(S1) S2, f result.Kleisli[S1, T], ) Operator[S1, S2]
BindResultK binds an idiomatic Go function (returning value and error) into the do-notation chain.
IMPORTANT: This is for PURE FUNCTIONS (side-effect free) that return (Value, error). The function (State -> (Value, error)) is pure - it only depends on state, not context. Use this for pure computations with error handling that don't need context.
For EFFECTFUL FUNCTIONS (that need context.Context), use Bind instead.
Example ¶
ExampleBindResultK demonstrates binding an idiomatic Go function (returning value and error) into a ReaderResult do-notation chain. This is particularly useful for integrating existing Go code that follows the standard (value, error) return pattern into functional pipelines.
Step-by-step breakdown:
dataStateLenses := MakeDataStateLenses() - Create lenses for accessing DataState fields. This provides functional accessors (getters and setters) for the Data field, enabling type-safe, immutable field updates.
fetchData := func(s DataState) (string, error) - Define an idiomatic Go function that takes the current state and returns a tuple of (value, error).
IMPORTANT: This function represents a PURE READER COMPOSITION - it reads from the state and performs computations that don't require a context.Context. This is suitable for: - Pure computations that may fail (parsing, validation, calculations) - Operations that only depend on the state, not external context - Stateless transformations with error handling - Synchronous operations that don't need cancellation or timeouts
For EFFECTFUL COMPOSITION (operations that need context), use the full ReaderResult type instead: func(context.Context) (Value, error) Use ReaderResult when you need: - Context cancellation or timeouts - Context values (request IDs, trace IDs, etc.) - Operations that depend on external context state - Async operations that should respect context lifecycle
In this example, fetchData always succeeds with "fetched data", but in real code it might perform pure operations like: - Parsing or validating data from the state - Performing calculations that could fail - Calling pure functions from external libraries - Data transformations that don't require context
Do(DataState{}) - Initialize the do-notation chain with an empty DataState. This creates the initial ReaderResult that will accumulate data through subsequent operations. Initial state: {Data: ""}
BindResultK(dataStateLenses.Data.Set, fetchData) - Bind the idiomatic Go function into the ReaderResult chain.
BindResultK takes two parameters:
a) First parameter: dataStateLenses.Data.Set This is a setter function from the lens that will update the Data field with the result of the computation. The lens ensures immutable updates.
b) Second parameter: fetchData This is the idiomatic Go function (State -> (Value, error)) that will be lifted into the ReaderResult context.
The BindResultK operation flow: - Takes the current state: {Data: ""} - Calls fetchData with the state: fetchData(DataState{}) - Gets the result tuple: ("fetched data", nil) - If error is not nil, short-circuits the chain and returns the error - If error is nil, uses the setter to update state.Data with "fetched data" - Returns the updated state: {Data: "fetched data"} After this step: {Data: "fetched data"}
result(context.Background()) - Execute the computation chain with a context. Even though fetchData doesn't use the context, the ReaderResult still needs one to maintain the uniform interface. This runs all operations in sequence and returns the final state and any error.
Key concepts demonstrated: - Integration of idiomatic Go code: BindResultK bridges functional and imperative styles - Error propagation: Errors from the Go function automatically propagate through the chain - State transformation: The result updates the state using lens-based setters - Context independence: The function doesn't need context but still works in ReaderResult
Comparison with other bind operations: - BindResultK: For idiomatic Go functions (State -> (Value, error)) - Bind: For full ReaderResult computations (State -> ReaderResult[Value]) - BindEitherK: For pure Result/Either values (State -> Result[Value]) - BindReaderK: For context-dependent functions (State -> Reader[Context, Value])
Use BindResultK when you need to: - Integrate existing Go code that returns (value, error) - Call functions that may fail but don't need context - Perform stateful computations with standard Go error handling - Bridge between functional pipelines and imperative Go code - Work with libraries that follow Go conventions
Real-world example scenarios: - Parsing JSON from a state field: func(s State) (ParsedData, error) - Validating user input: func(s State) (ValidatedInput, error) - Performing calculations: func(s State) (Result, error) - Calling third-party libraries: func(s State) (APIResponse, error)
dataStateLenses := MakeDataStateLenses()
// An idiomatic Go function returning (value, error)
fetchData := func(s DataState) (string, error) {
return "fetched data", nil
}
result := F.Pipe1(
Do(DataState{}),
BindResultK(
dataStateLenses.Data.Set,
fetchData,
),
)
state, err := result(context.Background())
fmt.Printf("Data: %s, Error: %v\n", state.Data, err)
Output: Data: fetched data, Error: <nil>
func BindTo ¶
func BindTo[S1, T any]( setter func(T) S1, ) Operator[T, S1]
BindTo initializes do-notation by binding a value to a state.
This is typically used as the first operation after a computation to start building up a state structure.
Type Parameters:
- S1: The state type to create
- T: The type of the initial value
Parameters:
- setter: A function that creates the initial state from a value
Returns:
- An Operator that transforms ReaderResult[T] to ReaderResult[S1]
Example ¶
ExampleBindTo demonstrates initializing do-notation by binding a value to a state. This is typically used as the first operation after a computation to start building up a state structure.
userStatePrisms := MakeUserStatePrisms()
result := F.Pipe1(
getUser(42),
BindToP(userStatePrisms.User),
)
state, err := result(context.Background())
fmt.Printf("User: %s, Error: %v\n", state.User.Name, err)
Output: User: Alice, Error: <nil>
func BindToP ¶
func BindToP[S1, T any]( setter Prism[S1, T], ) Operator[T, S1]
BindToP initializes do-notation by binding a value to a state using a Prism.
This is a variant of BindTo that uses a prism instead of a setter function. Prisms are useful for working with sum types and optional values.
Type Parameters:
- S1: The state type to create
- T: The type of the initial value
Parameters:
- setter: A prism that can construct the state from a value
Returns:
- An Operator that transforms ReaderResult[T] to ReaderResult[S1]
func Chain ¶
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B]
Chain is the curried version of MonadChain, useful for function composition.
It returns an Operator that can be used in pipelines with F.Pipe.
Type Parameters:
- A: The input value type
- B: The output value type
Parameters:
- f: A Kleisli arrow for the second computation
Returns:
- An Operator that chains ReaderResult computations
func ChainEitherK ¶
func ChainReaderK ¶
func Let ¶
func Let[S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) T, ) Operator[S1, S2]
Let attaches the result of a PURE computation to a state.
IMPORTANT: Let is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context. The function parameter (State -> Value) is pure - it only reads from state with no 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))
Unlike Bind, Let works with pure functions (not ReaderResult computations). This is useful for deriving values from the current state without performing any effects.
Type Parameters:
- S1: The input state type
- S2: The output state type
- T: The type of value computed
Parameters:
- setter: A function that takes the computed value and returns a state updater
- f: A pure function that computes a value from the current state
Returns:
- An Operator that transforms ReaderResult[S1] to ReaderResult[S2]
Example ¶
ExampleLet demonstrates attaching the result of a pure computation to a state. Unlike Bind, Let works with pure functions (not ReaderResult computations). This is useful for deriving values from the current state without performing any effects.
Step-by-step breakdown:
nameStateLenses := MakeNameStateLenses() - Create lenses for accessing NameState fields. Lenses provide a functional way to get and set nested fields in immutable data structures. This gives us lenses for FirstName, LastName, and FullName fields.
Do(NameState{FirstName: "John", LastName: "Doe"}) - Initialize the do-notation chain with a NameState containing first and last names. Initial state: {FirstName: "John", LastName: "Doe", FullName: ""}
Let(nameStateLenses.FullName.Set, func(s NameState) string {...}) - Compute a derived value from the current state and update the state with it.
Let takes two parameters:
a) First parameter: nameStateLenses.FullName.Set This is a setter function (from the lens) that takes a value and returns a function to update the FullName field in the state. The lens-based setter ensures immutable updates.
b) Second parameter: func(s NameState) string This is a pure "getter" or "computation" function that derives a value from the current state. Here it concatenates FirstName and LastName with a space. This function has no side effects - it just computes a value.
The Let operation flow: - Takes the current state: {FirstName: "John", LastName: "Doe", FullName: ""} - Calls the computation function: "John" + " " + "Doe" = "John Doe" - Passes "John Doe" to the setter (nameStateLenses.FullName.Set) - The setter creates a new state with FullName updated After this step: {FirstName: "John", LastName: "Doe", FullName: "John Doe"}
Map(nameStateLenses.FullName.Get) - Transform the final state to extract just the FullName field using the lens getter. This changes the result type from ReaderResult[NameState] to ReaderResult[string].
result(context.Background()) - Execute the computation chain and return the final extracted value ("John Doe") and any error.
Key differences between Let and Bind: - Let: Works with pure functions (State -> Value), no effects or errors - Bind: Works with effectful computations (State -> ReaderResult[Value]) - Let: Used for deriving/computing values from existing state - Bind: Used for operations that may fail, need context, or have side effects
Use Let when you need to: - Compute derived values from existing state fields - Transform or combine state values without side effects - Add computed fields to your state for later use in the pipeline - Perform pure calculations that don't require context or error handling
nameStateLenses := MakeNameStateLenses()
result := F.Pipe2(
Do(NameState{FirstName: "John", LastName: "Doe"}),
Let(nameStateLenses.FullName.Set,
func(s NameState) string {
return s.FirstName + " " + s.LastName
},
),
Map(nameStateLenses.FullName.Get),
)
fullName, err := result(context.Background())
fmt.Printf("Full Name: %s, Error: %v\n", fullName, err)
Output: Full Name: John Doe, Error: <nil>
func LetL ¶
func LetL[S, T any]( lens Lens[S, T], f Endomorphism[T], ) Operator[S, S]
LetL is a variant of Let that uses a lens to focus on a specific field in the state.
IMPORTANT: LetL is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context. The endomorphism parameter is a pure function (T -> T) with no errors or effects.
It applies a pure transformation to the focused field without any effects.
Type Parameters:
- S: The state type
- T: The type of the field to update
Parameters:
- lens: A lens that focuses on a field of type T within state S
- f: A pure endomorphism that transforms the field value
Returns:
- An Operator that transforms ReaderResult[S] to ReaderResult[S]
func LetTo ¶
func LetTo[S1, S2, T any]( setter func(T) func(S1) S2, b T, ) Operator[S1, S2]
LetTo attaches a constant value to a state. This is a PURE operation (side-effect free).
This is a simplified version of Let for when you want to add a constant value to the state without computing it.
Type Parameters:
- S1: The input state type
- S2: The output state type
- T: The type of the constant value
Parameters:
- setter: A function that takes the constant and returns a state updater
- b: The constant value to attach
Returns:
- An Operator that transforms ReaderResult[S1] to ReaderResult[S2]
Example ¶
ExampleLetTo demonstrates attaching a constant value to a state. This is a simplified version of Let for when you want to add a constant value to the state without computing it.
Step-by-step breakdown:
statusStateLenses := MakeStatusStateLenses() - Create lenses for accessing StatusState fields. This provides functional accessors (getters and setters) for the Status field.
Do(StatusState{}) - Initialize the do-notation chain with an empty StatusState. Initial state: {Status: ""}
LetToL(statusStateLenses.Status, "active") - Set the Status field to the constant value "active".
LetToL is the lens-based version of LetTo and takes two parameters:
a) First parameter: statusStateLenses.Status This is a lens that focuses on the Status field. The lens provides both a getter and setter for the field, enabling immutable updates.
b) Second parameter: "active" This is the constant value to assign to the Status field. Unlike Let, which takes a function to compute the value, LetToL directly takes the value itself.
The LetToL operation: - Takes the constant value "active" - Uses the lens setter to create a new state with Status = "active" - Returns the updated state After this step: {Status: "active"}
Map(statusStateLenses.Status.Get) - Transform the final state to extract just the Status field using the lens getter. This changes the result type from ReaderResult[StatusState] to ReaderResult[string].
result(context.Background()) - Execute the computation chain and return the final extracted value ("active") and any error.
Comparison of state-setting operations: - LetToL: Set a field to a constant value using a lens (simplest) - LetL: Transform a field using a function and a lens - Let: Compute a value from state and update using a custom setter - Bind: Execute an effectful computation and update state with the result
Use LetToL when you need to: - Set a field to a known constant value - Initialize state fields with default values - Update configuration or status flags - Assign literal values without any computation
LetToL is the most straightforward way to set a constant value in do-notation, combining the simplicity of LetTo with the power of lenses for type-safe, immutable field updates.
statusStateLenses := MakeStatusStateLenses()
result := F.Pipe2(
Do(StatusState{}),
LetToL(
statusStateLenses.Status,
"active",
),
Map(statusStateLenses.Status.Get),
)
status, err := result(context.Background())
fmt.Printf("Status: %s, Error: %v\n", status, err)
Output: Status: active, Error: <nil>
func LetToL ¶
func LetToL[S, T any]( lens Lens[S, T], b T, ) Operator[S, S]
LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state.
IMPORTANT: LetToL is for setting constant values. This is a PURE operation (side-effect free).
It sets the focused field to a constant value.
Type Parameters:
- S: The state type
- T: The type of the field to update
Parameters:
- lens: A lens that focuses on a field of type T within state S
- b: The constant value to set
Returns:
- An Operator that transforms ReaderResult[S] to ReaderResult[S]
func Local ¶
Local transforms the context.Context environment before passing it to a ReaderResult computation.
This is the Reader's local operation, which allows you to modify the environment for a specific computation without affecting the outer context. The transformation function receives the current context and returns a new context along with a cancel function. The cancel function is automatically called when the computation completes (via defer), ensuring proper cleanup of resources.
This is useful for:
- Adding timeouts or deadlines to specific operations
- Adding context values for nested computations
- Creating isolated context scopes
- Implementing context-based dependency injection
Type Parameters:
- A: The value type of the ReaderResult
Parameters:
- f: A function that transforms the context and returns a cancel function
Returns:
- An Operator that runs the computation with the transformed context
Example:
import F "github.com/IBM/fp-go/v2/function"
// Add a custom value to the context
type key int
const userKey key = 0
addUser := readerresult.Local[string](func(ctx context.Context) (context.Context, context.CancelFunc) {
newCtx := context.WithValue(ctx, userKey, "Alice")
return newCtx, func() {} // No-op cancel
})
getUser := readerresult.Asks(func(ctx context.Context) string {
return ctx.Value(userKey).(string)
})
result := F.Pipe1(
getUser,
addUser,
)
user, err := result(context.Background()) // Returns ("Alice", nil)
Timeout Example:
// Add a 5-second timeout to a specific operation
withTimeout := readerresult.Local[Data](func(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, 5*time.Second)
})
result := F.Pipe1(
fetchData,
withTimeout,
)
func Map ¶
func Map[A, B any](f func(A) B) Operator[A, B]
Map is the curried version of MonadMap, useful for function composition.
It returns an Operator that can be used in pipelines with F.Pipe.
Type Parameters:
- A: The input value type
- B: The output value type
Parameters:
- f: The transformation function
Returns:
- An Operator that transforms ReaderResult[A] to ReaderResult[B]
func MapLeft ¶
func MapLeft[A any](f Endomorphism[error]) Operator[A, A]
func WithDeadline ¶
WithDeadline adds an absolute deadline to the context for a ReaderResult computation.
This is a convenience wrapper around Local that uses context.WithDeadline. The computation must complete before the specified time, or it will be cancelled. This is useful for coordinating operations that must finish by a specific time, such as request deadlines or scheduled tasks.
The deadline is an absolute time, unlike WithTimeout which uses a relative duration. The cancel function is automatically called when the computation completes, ensuring proper cleanup.
Type Parameters:
- A: The value type of the ReaderResult
Parameters:
- deadline: The absolute time by which the computation must complete
Returns:
- An Operator that runs the computation with a deadline
func WithTimeout ¶
WithTimeout adds a timeout to the context for a ReaderResult computation.
This is a convenience wrapper around Local that uses context.WithTimeout. The computation must complete within the specified duration, or it will be cancelled. This is useful for ensuring operations don't run indefinitely and for implementing timeout-based error handling.
The timeout is relative to when the ReaderResult is executed, not when WithTimeout is called. The cancel function is automatically called when the computation completes, ensuring proper cleanup.
Type Parameters:
- A: The value type of the ReaderResult
Parameters:
- timeout: The maximum duration for the computation
Returns:
- An Operator that runs the computation with a timeout
Example:
import (
"time"
F "github.com/IBM/fp-go/v2/function"
)
// Fetch data with a 5-second timeout
fetchData := readerresult.FromReader(func(ctx context.Context) Data {
// Simulate slow operation
select {
case <-time.After(10 * time.Second):
return Data{Value: "slow"}
case <-ctx.Done():
return Data{}
}
})
result := F.Pipe1(
fetchData,
readerresult.WithTimeout[Data](5*time.Second),
)
_, err := result(context.Background()) // Returns context.DeadlineExceeded after 5s
Successful Example:
quickFetch := readerresult.Right(Data{Value: "quick"})
result := F.Pipe1(
quickFetch,
readerresult.WithTimeout[Data](5*time.Second),
)
data, err := result(context.Background()) // Returns (Data{Value: "quick"}, nil)
type Reader ¶
Reader represents a computation that depends on a read-only environment of type R and produces a value of type A.
type ReaderResult ¶
ReaderResult represents a computation that depends on a context.Context and produces either a value of type A or an error. It combines the Reader pattern with Result (error handling), making it suitable for context-aware operations that may fail.
func Ask ¶
Ask retrieves the current context.Context environment.
This is the Reader's ask operation, which provides access to the environment. It always succeeds and returns the context that was passed in.
Returns:
- A ReaderResult[context.Context] that returns the environment
Example ¶
ExampleAsk demonstrates getting the context.Context environment. This returns a ReaderResult that provides access to the context itself.
rr := Ask() ctx := context.Background() value, err := rr(ctx) fmt.Println(value == ctx, err)
Output: true <nil>
func Asks ¶
Asks extracts a value from the context.Context environment using a Reader function.
This is useful for accessing specific parts of the environment. The Reader function is applied to the context, and the result is wrapped in a successful ReaderResult.
Type Parameters:
- A: The extracted value type
Parameters:
- r: A Reader function that extracts a value from the context
Returns:
- A ReaderResult[A] that extracts and returns the value
Example ¶
ExampleAsks demonstrates extracting a value from the context using a function. This is useful for accessing configuration or other data stored in the context.
type Config struct {
Port int
}
getPort := Asks(func(ctx context.Context) int {
// In real code, extract config from context
return 8080
})
value, err := getPort(context.Background())
fmt.Println(value, err)
Output: 8080 <nil>
func Bracket ¶
func Bracket[ A, B, ANY any]( acquire Lazy[ReaderResult[A]], use Kleisli[A, B], release func(A, B, error) ReaderResult[ANY], ) ReaderResult[B]
Bracket ensures safe resource management with guaranteed cleanup in the ReaderResult monad.
This function implements the bracket pattern (also known as try-with-resources or RAII) for ReaderResult computations. It guarantees that the release action is called regardless of whether the use action succeeds or fails, making it ideal for managing resources like file handles, database connections, network sockets, or locks.
The execution flow is:
- Acquire the resource (lazily evaluated)
- Use the resource with the provided function
- Release the resource with access to: the resource, the result (if successful), and any error
The release function is always called, even if:
- The acquire action fails (release is not called in this case)
- The use action fails (release receives the error)
- The use action succeeds (release receives nil error)
Type Parameters:
- A: The type of the acquired resource
- B: The type of the result produced by using the resource
- ANY: The type returned by the release action (typically ignored)
Parameters:
- acquire: Lazy computation that acquires the resource
- use: Function that uses the resource to produce a result
- release: Function that releases the resource, receiving the resource, result, and any error
Returns:
- A ReaderResult[B] that safely manages the resource lifecycle
Example - File handling:
import (
"context"
"os"
)
readFile := readerresult.Bracket(
// Acquire: Open file
func() readerresult.ReaderResult[*os.File] {
return func(ctx context.Context) (*os.File, error) {
return os.Open("data.txt")
}
},
// Use: Read file contents
func(file *os.File) readerresult.ReaderResult[string] {
return func(ctx context.Context) (string, error) {
data, err := io.ReadAll(file)
return string(data), err
}
},
// Release: Close file (always called)
func(file *os.File, content string, err error) readerresult.ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, file.Close()
}
},
)
content, err := readFile(context.Background())
Example - Database connection:
queryDB := readerresult.Bracket(
// Acquire: Open connection
func() readerresult.ReaderResult[*sql.DB] {
return func(ctx context.Context) (*sql.DB, error) {
return sql.Open("postgres", connString)
}
},
// Use: Execute query
func(db *sql.DB) readerresult.ReaderResult[[]User] {
return func(ctx context.Context) ([]User, error) {
return queryUsers(ctx, db)
}
},
// Release: Close connection (always called)
func(db *sql.DB, users []User, err error) readerresult.ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, db.Close()
}
},
)
Example - Lock management:
withLock := readerresult.Bracket(
// Acquire: Lock mutex
func() readerresult.ReaderResult[*sync.Mutex] {
return func(ctx context.Context) (*sync.Mutex, error) {
mu.Lock()
return mu, nil
}
},
// Use: Perform critical section work
func(mu *sync.Mutex) readerresult.ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return performCriticalWork(ctx)
}
},
// Release: Unlock mutex (always called)
func(mu *sync.Mutex, result int, err error) readerresult.ReaderResult[any] {
return func(ctx context.Context) (any, error) {
mu.Unlock()
return nil, nil
}
},
)
func Curry0 ¶
Curry0 converts a function that takes context.Context and returns (A, error) into a ReaderResult[A].
This is useful for lifting existing functions that follow Go's context-first convention into the ReaderResult monad.
Type Parameters:
- A: The return value type
Parameters:
- f: A function that takes context.Context and returns (A, error)
Returns:
- A ReaderResult[A] that wraps the function
Example:
func getConfig(ctx context.Context) (Config, error) {
// ... implementation
return config, nil
}
rr := readerresult.Curry0(getConfig)
config, err := rr(ctx)
func Do ¶
func Do[S any]( empty S, ) ReaderResult[S]
Do initializes a do-notation context with an empty state.
This is the starting point for do-notation style composition, which allows imperative-style sequencing of ReaderResult computations while maintaining functional purity.
Type Parameters:
- S: The state type
Parameters:
- empty: The initial empty state
Returns:
- A ReaderResult[S] containing the initial state
Example ¶
ExampleDo demonstrates initializing a do-notation context with an empty state. This is the starting point for do-notation style composition, which allows imperative-style sequencing of ReaderResult computations while maintaining functional purity.
Step-by-step breakdown:
Do(SimpleState{}) - Initialize the do-notation chain with an empty SimpleState. This creates a ReaderResult that, when executed, will return the initial state. The state acts as an accumulator that will be threaded through subsequent operations.
LetToL(simpleStateLenses.Value, 42) - Set the Value field to the constant 42. LetToL uses a lens to focus on a specific field in the state and assign a constant value. The "L" suffix indicates this is the lens-based version of LetTo. After this step, state.Value = 42.
LetL(simpleStateLenses.Value, N.Add(8)) - Transform the Value field by adding 8. LetL uses a lens to focus on a field and apply a transformation function to it. N.Add(8) creates a function that adds 8 to its input. After this step, state.Value = 42 + 8 = 50.
result(context.Background()) - Execute the composed ReaderResult computation. This runs the entire chain with the provided context and returns the final state and any error that occurred during execution.
The key insight: Do-notation allows you to build complex stateful computations in a declarative, pipeline style while maintaining immutability and composability.
simpleStateLenses := MakeSimpleStateLenses()
result := F.Pipe2(
Do(SimpleState{}),
LetToL(
simpleStateLenses.Value,
42,
),
LetL(
simpleStateLenses.Value,
N.Add(8),
),
)
state, err := result(context.Background())
fmt.Printf("Value: %d, Error: %v\n", state.Value, err)
Output: Value: 50, Error: <nil>
func Flatten ¶
func Flatten[A any](mma ReaderResult[ReaderResult[A]]) ReaderResult[A]
Flatten removes one level of ReaderResult nesting.
This is equivalent to Chain with the identity function. It's useful when you have a ReaderResult that produces another ReaderResult and want to collapse them into one.
Type Parameters:
- A: The inner value type
Parameters:
- mma: A nested ReaderResult[ReaderResult[A]]
Returns:
- A flattened ReaderResult[A]
func FromEither ¶
func FromEither[A any](e Result[A]) ReaderResult[A]
FromEither lifts a Result (Either[error, A]) into a ReaderResult.
The resulting ReaderResult ignores the context.Context environment and simply returns the Result value. This is useful for converting existing Result values into the ReaderResult monad for composition with other ReaderResult operations.
Type Parameters:
- A: The success value type
Parameters:
- e: A Result[A] (Either[error, A]) to lift
Returns:
- A ReaderResult[A] that ignores the context and returns the Result
Example ¶
ExampleFromEither demonstrates lifting a Result (Either) into a The resulting ReaderResult ignores the context and returns the Result value.
res := RES.Of(42) rr := FromEither(res) value, err := rr(context.Background()) fmt.Println(value, err)
Output: 42 <nil>
func FromReader ¶
FromReader lifts a Reader into a ReaderResult that always succeeds.
The Reader computation is executed and its result is wrapped in a successful Result. This is useful for incorporating Reader computations into ReaderResult pipelines.
Type Parameters:
- A: The value type
Parameters:
- r: A Reader[context.Context, A] to lift
Returns:
- A ReaderResult[A] that executes the Reader and always succeeds
func FromReaderResult ¶
func FromReaderResult[A any](r RS.ReaderResult[A]) ReaderResult[A]
func FromResult ¶
FromResult creates a ReaderResult from a Go-style (value, error) tuple.
This is a convenience function for converting standard Go error handling into the ReaderResult monad. The resulting ReaderResult ignores the context.
Type Parameters:
- A: The value type
Parameters:
- a: The value
- err: The error (nil for success)
Returns:
- A ReaderResult[A] that returns the given value and error
Example ¶
ExampleFromResult demonstrates creating a ReaderResult from a Go-style (value, error) tuple. This is useful for converting standard Go error handling into the ReaderResult monad.
rr := FromResult(42, nil) value, err := rr(context.Background()) fmt.Println(value, err)
Output: 42 <nil>
Example (Error) ¶
ExampleFromResult_error demonstrates creating a ReaderResult from an error case. The resulting ReaderResult will propagate the error when executed.
rr := FromResult(0, errors.New("failed"))
value, err := rr(context.Background())
fmt.Println(value, err != nil)
Output: 0 true
func Left ¶
Left creates a ReaderResult that always fails with the given error.
This is the error constructor for ReaderResult, analogous to Either's Left. The resulting computation ignores the context and immediately returns the error.
Type Parameters:
- A: The success type (for type inference)
Parameters:
- err: The error to return
Returns:
- A ReaderResult[A] that always fails with the given error
Example ¶
ExampleLeft demonstrates creating a ReaderResult that always fails with an error. This is the error constructor for ReaderResult, analogous to Either's Left.
rr := Left[int](errors.New("failed"))
value, err := rr(context.Background())
fmt.Println(value, err != nil)
Output: 0 true
func MonadAlt ¶
func MonadAlt[A any](first ReaderResult[A], second Lazy[ReaderResult[A]]) ReaderResult[A]
func MonadAp ¶
func MonadAp[B, A any](fab ReaderResult[func(A) B], fa ReaderResult[A]) ReaderResult[B]
MonadAp applies a function wrapped in a ReaderResult to a value wrapped in a ReaderResult.
This is the Applicative's ap operation. Both computations are executed concurrently using goroutines, and the context is shared between them. If either computation fails, the entire operation fails. If the context is cancelled, the operation is aborted.
The concurrent execution allows for parallel independent computations, which can improve performance when both operations involve I/O or other blocking operations.
Type Parameters:
- B: The result type after applying the function
- A: The input type to the function
Parameters:
- fab: A ReaderResult containing a function from A to B
- fa: A ReaderResult containing a value of type A
Returns:
- A ReaderResult[B] that applies the function to the value
Example:
// Create a function wrapped in ReaderResult
addTen := readerresult.Right(func(n int) int {
return n + 10
})
// Create a value wrapped in ReaderResult
value := readerresult.Right(32)
// Apply the function to the value
result := readerresult.MonadAp(addTen, value)
output, err := result(ctx) // Returns (42, nil)
Error Handling:
// If the function fails
failedFn := readerresult.Left[func(int) int](errors.New("function error"))
result := readerresult.MonadAp(failedFn, value)
_, err := result(ctx) // Returns function error
// If the value fails
failedValue := readerresult.Left[int](errors.New("value error"))
result := readerresult.MonadAp(addTen, failedValue)
_, err := result(ctx) // Returns value error
Context Cancellation:
ctx, cancel := context.WithCancel(context.Background()) cancel() // Cancel immediately result := readerresult.MonadAp(addTen, value) _, err := result(ctx) // Returns context cancellation error
func MonadBiMap ¶
func MonadBiMap[A, B any](fa ReaderResult[A], f Endomorphism[error], g func(A) B) ReaderResult[B]
func MonadChain ¶
func MonadChain[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[B]
MonadChain sequences two ReaderResult computations where the second depends on the first.
This is the monadic bind operation (flatMap). If the first computation fails, the error is propagated and the second computation is not executed. Both computations share the same context.Context environment.
Type Parameters:
- A: The input value type
- B: The output value type
Parameters:
- ma: The first ReaderResult computation
- f: A Kleisli arrow that produces the second computation based on the first's result
Returns:
- A ReaderResult[B] representing the sequenced computation
func MonadChainEitherK ¶
func MonadChainReaderK ¶
func MonadMap ¶
func MonadMap[A, B any](fa ReaderResult[A], f func(A) B) ReaderResult[B]
MonadMap transforms the success value of a ReaderResult using the given function.
If the ReaderResult fails, the error is propagated unchanged. This is the Functor's map operation for ReaderResult.
Type Parameters:
- A: The input value type
- B: The output value type
Parameters:
- fa: The ReaderResult to transform
- f: The transformation function
Returns:
- A ReaderResult[B] with the transformed value
func MonadMapLeft ¶
func MonadMapLeft[A any](fa ReaderResult[A], f Endomorphism[error]) ReaderResult[A]
func MonadTraverseArray ¶
func MonadTraverseArray[A, B any](as []A, f Kleisli[A, B]) ReaderResult[[]B]
func Of ¶
func Of[A any](a A) ReaderResult[A]
Of creates a ReaderResult that always succeeds with the given value.
This is an alias for Right and represents the Applicative's pure/return operation. The resulting computation ignores the context and immediately returns the value.
Type Parameters:
- A: The value type
Parameters:
- a: The value to wrap
Returns:
- A ReaderResult[A] that always succeeds with the given value
Example ¶
ExampleOf demonstrates the monadic return/pure operation for It creates a ReaderResult that always succeeds with the given value.
rr := Of(42) value, err := rr(context.Background()) fmt.Println(value, err)
Output: 42 <nil>
func Retrying ¶
func Retrying[A any]( policy R.RetryPolicy, action Kleisli[R.RetryStatus, A], check func(A, error) bool, ) ReaderResult[A]
Retrying retries a ReaderResult computation according to a retry policy with context awareness.
This is the idiomatic wrapper around the functional github.com/IBM/fp-go/v2/context/readerresult.Retrying function. It provides a more Go-friendly API by working with (value, error) tuples instead of Result types.
The function implements a retry mechanism for operations that depend on a context.Context and can fail. 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)
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, error). The context passed to the action will be the same context used for retry delays, so cancellation is properly propagated.
check: A predicate function that examines the result value and error, returning 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). The function receives both the value and error from the action's result. Note that context cancellation errors will automatically stop retrying regardless of this function's return value.
Returns:
A ReaderResult[A] that, when executed with a context, will perform the retry logic with context cancellation support and return the final (value, error) tuple.
Type Parameters:
- A: The type of the success value
Context Cancellation:
The retry mechanism respects context cancellation in two ways:
- During retry delays: If the context is cancelled while waiting between retries, the operation stops immediately and returns the context error.
- During action execution: If the action itself checks the context and returns an error due to cancellation, the retry loop will stop (assuming the check function doesn't force a retry on context errors).
Implementation Details:
This function wraps the functional github.com/IBM/fp-go/v2/context/readerresult.Retrying by converting between the idiomatic (value, error) tuple representation and the functional Result[A] representation. The conversion is handled by ToReaderResult and FromReaderResult, ensuring seamless integration with the underlying retry mechanism that uses delayWithCancel to properly handle context cancellation during delays.
Example:
// Create a retry policy: exponential backoff with a cap, limited to 5 retries
policy := retry.Monoid.Concat(
retry.LimitRetries(5),
retry.CapDelay(10*time.Second, retry.ExponentialBackoff(100*time.Millisecond)),
)
// Action that fetches data, with retry status information
fetchData := func(status retry.RetryStatus) ReaderResult[string] {
return func(ctx context.Context) (string, error) {
// Check if context is cancelled
if ctx.Err() != nil {
return "", ctx.Err()
}
// Simulate an HTTP request that might fail
if status.IterNumber < 3 {
return "", fmt.Errorf("temporary error")
}
return "success", nil
}
}
// Check function: retry on any error except context cancellation
shouldRetry := func(val string, err error) bool {
return err != nil && !errors.Is(err, context.Canceled)
}
// Create the retrying computation
retryingFetch := Retrying(policy, fetchData, shouldRetry)
// Execute with a cancellable context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := retryingFetch(ctx)
See also:
- retry.RetryPolicy for available retry policies
- retry.RetryStatus for information passed to the action
- context.Context for context cancellation semantics
- github.com/IBM/fp-go/v2/context/readerresult.Retrying for the underlying functional implementation
func Right ¶
func Right[A any](a A) ReaderResult[A]
Right creates a ReaderResult that always succeeds with the given value.
This is the success constructor for ReaderResult, analogous to Either's Right. The resulting computation ignores the context and immediately returns the value.
Type Parameters:
- A: The value type
Parameters:
- a: The value to return
Returns:
- A ReaderResult[A] that always succeeds with the given value
Example ¶
ExampleRight demonstrates creating a ReaderResult that always succeeds with a value. This is the success constructor for ReaderResult, analogous to Either's Right.
rr := Right(42) value, err := rr(context.Background()) fmt.Println(value, err)
Output: 42 <nil>
func RightReader ¶
func SequenceArray ¶
func SequenceArray[A any](ma []ReaderResult[A]) ReaderResult[[]A]
SequenceArray converts an array of ReaderResult values into a single ReaderResult of an array. If any element fails, the entire operation fails with the first error encountered. All computations share the same environment.
Example:
readers := []readerresult.ReaderResult[Config, int]{
readerresult.Of[Config](1),
readerresult.Of[Config](2),
readerresult.Of[Config](3),
}
result := readerresult.SequenceArray(readers)
// result(cfg) returns ([]int{1, 2, 3}, nil)
func SequenceT1 ¶
SequenceT1 wraps a single ReaderResult in a Tuple1.
This is mainly for consistency with the other SequenceT functions.
Type Parameters:
- A: The value type
Parameters:
- a: A ReaderResult[A]
Returns:
- A ReaderResult[Tuple1[A]]
Example:
rr := readerresult.Right(42)
result := readerresult.SequenceT1(rr)
tuple, err := result(ctx) // Returns (Tuple1{42}, nil)
func SequenceT2 ¶
SequenceT2 combines two independent ReaderResult computations into a tuple.
Both computations are executed with the same context. If either fails, the entire operation fails with the first error encountered.
Type Parameters:
- A: The first value type
- B: The second value type
Parameters:
- a: The first ReaderResult
- b: The second ReaderResult
Returns:
- A ReaderResult[Tuple2[A, B]] containing both results
Example:
getUser := readerresult.Right(User{ID: 1})
getConfig := readerresult.Right(Config{Port: 8080})
result := readerresult.SequenceT2(getUser, getConfig)
tuple, err := result(ctx) // Returns (Tuple2{User, Config}, nil)
func SequenceT3 ¶
func SequenceT3[A, B, C any]( a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], ) ReaderResult[T.Tuple3[A, B, C]]
SequenceT3 combines three independent ReaderResult computations into a tuple.
All computations are executed with the same context. If any fails, the entire operation fails with the first error encountered.
Type Parameters:
- A: The first value type
- B: The second value type
- C: The third value type
Parameters:
- a: The first ReaderResult
- b: The second ReaderResult
- c: The third ReaderResult
Returns:
- A ReaderResult[Tuple3[A, B, C]] containing all three results
func SequenceT4 ¶
func SequenceT4[A, B, C, D any]( a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], d ReaderResult[D], ) ReaderResult[T.Tuple4[A, B, C, D]]
SequenceT4 combines four independent ReaderResult computations into a tuple.
All computations are executed with the same context. If any fails, the entire operation fails with the first error encountered.
Type Parameters:
- A: The first value type
- B: The second value type
- C: The third value type
- D: The fourth value type
Parameters:
- a: The first ReaderResult
- b: The second ReaderResult
- c: The third ReaderResult
- d: The fourth ReaderResult
Returns:
- A ReaderResult[Tuple4[A, B, C, D]] containing all four results
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 underlying computation.
If the context has been cancelled (ctx.Err() != nil), it immediately returns an error containing the cancellation cause without executing the wrapped computation. Otherwise, it delegates to the original ReaderResult.
This is useful for adding cancellation checks to computations that may not check the context themselves, ensuring that cancelled operations fail fast.
Example:
// A computation that might take a long time
slowComputation := func(ctx context.Context) (int, error) {
time.Sleep(5 * time.Second)
return 42, nil
}
// Wrap it to check for cancellation before execution
safeSlow := readerresult.WithContext(slowComputation)
// If context is already cancelled, this returns immediately
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
result, err := safeSlow(ctx) // Returns error immediately without sleeping