Documentation
¶
Overview ¶
Package readerresult provides a ReaderResult monad that combines the Reader and Result monads.
A ReaderResult[R, A] represents a computation that:
- Depends on an environment of type R (Reader aspect)
- May fail with an error (Result aspect, which is Either[error, A])
This is equivalent to Reader[R, Result[A]] or Reader[R, Either[error, A]].
Use Cases ¶
ReaderResult is particularly useful for:
- Dependency injection with error handling - pass configuration/services through computations that may fail
- Functional error handling - compose operations that depend on context and may error
- Testing - easily mock dependencies by changing the environment value
Basic Example ¶
type Config struct {
DatabaseURL string
}
// Function that needs config and may fail
func getUser(id int) readerresult.ReaderResult[Config, User] {
return readerresult.Asks(func(cfg Config) result.Result[User] {
// Use cfg.DatabaseURL to fetch user
return result.Of(user)
})
}
// Execute by providing the config
cfg := Config{DatabaseURL: "postgres://..."}
user, err := getUser(42)(cfg) // Returns (User, error)
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[Config](State{}),
readerresult.Bind(
func(user User) func(State) State {
return func(s State) State { s.User = user; return s }
},
func(s State) readerresult.ReaderResult[Config, 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[Config, []Post] {
return getPosts(s.User.ID)
},
),
)
Object-Oriented Patterns with Curry Functions ¶
The Curry functions enable an interesting pattern where you can treat the Reader context (R) as an object instance, effectively creating method-like functions that compose functionally.
When you curry a function like func(R, T1, T2) (A, error), the context R 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 R is the last curried argument:
- In Go, context conventionally comes first: func(ctx Context, params...) (Result, error)
- In curried form: Curry2(f)(param1)(param2) returns ReaderResult[R, A]
- The ReaderResult is then applied to R: Curry2(f)(param1)(param2)(ctx)
- This allows partial application of business parameters before providing the context/object
Object-Oriented Example:
// A service struct that acts as the Reader context
type UserService struct {
db *sql.DB
cache Cache
}
// A method-like function following Go conventions (context first)
func (s *UserService) GetUserByID(ctx context.Context, id int) (User, error) {
// Use s.db and s.cache...
}
func (s *UserService) UpdateUser(ctx context.Context, id int, name string) (User, error) {
// Use s.db and s.cache...
}
// Curry these into composable operations
getUser := readerresult.Curry1((*UserService).GetUserByID)
updateUser := readerresult.Curry2((*UserService).UpdateUser)
// Now compose operations that will be bound to a UserService instance
type Context struct {
Svc *UserService
}
pipeline := F.Pipe2(
getUser(42), // ReaderResult[Context, User]
readerresult.Chain(func(user User) readerresult.ReaderResult[Context, User] {
newName := user.Name + " (updated)"
return updateUser(user.ID)(newName)
}),
)
// Execute by providing the service instance as context
svc := &UserService{db: db, cache: cache}
ctx := Context{Svc: svc}
updatedUser, err := pipeline(ctx)
The key insight is that currying creates a chain where:
- Business parameters are applied first: getUser(42)
- This returns a ReaderResult that waits for the context
- Multiple operations can be composed before providing the context
- Finally, the context/object is provided to execute everything: pipeline(ctx)
This pattern is particularly useful for:
- Creating reusable operation pipelines independent of service instances
- Testing with mock service instances
- Dependency injection in a functional style
- Composing operations that share the same service context
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[R, A] - ReaderResult without error handling
- Result[A] (Either[error, A]) - error handling without environment
- ReaderEither[R, E, A] - like ReaderResult but with custom error type E
- IOResult[A] - like ReaderResult but with no environment (IO with errors)
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[R, S1, S2, T any](setter func(T) func(S1) S2) func(T, error) Operator[R, S1, S2]
- func BindToEither[R, S1, T any](setter func(T) S1) func(Result[T]) ReaderResult[R, S1]
- func BindToReader[R, S1, T any](setter func(T) S1) func(Reader[R, T]) ReaderResult[R, S1]
- func BindToResult[R, S1, T any](setter func(T) S1) func(T, error) ReaderResult[R, S1]
- func ChainOptionK[R, A, B any](onNone Lazy[error]) func(option.Kleisli[A, B]) Operator[R, A, B]
- func Curry1[R, T1, A any](f func(R, T1) (A, error)) func(T1) ReaderResult[R, A]
- func Curry2[R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1) func(T2) ReaderResult[R, A]
- func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderResult[R, A]
- func Fold[R, A, B any](onLeft reader.Kleisli[R, error, B], onRight reader.Kleisli[R, A, B]) func(ReaderResult[R, A]) Reader[R, B]
- func From0[R, A any](f func(R) (A, error)) func() ReaderResult[R, A]
- func From1[R, T1, A any](f func(R, T1) (A, error)) func(T1) ReaderResult[R, A]
- func From2[R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1, T2) ReaderResult[R, A]
- func From3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderResult[R, A]
- func GetOrElse[R, A any](onLeft reader.Kleisli[R, error, A]) func(ReaderResult[R, A]) Reader[R, A]
- func LetTo[R, S1, S2, T any](setter func(T) func(S1) S2, b T) func(ReaderResult[R, S1]) ReaderResult[R, S2]
- func Local[A, R2, R1 any](f func(R2) R1) func(ReaderResult[R1, A]) ReaderResult[R2, A]
- func Read[A, R any](r R) func(ReaderResult[R, A]) (A, error)
- func Traverse[R2, R1, A, B any](f Kleisli[R1, A, B]) func(ReaderResult[R2, A]) Kleisli[R2, R1, B]
- func TraverseReader[R2, R1, A, B any](f reader.Kleisli[R1, A, B]) func(ReaderResult[R2, A]) Kleisli[R2, R1, B]
- func Uncurry1[R, T1, A any](f func(T1) ReaderResult[R, A]) func(R, T1) (A, error)
- func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) ReaderResult[R, A]) func(R, T1, T2) (A, error)
- func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderResult[R, A]) func(R, T1, T2, T3) (A, error)
- type Either
- type Endomorphism
- type Kleisli
- func FromPredicate[R, A any](pred func(A) bool, onFalse func(A) error) Kleisli[R, A, A]
- func Sequence[R1, R2, A any](ma ReaderResult[R2, ReaderResult[R1, A]]) Kleisli[R2, R1, A]
- func SequenceReader[R1, R2, A any](ma ReaderResult[R2, Reader[R1, A]]) Kleisli[R2, R1, A]
- func TraverseArray[R, A, B any](f Kleisli[R, A, B]) Kleisli[R, []A, []B]
- func TraverseArrayWithIndex[R, A, B any](f func(int, A) ReaderResult[R, B]) Kleisli[R, []A, []B]
- func WithResource[B, R, A, ANY any](onCreate Lazy[ReaderResult[R, A]], onRelease Kleisli[R, A, ANY]) Kleisli[R, Kleisli[R, A, B], B]
- type Lazy
- type Monoid
- type Operator
- func Alt[R, A any](second Lazy[ReaderResult[R, A]]) Operator[R, A, A]
- func Ap[B, R, A any](fa ReaderResult[R, A]) Operator[R, func(A) B, B]
- func ApEitherS[R, S1, S2, T any](setter func(T) func(S1) S2, fa Result[T]) Operator[R, S1, S2]
- func ApReaderS[R, S1, S2, T any](setter func(T) func(S1) S2, fa Reader[R, T]) Operator[R, S1, S2]
- func ApS[R, S1, S2, T any](setter func(T) func(S1) S2, fa ReaderResult[R, T]) Operator[R, S1, S2]
- func ApSL[R, S, T any](lens L.Lens[S, T], fa ReaderResult[R, T]) Operator[R, S, S]
- func BiMap[R, A, B any](f Endomorphism[error], g func(A) B) Operator[R, A, B]
- func Bind[R, S1, S2, T any](setter func(T) func(S1) S2, f Kleisli[R, S1, T]) Operator[R, S1, S2]
- func BindEitherK[R, S1, S2, T any](setter func(T) func(S1) S2, f RES.Kleisli[S1, T]) Operator[R, S1, S2]
- func BindL[R, S, T any](lens L.Lens[S, T], f Kleisli[R, T, T]) Operator[R, S, S]
- func BindReaderK[R, S1, S2, T any](setter func(T) func(S1) S2, f reader.Kleisli[R, S1, T]) Operator[R, S1, S2]
- func BindResultK[R, S1, S2, T any](setter func(T) func(S1) S2, f result.Kleisli[S1, T]) Operator[R, S1, S2]
- func BindTo[R, S1, T any](setter func(T) S1) Operator[R, T, S1]
- func Chain[R, A, B any](f Kleisli[R, A, B]) Operator[R, A, B]
- func ChainEitherK[R, A, B any](f RES.Kleisli[A, B]) Operator[R, A, B]
- func ChainReaderK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, B]
- func Flap[R, B, A any](a A) Operator[R, func(A) B, B]
- func Let[R, S1, S2, T any](setter func(T) func(S1) S2, f func(S1) T) Operator[R, S1, S2]
- func LetL[R, S, T any](lens L.Lens[S, T], f Endomorphism[T]) Operator[R, S, S]
- func LetToL[R, S, T any](lens L.Lens[S, T], b T) Operator[R, S, S]
- func Map[R, A, B any](f func(A) B) Operator[R, A, B]
- func MapLeft[R, A any](f Endomorphism[error]) Operator[R, A, A]
- func OrElse[R, A any](onLeft Kleisli[R, error, A]) Operator[R, A, A]
- func OrLeft[A, R any](onLeft reader.Kleisli[R, error, error]) Operator[R, A, A]
- type Option
- type Reader
- type ReaderResult
- func Ask[R any]() ReaderResult[R, R]
- func Asks[R, A any](r Reader[R, A]) ReaderResult[R, A]
- func Bracket[R, A, B, ANY any](acquire Lazy[ReaderResult[R, A]], use Kleisli[R, A, B], ...) ReaderResult[R, B]
- func Curry0[R, A any](f func(R) (A, error)) ReaderResult[R, A]
- func Do[R, S any](empty S) ReaderResult[R, S]
- func Flatten[R, A any](mma ReaderResult[R, ReaderResult[R, A]]) ReaderResult[R, A]
- func FromEither[R, A any](e Result[A]) ReaderResult[R, A]
- func FromReader[R, A any](r Reader[R, A]) ReaderResult[R, A]
- func FromResult[R, A any](a A, err error) ReaderResult[R, A]
- func Left[R, A any](err error) ReaderResult[R, A]
- func LeftReader[A, R any](l Reader[R, error]) ReaderResult[R, A]
- func MonadAlt[R, A any](first ReaderResult[R, A], second Lazy[ReaderResult[R, A]]) ReaderResult[R, A]
- func MonadAp[B, R, A any](fab ReaderResult[R, func(A) B], fa ReaderResult[R, A]) ReaderResult[R, B]
- func MonadBiMap[R, A, B any](fa ReaderResult[R, A], f Endomorphism[error], g func(A) B) ReaderResult[R, B]
- func MonadChain[R, A, B any](ma ReaderResult[R, A], f Kleisli[R, A, B]) ReaderResult[R, B]
- func MonadChainEitherK[R, A, B any](ma ReaderResult[R, A], f RES.Kleisli[A, B]) ReaderResult[R, B]
- func MonadChainReaderK[R, A, B any](ma ReaderResult[R, A], f result.Kleisli[A, B]) ReaderResult[R, B]
- func MonadFlap[R, A, B any](fab ReaderResult[R, func(A) B], a A) ReaderResult[R, B]
- func MonadMap[R, A, B any](fa ReaderResult[R, A], f func(A) B) ReaderResult[R, B]
- func MonadMapLeft[R, A any](fa ReaderResult[R, A], f Endomorphism[error]) ReaderResult[R, A]
- func MonadTraverseArray[R, A, B any](as []A, f Kleisli[R, A, B]) ReaderResult[R, []B]
- func Of[R, A any](a A) ReaderResult[R, A]
- func Right[R, A any](a A) ReaderResult[R, A]
- func RightReader[R, A any](rdr Reader[R, A]) ReaderResult[R, A]
- func SequenceArray[R, A any](ma []ReaderResult[R, A]) ReaderResult[R, []A]
- func SequenceT1[R, A any](a ReaderResult[R, A]) ReaderResult[R, T.Tuple1[A]]
- func SequenceT2[R, A, B any](a ReaderResult[R, A], b ReaderResult[R, B]) ReaderResult[R, T.Tuple2[A, B]]
- func SequenceT3[R, A, B, C any](a ReaderResult[R, A], b ReaderResult[R, B], c ReaderResult[R, C]) ReaderResult[R, T.Tuple3[A, B, C]]
- func SequenceT4[R, A, B, C, D any](a ReaderResult[R, A], b ReaderResult[R, B], c ReaderResult[R, C], ...) ReaderResult[R, T.Tuple4[A, B, C, D]]
- type Result
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BindToEither ¶
func BindToEither[ R, S1, T any]( setter func(T) S1, ) func(Result[T]) ReaderResult[R, S1]
func BindToReader ¶
func BindToReader[ R, S1, T any]( setter func(T) S1, ) func(Reader[R, T]) ReaderResult[R, S1]
BindToReader initializes a new state S1 from a Reader[R, T] computation. This is used to start a ReaderResult computation chain from a pure Reader value.
The setter function takes the result T from the Reader and initializes the state S1. This is useful when you want to begin a do-notation chain with a Reader computation that doesn't involve error handling.
Example:
type Env struct {
ConfigPath string
}
type State struct {
Config string
}
// A Reader that just reads from the environment
getConfigPath := func(env Env) string {
return env.ConfigPath
}
result := function.Pipe1(
reader.Of[Env](getConfigPath),
readerresult.BindToReader(func(path string) State {
return State{Config: path}
}),
)
func BindToResult ¶
BindToResult initializes a new state S1 from a Result[T] value. This is used to start a ReaderResult computation chain from a Result that may contain an error.
The setter function takes the successful result T and initializes the state S1. If the Result is an error, the entire computation will carry that error forward. This is useful when you want to begin a do-notation chain with a Result computation that doesn't need environment access.
Example:
type State struct {
Value int
}
// A Result that might contain an error
parseResult := result.TryCatch(func() int {
// some parsing logic that might fail
return 42
})
computation := function.Pipe1(
parseResult,
readerresult.BindToResult[any](func(value int) State {
return State{Value: value}
}),
)
func ChainOptionK ¶
ChainOptionK chains with a function that returns an Option, converting None to an error. This is useful for integrating functions that return optional values.
Example:
findUser := func(id int) option.Option[User] { ... }
notFound := func() error { return errors.New("user not found") }
chain := readerresult.ChainOptionK[Config, int, User](notFound)
result := F.Pipe1(readerresult.Of[Config](42), chain(findUser))
func Curry1 ¶
Curry1 converts a function with one parameter into a curried function returning a ReaderResult.
Example:
getUser := func(ctx context.Context, id int) (User, error) { ... }
curried := readerresult.Curry1(getUser)
// curried(42) returns ReaderResult[context.Context, User]
func Curry2 ¶
Curry2 converts a function with two parameters into a fully curried function. Each parameter is applied one at a time.
Example:
queryDB := func(ctx context.Context, table string, id int) (Record, error) { ... }
curried := readerresult.Curry2(queryDB)
// curried("users")(42) returns ReaderResult[context.Context, Record]
func Curry3 ¶
func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderResult[R, A]
Curry3 converts a function with three parameters into a fully curried function.
Example:
updateRecord := func(ctx context.Context, table string, id int, data string) (Result, error) { ... }
curried := readerresult.Curry3(updateRecord)
// curried("users")(42)("data") returns ReaderResult[context.Context, Result]
func Fold ¶
func Fold[R, A, B any](onLeft reader.Kleisli[R, error, B], onRight reader.Kleisli[R, A, B]) func(ReaderResult[R, A]) Reader[R, B]
Fold handles both success and failure cases by providing functions for each. The result is always a Reader[R, B] (without the error channel). This is useful for converting a ReaderResult into a plain Reader by handling the error case.
Example:
handleError := func(err error) reader.Reader[Config, string] {
return func(cfg Config) string { return "Error: " + err.Error() }
}
handleSuccess := func(user User) reader.Reader[Config, string] {
return func(cfg Config) string { return user.Name }
}
result := readerresult.Fold[Config, User, string](handleError, handleSuccess)(getUserRR)
func From0 ¶
From0 converts a function that takes only a context and returns (A, error) into a ReaderResult.
Example:
getConfig := func(ctx context.Context) (Config, error) { ... }
rr := readerresult.From0(getConfig)()
// rr is a ReaderResult[context.Context, Config]
func From1 ¶
From1 converts a function with one parameter into a ReaderResult-returning function. The context parameter is moved to the end (ReaderResult style).
Example:
getUser := func(ctx context.Context, id int) (User, error) { ... }
rr := readerresult.From1(getUser)
// rr(42) returns ReaderResult[context.Context, User]
func From2 ¶
From2 converts a function with two parameters into a ReaderResult-returning function. The context parameter is moved to the end (ReaderResult style).
Example:
queryDB := func(ctx context.Context, table string, id int) (Record, error) { ... }
rr := readerresult.From2(queryDB)
// rr("users", 42) returns ReaderResult[context.Context, Record]
func From3 ¶
func From3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderResult[R, A]
From3 converts a function with three parameters into a ReaderResult-returning function. The context parameter is moved to the end (ReaderResult style).
Example:
updateRecord := func(ctx context.Context, table string, id int, data string) (Result, error) { ... }
rr := readerresult.From3(updateRecord)
// rr("users", 42, "data") returns ReaderResult[context.Context, Result]
func GetOrElse ¶
GetOrElse extracts the success value or computes a default value from the error. The result is a Reader[R, A] that always succeeds.
Example:
defaultUser := func(err error) reader.Reader[Config, User] {
return func(cfg Config) User { return User{Name: "Guest"} }
}
result := readerresult.GetOrElse[Config](defaultUser)(getUserRR)
func LetTo ¶
func LetTo[R, S1, S2, T any]( setter func(T) func(S1) S2, b T, ) func(ReaderResult[R, S1]) ReaderResult[R, S2]
LetTo attaches the a value to a context [S1] to produce a context [S2]
func Local ¶
func Local[A, R2, R1 any](f func(R2) R1) func(ReaderResult[R1, A]) ReaderResult[R2, A]
Local changes the environment type during execution of a ReaderResult. This is similar to Contravariant's contramap and allows adapting computations to work with different environment types.
Example:
// Convert DB environment to Config environment
toConfig := func(db DB) Config { return db.Config }
rr := readerresult.Of[Config](42)
adapted := readerresult.Local[int](toConfig)(rr)
// adapted now accepts DB instead of Config
func Read ¶
Read applies an environment value to a ReaderResult to execute it and obtain the Result. This is the primary way to "run" a ReaderResult computation.
Example:
rr := readerresult.Asks(func(cfg Config) int { return cfg.Port })
run := readerresult.Read[int](myConfig)
port, err := run(rr) // Returns (int, error)
func Traverse ¶
func Traverse[R2, R1, A, B any]( f Kleisli[R1, A, B], ) func(ReaderResult[R2, A]) Kleisli[R2, R1, B]
Traverse transforms a ReaderResult computation by applying a Kleisli arrow that introduces a new environment dependency, effectively swapping the environment order.
This is a higher-order function that takes a Kleisli arrow and returns a function that can transform ReaderResult computations. It's useful for introducing environment-dependent transformations into existing computations while reordering the environment parameters.
Type Parameters:
- R2: The type of the original computation's environment
- R1: The type of the new environment introduced by the Kleisli arrow
- A: The input type to the Kleisli arrow
- B: The output type of the transformation
Parameters:
- f: A Kleisli arrow (func(A) ReaderResult[R1, B]) that transforms A to B with R1 dependency
Returns:
- A function that transforms ReaderResult[R2, A] into a Kleisli arrow with swapped environments
The transformation preserves error handling from both the original computation and the Kleisli arrow. The resulting computation takes R1 first, then R2.
Example:
type Database struct {
Prefix string
}
// Original computation: depends on int environment
original := func(x int) (int, error) {
if x < 0 {
return 0, errors.New("negative value")
}
return x * 2, nil
}
// Kleisli arrow: transforms int to string with Database dependency
format := func(value int) func(Database) (string, error) {
return func(db Database) (string, error) {
return fmt.Sprintf("%s:%d", db.Prefix, value), nil
}
}
// Apply Traverse
traversed := Traverse[int](format)
result := traversed(original)
// Use with Database first, then int
db := Database{Prefix: "ID"}
output, err := result(db)(10)
// output: "ID:20", err: nil
func TraverseReader ¶
func TraverseReader[R2, R1, A, B any]( f reader.Kleisli[R1, A, B], ) func(ReaderResult[R2, A]) Kleisli[R2, R1, B]
TraverseReader transforms a ReaderResult computation by applying a Reader-based Kleisli arrow, introducing a new environment dependency while swapping the environment order.
This function is similar to Traverse but specialized for pure Reader transformations that cannot fail. It's useful when you want to introduce environment-dependent logic without adding error handling complexity.
Type Parameters:
- R2: The type of the original computation's environment
- R1: The type of the new environment introduced by the Reader Kleisli arrow
- A: The input type to the Kleisli arrow
- B: The output type of the transformation
Parameters:
- f: A Reader Kleisli arrow (func(A) func(R1) B) that transforms A to B with R1 dependency
Returns:
- A function that transforms ReaderResult[R2, A] into a Kleisli arrow with swapped environments
The Reader transformation is automatically lifted into the Result context. Only the original ReaderResult computation can fail; the Reader transformation itself is pure and cannot fail.
Example:
type Config struct {
Multiplier int
}
// Original computation: depends on int environment, may fail
original := func(x int) (int, error) {
if x < 0 {
return 0, errors.New("negative value")
}
return x * 2, nil
}
// Pure Reader transformation: multiplies by config value
multiply := func(value int) func(Config) int {
return func(cfg Config) int {
return value * cfg.Multiplier
}
}
// Apply TraverseReader
traversed := TraverseReader[int, Config, error](multiply)
result := traversed(original)
// Use with Config first, then int
cfg := Config{Multiplier: 5}
output, err := result(cfg)(10)
// output: 100 (10 * 2 * 5), err: nil
func Uncurry1 ¶
Uncurry1 converts a ReaderResult-returning function back into an idiomatic Go function. This is useful for adapting functional code to work with traditional Go APIs.
Example:
rrf := func(id int) readerresult.ReaderResult[context.Context, User] { ... }
gofunc := readerresult.Uncurry1(rrf)
// gofunc(ctx, 42) returns (User, error)
func Uncurry2 ¶
Uncurry2 converts a curried two-parameter ReaderResult function into an idiomatic Go function.
Example:
rrf := func(table string) func(int) readerresult.ReaderResult[context.Context, Record] { ... }
gofunc := readerresult.Uncurry2(rrf)
// gofunc(ctx, "users", 42) returns (Record, error)
func Uncurry3 ¶
func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderResult[R, A]) func(R, T1, T2, T3) (A, error)
Uncurry3 converts a curried three-parameter ReaderResult function into an idiomatic Go function.
Example:
rrf := func(table string) func(int) func(string) readerresult.ReaderResult[context.Context, Result] { ... }
gofunc := readerresult.Uncurry3(rrf)
// gofunc(ctx, "users", 42, "data") returns (Result, 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[R, A, B any] = Reader[A, ReaderResult[R, B]]
Kleisli represents a function from A to a ReaderResult of B. It is used for chaining computations that depend on environment and may fail.
func FromPredicate ¶
FromPredicate creates a Kleisli arrow that tests a predicate and returns either the input value or an error. If the predicate returns true, the value is returned as a success. If false, the onFalse function is called to generate an error.
Example:
isPositive := readerresult.FromPredicate[Config](
func(x int) bool { return x > 0 },
func(x int) error { return fmt.Errorf("%d is not positive", x) },
)
result := isPositive(5) // Returns ReaderResult that succeeds with 5
result := isPositive(-1) // Returns ReaderResult that fails with error
func Sequence ¶
func Sequence[R1, R2, A any](ma ReaderResult[R2, ReaderResult[R1, A]]) Kleisli[R2, R1, A]
Sequence swaps the order of nested environment parameters in a ReaderResult computation.
This function transforms a computation that takes environment R2 and produces a ReaderResult[R1, A] into a Kleisli arrow that takes R1 first and returns a ReaderResult[R2, A].
Type Parameters:
- R1: The type of the inner environment (becomes the outer parameter after sequencing)
- R2: The type of the outer environment (becomes the inner environment after sequencing)
- A: The type of the value produced by the computation
Parameters:
- ma: A ReaderResult that depends on R2 and produces a ReaderResult[R1, A]
Returns:
- A Kleisli arrow (func(R1) ReaderResult[R2, A]) that reverses the environment order
The transformation preserves error handling - if the outer computation fails, the error is propagated; if the inner computation fails, that error is also propagated.
Example:
import S "github.com/IBM/fp-go/v2/string"
type Database struct {
ConnectionString string
}
type Config struct {
Timeout int
}
// Original: takes Config, produces ReaderResult[Database, string]
original := func(cfg Config) (func(Database) (string, error), error) {
if cfg.Timeout <= 0 {
return nil, errors.New("invalid timeout")
}
return func(db Database) (string, error) {
if S.IsEmpty(db.ConnectionString) {
return "", errors.New("empty connection")
}
return fmt.Sprintf("Query on %s with timeout %d",
db.ConnectionString, cfg.Timeout), nil
}, nil
}
// Sequenced: takes Database first, then Config
sequenced := Sequence(original)
db := Database{ConnectionString: "localhost:5432"}
cfg := Config{Timeout: 30}
result, err := sequenced(db)(cfg)
// result: "Query on localhost:5432 with timeout 30"
func SequenceReader ¶
func SequenceReader[R1, R2, A any](ma ReaderResult[R2, Reader[R1, A]]) Kleisli[R2, R1, A]
SequenceReader swaps the order of environment parameters when the inner computation is a pure Reader.
This function is similar to Sequence but specialized for cases where the inner computation is a Reader (pure function) rather than a ReaderResult. It transforms a ReaderResult that produces a Reader into a Kleisli arrow with swapped environment order.
Type Parameters:
- R1: The type of the Reader's environment (becomes the outer parameter after sequencing)
- R2: The type of the ReaderResult's environment (becomes the inner environment after sequencing)
- A: The type of the value produced by the computation
Parameters:
- ma: A ReaderResult[R2, Reader[R1, A]] - depends on R2 and produces a pure Reader
Returns:
- A Kleisli arrow (func(R1) ReaderResult[R2, A]) that reverses the environment order
The inner Reader computation is automatically lifted into the Result context (cannot fail). Only the outer ReaderResult can fail with an error.
Example:
type Config struct {
Multiplier int
}
// Original: takes int, produces Reader[Config, int]
original := func(x int) (func(Config) int, error) {
if x < 0 {
return nil, errors.New("negative value")
}
return func(cfg Config) int {
return x * cfg.Multiplier
}, nil
}
// Sequenced: takes Config first, then int
sequenced := SequenceReader(original)
cfg := Config{Multiplier: 5}
result, err := sequenced(cfg)(10)
// result: 50, err: nil
func TraverseArray ¶
func TraverseArray[R, A, B any](f Kleisli[R, A, B]) Kleisli[R, []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 WithResource ¶
func WithResource[B, R, A, ANY any]( onCreate Lazy[ReaderResult[R, A]], onRelease Kleisli[R, A, ANY], ) Kleisli[R, Kleisli[R, A, B], B]
type Monoid ¶
type Monoid[R, A any] = monoid.Monoid[ReaderResult[R, A]]
Monoid represents a monoid structure for ReaderResult values.
func AltMonoid ¶
func AltMonoid[R, A any](zero Lazy[ReaderResult[R, A]]) Monoid[R, A]
AltMonoid creates a Monoid for ReaderResult based on the Alternative pattern. The empty element is the provided zero computation, and concat tries the first computation, falling back to the second if it fails.
This is useful for combining computations where you want to try alternatives until one succeeds.
Example:
zero := func() readerresult.ReaderResult[Config, User] {
return readerresult.Left[Config, User](errors.New("no user"))
}
userMonoid := readerresult.AltMonoid[Config](zero)
primary := getPrimaryUser()
backup := getBackupUser()
combined := userMonoid.Concat(primary, backup)
// Tries primary, falls back to backup if primary fails
func AlternativeMonoid ¶
AlternativeMonoid creates a Monoid for ReaderResult that combines both Alternative and Applicative behavior. It uses the provided monoid for the success values and falls back to alternative computations on failure.
The empty element is Of(m.Empty()), and concat tries the first computation, falling back to the second if it fails, then combines successful values using the underlying monoid.
Example:
intAdd := monoid.MakeMonoid(0, func(a, b int) int { return a + b })
rrMonoid := readerresult.AlternativeMonoid[Config](intAdd)
rr1 := readerresult.Of[Config](5)
rr2 := readerresult.Of[Config](3)
combined := rrMonoid.Concat(rr1, rr2)
// combined(cfg) returns (8, nil)
func ApplicativeMonoid ¶
ApplicativeMonoid creates a Monoid for ReaderResult based on Applicative functor composition. The empty element is Of(m.Empty()), and concat combines two computations using the underlying monoid. Both computations must succeed for the result to succeed.
This is useful for accumulating results from multiple independent computations.
Example:
intAdd := monoid.MakeMonoid(0, func(a, b int) int { return a + b })
rrMonoid := readerresult.ApplicativeMonoid[Config](intAdd)
rr1 := readerresult.Of[Config](5)
rr2 := readerresult.Of[Config](3)
combined := rrMonoid.Concat(rr1, rr2)
// combined(cfg) returns (8, nil)
// If either fails, the whole computation fails
rr3 := readerresult.Left[Config, int](errors.New("error"))
failed := rrMonoid.Concat(rr1, rr3)
// failed(cfg) returns (nil, error)
type Operator ¶
type Operator[R, A, B any] = Kleisli[R, ReaderResult[R, A], B]
Operator represents a transformation from ReaderResult[R, A] to ReaderResult[R, B]. It is commonly used in function composition pipelines.
func Alt ¶
func Alt[R, A any](second Lazy[ReaderResult[R, A]]) Operator[R, A, A]
Alt tries the first computation, and if it fails, tries the second. This implements the Alternative pattern for error recovery.
func Ap ¶
func Ap[B, R, A any](fa ReaderResult[R, A]) Operator[R, func(A) B, B]
Ap is the curried version of MonadAp. It returns an Operator that can be used in function composition pipelines.
func ApEitherS ¶
func ApEitherS[ R, S1, S2, T any]( setter func(T) func(S1) S2, fa Result[T], ) Operator[R, S1, S2]
ApResultS attaches a value from a Result to a context [S1] to produce a context [S2] using Applicative semantics (independent, non-sequential composition).
Unlike BindResultK which uses monadic bind (sequential), ApResultS uses applicative apply, meaning the Result computation fa is independent of the current state and can conceptually execute in parallel.
If the Result fa contains an error, the entire computation short-circuits with that error. This is useful when you want to combine a Result value with your ReaderResult state without creating a dependency between them.
Example:
type State struct {
Value1 int
Value2 int
}
// Independent Result computations
parseValue1 := result.TryCatch(func() int { return 42 })
parseValue2 := result.TryCatch(func() int { return 100 })
computation := function.Pipe2(
readerresult.Do[any](State{}),
readerresult.ApResultS(
func(v int) func(State) State {
return func(s State) State { s.Value1 = v; return s }
},
parseValue1,
),
readerresult.ApResultS(
func(v int) func(State) State {
return func(s State) State { s.Value2 = v; return s }
},
parseValue2,
),
)
func ApReaderS ¶
func ApReaderS[ R, S1, S2, T any]( setter func(T) func(S1) S2, fa Reader[R, T], ) Operator[R, S1, S2]
ApReaderS attaches a value from a pure Reader computation to a context [S1] to produce a context [S2] using Applicative semantics (independent, non-sequential composition).
Unlike BindReaderK which uses monadic bind (sequential), ApReaderS uses applicative apply, meaning the Reader computation fa is independent of the current state and can conceptually execute in parallel.
This is useful when you want to combine a Reader computation with your ReaderResult state without creating a dependency between them.
Example:
type Env struct {
DefaultPort int
DefaultHost string
}
type State struct {
Port int
Host string
}
getDefaultPort := func(env Env) int { return env.DefaultPort }
getDefaultHost := func(env Env) string { return env.DefaultHost }
result := function.Pipe2(
readerresult.Do[Env](State{}),
readerresult.ApReaderS(
func(port int) func(State) State {
return func(s State) State { s.Port = port; return s }
},
getDefaultPort,
),
readerresult.ApReaderS(
func(host string) func(State) State {
return func(s State) State { s.Host = host; return s }
},
getDefaultHost,
),
)
func ApS ¶
func ApS[R, S1, S2, T any]( setter func(T) func(S1) S2, fa ReaderResult[R, T], ) Operator[R, S1, S2]
ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently (using Applicative rather than Monad). This allows independent computations to be combined without one depending on the result of the other.
Unlike Bind, which sequences operations, ApS can be used when operations are independent and can conceptually run in parallel.
Example:
type State struct {
User User
Config Config
}
type Env struct {
UserService UserService
ConfigService ConfigService
}
// These operations are independent and can be combined with ApS
getUser := readereither.Asks(func(env Env) either.Either[error, User] {
return env.UserService.GetUser()
})
getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
return env.ConfigService.GetConfig()
})
result := function.Pipe2(
readereither.Do[Env, error](State{}),
readereither.ApS(
func(user User) func(State) State {
return func(s State) State { s.User = user; return s }
},
getUser,
),
readereither.ApS(
func(cfg Config) func(State) State {
return func(s State) State { s.Config = cfg; return s }
},
getConfig,
),
)
func ApSL ¶
ApSL attaches a value to a context using a lens-based setter. This is a convenience function that combines ApS with a lens, allowing you to use optics to update nested structures in a more composable way.
The lens parameter provides both the getter and setter for a field within the structure S. This eliminates the need to manually write setter functions.
Example:
type State struct {
User User
Config Config
}
type Env struct {
UserService UserService
ConfigService ConfigService
}
configLens := lens.MakeLens(
func(s State) Config { return s.Config },
func(s State, c Config) State { s.Config = c; return s },
)
getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
return env.ConfigService.GetConfig()
})
result := function.Pipe2(
readereither.Of[Env, error](State{}),
readereither.ApSL(configLens, getConfig),
)
func BiMap ¶
func BiMap[R, A, B any](f Endomorphism[error], g func(A) B) Operator[R, A, B]
BiMap is the curried version of MonadBiMap. It maps a pair of functions over the error and success channels.
Example:
enrichErr := func(e error) error { return fmt.Errorf("enriched: %w", e) }
double := N.Mul(2)
result := F.Pipe1(rr, readerresult.BiMap[Config](enrichErr, double))
func Bind ¶
func Bind[R, S1, S2, T any]( setter func(T) func(S1) S2, f Kleisli[R, S1, T], ) Operator[R, S1, S2]
Bind attaches the result of a computation to a context [S1] to produce a context [S2]. This enables sequential composition where each step can depend on the results of previous steps and access the shared environment.
The setter function takes the result of the computation and returns a function that updates the context from S1 to S2.
Example:
type State struct {
User User
Config Config
}
type Env struct {
UserService UserService
ConfigService ConfigService
}
result := function.Pipe2(
readereither.Do[Env, error](State{}),
readereither.Bind(
func(user User) func(State) State {
return func(s State) State { s.User = user; return s }
},
func(s State) readereither.ReaderResult[Env, error, User] {
return readereither.Asks(func(env Env) either.Either[error, User] {
return env.UserService.GetUser()
})
},
),
readereither.Bind(
func(cfg Config) func(State) State {
return func(s State) State { s.Config = cfg; return s }
},
func(s State) readereither.ReaderResult[Env, error, Config] {
// This can access s.User from the previous step
return readereither.Asks(func(env Env) either.Either[error, Config] {
return env.ConfigService.GetConfigForUser(s.User.ID)
})
},
),
)
func BindEitherK ¶
func BindL ¶
BindL is a variant of Bind that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.
The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a ReaderEither computation that produces an updated value.
Example:
type State struct {
User User
Config Config
}
type Env struct {
UserService UserService
ConfigService ConfigService
}
userLens := lens.MakeLens(
func(s State) User { return s.User },
func(s State, u User) State { s.User = u; return s },
)
result := function.Pipe2(
readereither.Do[Env, error](State{}),
readereither.BindL(userLens, func(user User) readereither.ReaderResult[Env, error, User] {
return readereither.Asks(func(env Env) either.Either[error, User] {
return env.UserService.GetUser()
})
}),
)
func BindReaderK ¶
func BindReaderK[R, S1, S2, T any]( setter func(T) func(S1) S2, f reader.Kleisli[R, S1, T], ) Operator[R, S1, S2]
BindReaderK lifts a Reader Kleisli arrow into a ReaderResult context and binds it to the state. This allows you to integrate pure Reader computations (that don't have error handling) into a ReaderResult computation chain.
The function f takes the current state S1 and returns a Reader[R, T] computation. The result T is then attached to the state using the setter to produce state S2.
Example:
type Env struct {
ConfigPath string
}
type State struct {
Config string
}
// A pure Reader computation that reads from environment
getConfigPath := func(s State) reader.Reader[Env, string] {
return func(env Env) string {
return env.ConfigPath
}
}
result := function.Pipe2(
readerresult.Do[Env](State{}),
readerresult.BindReaderK(
func(path string) func(State) State {
return func(s State) State { s.Config = path; return s }
},
getConfigPath,
),
)
func BindResultK ¶
func BindResultK[R, S1, S2, T any]( setter func(T) func(S1) S2, f result.Kleisli[S1, T], ) Operator[R, S1, S2]
BindResultK lifts a Result Kleisli arrow into a ReaderResult context and binds it to the state. This allows you to integrate Result computations (that may fail with an error but don't need environment access) into a ReaderResult computation chain.
The function f takes the current state S1 and returns a Result[T] computation. If the Result is successful, the value T is attached to the state using the setter to produce state S2. If the Result is an error, the entire computation short-circuits with that error.
Example:
type State struct {
Value int
ParsedValue int
}
// A Result computation that may fail
parseValue := func(s State) result.Result[int] {
if s.Value < 0 {
return result.Error[int](errors.New("negative value"))
}
return result.Of(s.Value * 2)
}
result := function.Pipe2(
readerresult.Do[any](State{Value: 5}),
readerresult.BindResultK(
func(parsed int) func(State) State {
return func(s State) State { s.ParsedValue = parsed; return s }
},
parseValue,
),
)
func BindTo ¶
func BindTo[R, S1, T any]( setter func(T) S1, ) Operator[R, T, S1]
BindTo initializes a new state [S1] from a value [T]
func Chain ¶
func Chain[R, A, B any](f Kleisli[R, A, B]) Operator[R, A, B]
Chain is the curried version of MonadChain. It returns an Operator that can be used in function composition pipelines.
Example:
getPosts := func(user User) readerresult.ReaderResult[DB, []Post] { ... }
result := F.Pipe1(getUser(42), readerresult.Chain[DB](getPosts))
func ChainEitherK ¶
ChainEitherK is the curried version of MonadChainEitherK. It lifts a Result-returning function into a ReaderResult operator.
Example:
parseUser := func(data string) result.Result[User] { ... }
result := F.Pipe1(getUserDataRR, readerresult.ChainEitherK[Config](parseUser))
func ChainReaderK ¶
ChainReaderK is the curried version of MonadChainEitherK. It lifts a Result-returning function into a ReaderResult operator.
Example:
parseUser := func(data string) result.Result[User] { ... }
result := F.Pipe1(getUserDataRR, readerresult.ChainReaderK[Config](parseUser))
func Flap ¶
func Flap[R, B, A any](a A) Operator[R, func(A) B, B]
Flap is the curried version of MonadFlap.
func Let ¶
func Let[R, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) T, ) Operator[R, S1, S2]
Let attaches the result of a computation to a context [S1] to produce a context [S2]
func LetL ¶
func LetL[R, S, T any]( lens L.Lens[S, T], f Endomorphism[T], ) Operator[R, S, S]
LetL is a variant of Let that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.
The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a new value (without wrapping in a ReaderEither).
Example:
type State struct {
User User
Config Config
}
configLens := lens.MakeLens(
func(s State) Config { return s.Config },
func(s State, c Config) State { s.Config = c; return s },
)
result := function.Pipe2(
readereither.Do[any, error](State{Config: Config{Host: "localhost"}}),
readereither.LetL(configLens, func(cfg Config) Config {
cfg.Port = 8080
return cfg
}),
)
func LetToL ¶
LetToL is a variant of LetTo that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.
The lens parameter provides both a getter and setter for a field of type T within the context S. The value b is set directly to the focused field.
Example:
type State struct {
User User
Config Config
}
configLens := lens.MakeLens(
func(s State) Config { return s.Config },
func(s State, c Config) State { s.Config = c; return s },
)
newConfig := Config{Host: "localhost", Port: 8080}
result := function.Pipe2(
readereither.Do[any, error](State{}),
readereither.LetToL(configLens, newConfig),
)
func Map ¶
func Map[R, A, B any](f func(A) B) Operator[R, A, B]
Map is the curried version of MonadMap. It returns an Operator that can be used in function composition pipelines.
Example:
double := readerresult.Map[Config](N.Mul(2)) result := F.Pipe1(readerresult.Of[Config](5), double)
func MapLeft ¶
func MapLeft[R, A any](f Endomorphism[error]) Operator[R, A, A]
MapLeft is the curried version of MonadMapLeft. It applies a mapping function to the error channel only.
Example:
enrichErr := func(e error) error { return fmt.Errorf("DB error: %w", e) }
result := F.Pipe1(getUserRR, readerresult.MapLeft[Config](enrichErr))
func OrElse ¶
OrElse provides an alternative ReaderResult computation if the first one fails. This is useful for fallback logic or retry scenarios.
Example:
getPrimaryUser := func(id int) readerresult.ReaderResult[DB, User] { ... }
getBackupUser := func(err error) readerresult.ReaderResult[DB, User] {
return readerresult.Of[DB](User{Name: "Guest"})
}
result := F.Pipe1(getPrimaryUser(42), readerresult.OrElse[DB](getBackupUser))
func OrLeft ¶
OrLeft transforms the error value if the computation fails, leaving successful values unchanged. This is useful for error mapping or enriching error information.
Example:
enrichError := func(err error) reader.Reader[Config, error] {
return func(cfg Config) error {
return fmt.Errorf("DB error on %s: %w", cfg.DBHost, err)
}
}
result := F.Pipe1(getUserRR, readerresult.OrLeft[Config](enrichError))
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 an environment R and may fail with an error. It is equivalent to Reader[R, Result[A]] or func(R) (A, error). This combines dependency injection with error handling in a functional style.
func Ask ¶
func Ask[R any]() ReaderResult[R, R]
Ask retrieves the current environment as a successful ReaderResult. This is useful for accessing configuration or context within a ReaderResult computation.
Example:
result := F.Pipe1(
readerresult.Ask[Config](),
readerresult.Map[Config](func(cfg Config) int { return cfg.Port }),
)
// result(cfg) returns (cfg.Port, nil)
func Asks ¶
func Asks[R, A any](r Reader[R, A]) ReaderResult[R, A]
Asks retrieves a value from the environment using the provided Reader function. This lifts a Reader computation into a ReaderResult that always succeeds.
Example:
getPort := func(cfg Config) int { return cfg.Port }
result := readerresult.Asks[Config](getPort)
// result(cfg) returns (cfg.Port, nil)
func Bracket ¶
func Bracket[ R, A, B, ANY any]( acquire Lazy[ReaderResult[R, A]], use Kleisli[R, A, B], release func(A, B, error) ReaderResult[R, ANY], ) ReaderResult[R, B]
Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of whether the body action returns and error or not.
func Curry0 ¶
Curry0 converts a context-only function into a ReaderResult (same as From0 but emphasizes immediate application).
Example:
getConfig := func(ctx context.Context) (Config, error) { ... }
rr := readerresult.Curry0(getConfig)
// rr is a ReaderResult[context.Context, Config]
func Do ¶
func Do[R, S any]( empty S, ) ReaderResult[R, S]
Do creates an empty context of type [S] to be used with the Bind operation. This is the starting point for do-notation style composition.
Example:
type State struct {
User User
Config Config
}
type Env struct {
UserService UserService
ConfigService ConfigService
}
result := readereither.Do[Env, error](State{})
func Flatten ¶
func Flatten[R, A any](mma ReaderResult[R, ReaderResult[R, A]]) ReaderResult[R, A]
Flatten removes one level of nesting from a nested ReaderResult. This converts ReaderResult[R, ReaderResult[R, A]] into ReaderResult[R, A].
Example:
nested := readerresult.Of[Config](readerresult.Of[Config](42)) flat := readerresult.Flatten(nested) // flat(cfg) returns (42, nil)
func FromEither ¶
func FromEither[R, A any](e Result[A]) ReaderResult[R, A]
FromEither lifts a Result[A] into a ReaderResult[R, A] that ignores the environment. The resulting computation will always produce the same result regardless of the environment provided.
Example:
res := result.Of(42) rr := readerresult.FromEither[Config](res) // rr(anyConfig) will always return (42, nil)
func FromReader ¶
func FromReader[R, A any](r Reader[R, A]) ReaderResult[R, A]
FromReader is an alias for RightReader. It lifts a Reader[R, A] into a ReaderResult[R, A] that always succeeds.
func FromResult ¶
FromResult is an alias for FromEither. It lifts a Result[A] into a ReaderResult[R, A] that ignores the environment.
func Left ¶
Left creates a ReaderResult that always fails with the given error, ignoring the environment.
Example:
rr := readerresult.Left[Config, User](errors.New("not found"))
// rr(anyConfig) always returns (nil, error)
func LeftReader ¶
LeftReader lifts a Reader[R, error] into a ReaderResult[R, A] that always fails. The resulting computation reads an error from the environment and wraps it in a failed Result.
Example:
getError := func(cfg Config) error { return cfg.InitError }
rr := readerresult.LeftReader[User](getError)
// rr(cfg) returns (nil, cfg.InitError)
func MonadAlt ¶
func MonadAlt[R, A any](first ReaderResult[R, A], second Lazy[ReaderResult[R, A]]) ReaderResult[R, A]
MonadAlt tries the first computation, and if it fails, tries the second. This implements the Alternative pattern for error recovery.
func MonadAp ¶
func MonadAp[B, R, A any](fab ReaderResult[R, func(A) B], fa ReaderResult[R, A]) ReaderResult[R, B]
MonadAp applies a function wrapped in a ReaderResult to a value wrapped in a ReaderResult. Both computations share the same environment. This is useful for combining independent computations that don't depend on each other's results.
Example:
add := func(x int) func(int) int { return func(y int) int { return x + y } }
fabr := readerresult.Of[Config](add(5))
fa := readerresult.Of[Config](3)
result := readerresult.MonadAp(fabr, fa) // Returns (8, nil)
func MonadBiMap ¶
func MonadBiMap[R, A, B any](fa ReaderResult[R, A], f Endomorphism[error], g func(A) B) ReaderResult[R, B]
MonadBiMap maps functions over both the error and success channels simultaneously. This transforms both the error type and the success type in a single operation.
Example:
enrichErr := func(e error) error { return fmt.Errorf("enriched: %w", e) }
double := N.Mul(2)
result := readerresult.MonadBiMap(rr, enrichErr, double)
func MonadChain ¶
func MonadChain[R, A, B any](ma ReaderResult[R, A], f Kleisli[R, A, B]) ReaderResult[R, B]
MonadChain sequences two ReaderResult computations, where the second depends on the result of the first. This is also known as "flatMap" or "bind". If the first computation fails, the second is not executed.
Example:
getUser := func(id int) readerresult.ReaderResult[DB, User] { ... }
getPosts := func(user User) readerresult.ReaderResult[DB, []Post] { ... }
userPosts := readerresult.MonadChain(getUser(42), getPosts)
func MonadChainEitherK ¶
MonadChainEitherK chains a ReaderResult with a function that returns a plain Result. This is useful for integrating functions that don't need environment access.
Example:
parseUser := func(data string) result.Result[User] { ... }
result := readerresult.MonadChainEitherK(getUserDataRR, parseUser)
func MonadChainReaderK ¶
func MonadChainReaderK[R, A, B any](ma ReaderResult[R, A], f result.Kleisli[A, B]) ReaderResult[R, B]
MonadChainReaderK chains a ReaderResult with a function that returns a plain Result. This is useful for integrating functions that don't need environment access.
Example:
parseUser := func(data string) result.Result[User] { ... }
result := readerresult.MonadChainReaderK(getUserDataRR, parseUser)
func MonadFlap ¶
func MonadFlap[R, A, B any](fab ReaderResult[R, func(A) B], a A) ReaderResult[R, B]
MonadFlap applies a wrapped function to a concrete value (reverse of Ap). This is useful when you have a function in a context and a plain value.
Example:
fabr := readerresult.Of[Config](N.Mul(2)) result := readerresult.MonadFlap(fabr, 5) // Returns (10, nil)
func MonadMap ¶
func MonadMap[R, A, B any](fa ReaderResult[R, A], f func(A) B) ReaderResult[R, B]
MonadMap transforms the success value of a ReaderResult using the given function. If the computation fails, the error is propagated unchanged.
Example:
rr := readerresult.Of[Config](5) doubled := readerresult.MonadMap(rr, N.Mul(2)) // doubled(cfg) returns (10, nil)
func MonadMapLeft ¶
func MonadMapLeft[R, A any](fa ReaderResult[R, A], f Endomorphism[error]) ReaderResult[R, A]
MonadMapLeft transforms the error value without affecting successful results. This is useful for error enrichment, wrapping, or transformation.
Example:
enrichErr := func(e error) error { return fmt.Errorf("DB error: %w", e) }
result := readerresult.MonadMapLeft(getUserRR, enrichErr)
func MonadTraverseArray ¶
func MonadTraverseArray[R, A, B any](as []A, f Kleisli[R, A, B]) ReaderResult[R, []B]
func Of ¶
func Of[R, A any](a A) ReaderResult[R, A]
Of creates a ReaderResult that always succeeds with the given value. This is an alias for Right and is the "pure" or "return" operation for the ReaderResult monad.
Example:
rr := readerresult.Of[Config](42) // rr(anyConfig) always returns (42, nil)
func Right ¶
func Right[R, A any](a A) ReaderResult[R, A]
Right creates a ReaderResult that always succeeds with the given value, ignoring the environment. This is the "pure" or "return" operation for the ReaderResult monad.
Example:
rr := readerresult.Right[Config](42) // rr(anyConfig) always returns (42, nil)
func RightReader ¶
func RightReader[R, A any](rdr Reader[R, A]) ReaderResult[R, A]
RightReader lifts a Reader[R, A] into a ReaderResult[R, A] that always succeeds. The resulting computation reads a value from the environment and wraps it in a successful Result.
Example:
getPort := func(cfg Config) int { return cfg.Port }
rr := readerresult.RightReader[Config](getPort)
// rr(cfg) returns (cfg.Port, nil)
func SequenceArray ¶
func SequenceArray[R, A any](ma []ReaderResult[R, A]) ReaderResult[R, []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 value in a Tuple1. This is primarily for consistency with the other SequenceT functions.
Example:
rr := readerresult.Of[Config](42)
result := readerresult.SequenceT1(rr)
// result(cfg) returns (Tuple1{42}, nil)
func SequenceT2 ¶
func SequenceT2[R, A, B any]( a ReaderResult[R, A], b ReaderResult[R, B], ) ReaderResult[R, T.Tuple2[A, B]]
SequenceT2 combines two independent ReaderResult computations into a tuple. Both computations share the same environment.
Example:
getPort := readerresult.Asks(func(cfg Config) int { return cfg.Port })
getHost := readerresult.Asks(func(cfg Config) string { return cfg.Host })
result := readerresult.SequenceT2(getPort, getHost)
// result(cfg) returns (Tuple2{cfg.Port, cfg.Host}, nil)
func SequenceT3 ¶
func SequenceT3[R, A, B, C any]( a ReaderResult[R, A], b ReaderResult[R, B], c ReaderResult[R, C], ) ReaderResult[R, T.Tuple3[A, B, C]]
SequenceT3 combines three independent ReaderResult computations into a tuple.
Example:
getUser := getUserRR(42)
getConfig := getConfigRR()
getStats := getStatsRR()
result := readerresult.SequenceT3(getUser, getConfig, getStats)
// result(env) returns (Tuple3{user, config, stats}, nil)
func SequenceT4 ¶
func SequenceT4[R, A, B, C, D any]( a ReaderResult[R, A], b ReaderResult[R, B], c ReaderResult[R, C], d ReaderResult[R, D], ) ReaderResult[R, T.Tuple4[A, B, C, D]]
SequenceT4 combines four independent ReaderResult computations into a tuple.
Example:
result := readerresult.SequenceT4(getUserRR, getConfigRR, getStatsRR, getMetadataRR)
// result(env) returns (Tuple4{user, config, stats, metadata}, nil)