readerresult

package
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: Apache-2.0 Imports: 18 Imported by: 0

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:

  1. Dependency injection with error handling - pass configuration/services through computations that may fail
  2. Functional error handling - compose operations that depend on context and may error
  3. 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:

  1. Map - transform successful values
  2. Chain (FlatMap) - sequence dependent operations
  3. Ap - combine independent computations
  4. 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:

  1. Business parameters are applied first: getUser(42)
  2. This returns a ReaderResult that waits for the context
  3. Multiple operations can be composed before providing the context
  4. 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

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApResultS

func ApResultS[
	R, S1, S2, T any](
	setter func(T) func(S1) S2,
) func(T, error) Operator[R, S1, S2]

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

func BindToResult[
	R, S1, T any](
	setter func(T) S1,
) func(T, error) ReaderResult[R, S1]

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

func ChainOptionK[R, A, B any](onNone Lazy[error]) func(option.Kleisli[A, B]) Operator[R, A, B]

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

func Curry1[R, T1, A any](f func(R, T1) (A, error)) func(T1) ReaderResult[R, A]

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

func Curry2[R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1) func(T2) ReaderResult[R, A]

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

func From0[R, A any](f func(R) (A, error)) func() ReaderResult[R, A]

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

func From1[R, T1, A any](f func(R, T1) (A, error)) func(T1) ReaderResult[R, A]

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

func From2[R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1, T2) ReaderResult[R, A]

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

func GetOrElse[R, A any](onLeft reader.Kleisli[R, error, A]) func(ReaderResult[R, A]) Reader[R, A]

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

func Read[A, R any](r R) func(ReaderResult[R, A]) (A, error)

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

func Uncurry1[R, T1, A any](f func(T1) ReaderResult[R, A]) func(R, T1) (A, error)

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

func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) ReaderResult[R, A]) func(R, T1, T2) (A, error)

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 Either

type Either[E, A any] = either.Either[E, A]

Either represents a value that can be one of two types: Left (E) or Right (A).

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

func FromPredicate[R, A any](pred func(A) bool, onFalse func(A) error) Kleisli[R, A, A]

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

func TraverseArrayWithIndex[R, A, B any](f func(int, A) ReaderResult[R, B]) Kleisli[R, []A, []B]

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 Lazy

type Lazy[A any] = lazy.Lazy[A]

Lazy represents a deferred computation that produces a value of type A when evaluated.

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

func AlternativeMonoid[R, A any](m M.Monoid[A]) Monoid[R, A]

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

func ApplicativeMonoid[R, A any](m M.Monoid[A]) Monoid[R, A]

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

func ApSL[R, S, T any](
	lens L.Lens[S, T],
	fa ReaderResult[R, T],
) Operator[R, S, S]

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 BindEitherK[R, S1, S2, T any](
	setter func(T) func(S1) S2,
	f RES.Kleisli[S1, T],
) Operator[R, S1, S2]

func BindL

func BindL[R, S, T any](
	lens L.Lens[S, T],
	f Kleisli[R, T, T],
) Operator[R, S, S]

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

func ChainEitherK[R, A, B any](f RES.Kleisli[A, B]) Operator[R, A, B]

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

func ChainReaderK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, B]

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

func LetToL[R, S, T any](
	lens L.Lens[S, T],
	b T,
) Operator[R, S, S]

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

func OrElse[R, A any](onLeft Kleisli[R, error, A]) Operator[R, A, A]

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

func OrLeft[A, R any](onLeft reader.Kleisli[R, error, error]) Operator[R, A, A]

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 Option

type Option[A any] = option.Option[A]

Option represents an optional value that may or may not be present.

type Reader

type Reader[R, A any] = reader.Reader[R, A]

Reader represents a computation that depends on a read-only environment of type R and produces a value of type A.

type ReaderResult

type ReaderResult[R, A any] = func(R) (A, error)

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

func Curry0[R, A any](f func(R) (A, error)) ReaderResult[R, A]

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

func FromResult[R, A any](a A, err error) ReaderResult[R, A]

FromResult is an alias for FromEither. It lifts a Result[A] into a ReaderResult[R, A] that ignores the environment.

func Left

func Left[R, A any](err error) ReaderResult[R, A]

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

func LeftReader[A, R any](l Reader[R, error]) ReaderResult[R, A]

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

func MonadChainEitherK[R, A, B any](ma ReaderResult[R, A], f RES.Kleisli[A, B]) ReaderResult[R, B]

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[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

func SequenceT1[R, A any](a ReaderResult[R, A]) ReaderResult[R, T.Tuple1[A]]

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)

type Result

type Result[A any] = result.Result[A]

Result represents an Either with error as the left type, compatible with Go's (value, error) tuple.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL