readerresult

package
v2.1.14 Latest Latest
Warning

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

Go to latest
Published: Jan 18, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package readerresult provides logging utilities for the ReaderResult monad, which combines the Reader monad (for dependency injection via context.Context) with the Result monad (for error handling).

The logging functions in this package allow you to log Result values (both successes and errors) while preserving the functional composition style.

Package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error

Package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error.

Side Effects and Context

IMPORTANT: In contrast to the functional readerresult package (readerresult.ReaderResult[R, A]), this context/readerresult package has side effects by design because it depends on context.Context, which is inherently effectful:

  • context.Context can be cancelled (ctx.Done() channel)
  • context.Context has deadlines and timeouts (ctx.Deadline())
  • context.Context carries request-scoped values (ctx.Value())
  • context.Context propagates cancellation signals across goroutines

This means that ReaderResult[A] = func(context.Context) (A, error) represents an EFFECTFUL computation, not a pure function. The computation's behavior can change based on the context's state (cancelled, timed out, etc.), making it fundamentally different from a pure Reader monad.

Comparison of packages:

  • readerresult.ReaderResult[R, A] = func(R) Result[A] - PURE (R can be any type, no side effects)
  • idiomatic/readerresult.ReaderResult[R, A] = func(R) (A, error) - EFFECTFUL (also uses context.Context)
  • context/readerresult.ReaderResult[A] = func(context.Context) (A, error) - EFFECTFUL (uses context.Context)

Use this package (context/readerresult) when you need:

  • Cancellation support for long-running operations
  • Timeout/deadline handling
  • Request-scoped values (tracing IDs, user context, etc.)
  • Integration with Go's standard context-aware APIs
  • Idiomatic Go error handling with (value, error) tuples

Use the functional readerresult package when you need:

  • Pure dependency injection without side effects
  • Testable computations with simple state/config objects
  • Functional composition without context propagation
  • Generic environment types (not limited to context.Context)

Pure vs Effectful Functions

This package distinguishes between pure (side-effect free) and effectful (side-effectful) functions:

EFFECTFUL FUNCTIONS (depend on context.Context):

  • ReaderResult[A]: func(context.Context) (A, error) - Effectful computation that needs context
  • These functions are effectful because context.Context is effectful (can be cancelled, has deadlines, carries values)
  • Use for: operations that need cancellation, timeouts, context values, or any context-dependent behavior
  • Examples: database queries, HTTP requests, operations that respect cancellation

PURE FUNCTIONS (side-effect free):

  • func(State) (Value, error) - Pure computation that only depends on state, not context
  • func(State) Value - Pure transformation without errors
  • These functions are pure because they only read from their input state and don't depend on external context
  • Use for: parsing, validation, calculations, data transformations that don't need context
  • Examples: JSON parsing, input validation, mathematical computations

The package provides different bind operations for each:

  • Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])
  • BindResultK: For pure functions with errors (State -> (Value, error))
  • Let: For pure functions without errors (State -> Value)
  • BindReaderK: For context-dependent pure functions (State -> Reader[Context, Value])
  • BindEitherK: For pure Result/Either values (State -> Result[Value])

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Ap

func Ap[A, B any](fa ReaderResult[A]) func(ReaderResult[func(A) B]) ReaderResult[B]

Ap creates a function that applies a ReaderResult containing a function to a ReaderResult containing a value. This is the curried version where the value ReaderResult is provided first.

Type Parameters:

  • A: The input type
  • B: The output type

Parameters:

  • fa: The ReaderResult containing the value

Returns:

  • A function that takes a ReaderResult containing a function and returns the result

Example:

faRR := readerresult.Of(42)
applyTo42 := readerresult.Ap[int, string](faRR)
fabRR := readerresult.Of(func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
result := applyTo42(fabRR)(t.Context()) // Right("value: 42")

func ChainEitherK

func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderResult[A]) ReaderResult[B]

ChainEitherK creates an operator that sequences a ReaderResult with a function that returns an Either. This is the curried version where the function is provided first.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the output Either

Parameters:

  • f: The function that takes the success value and returns an Either

Returns:

  • An Operator that sequences the computations

Example:

validate := readerresult.ChainEitherK(func(x int) result.Result[int] {
    if x > 0 {
        return result.Of(x)
    }
    return result.Error[int](errors.New("must be positive"))
})
rr := readerresult.Of(42)
result := validate(rr)(t.Context()) // Right(42)

func ChainOptionK

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

ChainOptionK creates an operator that sequences a ReaderResult with a function that returns an Option. If the Option is None, the onNone function is called to generate an error.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the output Option

Parameters:

  • onNone: The function to generate an error when the Option is None

Returns:

  • A function that takes an Option Kleisli and returns an Operator

Example:

chainOpt := readerresult.ChainOptionK[int, string](func() error {
    return errors.New("value not found")
})
optKleisli := func(x int) option.Option[string] {
    if x > 0 {
        return option.Some(fmt.Sprintf("value: %d", x))
    }
    return option.None[string]()
}
operator := chainOpt(optKleisli)
result := operator(readerresult.Of(42))(t.Context()) // Right("value: 42")

func Curry2

func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) Kleisli[T2, A]

Curry2 converts a Go function with context and two parameters into a curried function. This enables partial application and functional composition of two-parameter functions.

Type Parameters:

  • T1: The type of the first parameter
  • T2: The type of the second parameter
  • A: The return type of the function

Parameters:

  • f: A function that takes a context and two parameters, returning a value and error

Returns:

  • A curried function that takes T1 and returns a Kleisli arrow for T2

Example:

// Idiomatic Go function
updateUser := func(ctx context.Context, id int, name string) (User, error) {
    if ctx.Err() != nil {
        return User{}, ctx.Err()
    }
    return User{ID: id, Name: name}, nil
}

// Convert to curried form
updateUserCurried := readerresult.Curry2(updateUser)

// Partial application
updateUser123 := updateUserCurried(123)

// Use in a pipeline
pipeline := F.Pipe1(
    readerresult.Of("Bob"),
    readerresult.Chain(updateUser123),
)
result := pipeline(t.Context()) // Right(User{ID: 123, Name: "Bob"})

func Curry3

func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) Kleisli[T3, A]

Curry3 converts a Go function with context and three parameters into a curried function. This enables partial application and functional composition of three-parameter functions.

Type Parameters:

  • T1: The type of the first parameter
  • T2: The type of the second parameter
  • T3: The type of the third parameter
  • A: The return type of the function

Parameters:

  • f: A function that takes a context and three parameters, returning a value and error

Returns:

  • A curried function that takes T1, T2, and returns a Kleisli arrow for T3

Example:

// Idiomatic Go function
createOrder := func(ctx context.Context, userID int, productID int, quantity int) (Order, error) {
    if ctx.Err() != nil {
        return Order{}, ctx.Err()
    }
    return Order{UserID: userID, ProductID: productID, Quantity: quantity}, nil
}

// Convert to curried form
createOrderCurried := readerresult.Curry3(createOrder)

// Partial application
createOrderForUser := createOrderCurried(123)
createOrderForProduct := createOrderForUser(456)

// Use in a pipeline
pipeline := F.Pipe1(
    readerresult.Of(2),
    readerresult.Chain(createOrderForProduct),
)
result := pipeline(t.Context()) // Right(Order{UserID: 123, ProductID: 456, Quantity: 2})

func From0

func From0[A any](f func(context.Context) (A, error)) func() ReaderResult[A]

func From2

func From2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderResult[A]

func From3

func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderResult[A]

func Read

func Read[A any](r context.Context) func(ReaderResult[A]) Result[A]

Read executes a ReaderResult by providing it with a context.Context value. This function "runs" the ReaderResult computation with the given context.

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • r: The context.Context to provide to the ReaderResult

Returns:

  • A function that takes a ReaderResult and returns its Result

Example:

rr := readerresult.Of(42)
ctx := t.Context()
runWithCtx := readerresult.Read[int](ctx)
result := runWithCtx(rr) // Right(42)

func ReadEither added in v2.1.10

func ReadEither[A any](r Result[context.Context]) func(ReaderResult[A]) Result[A]

ReadEither executes a ReaderResult by providing it with a Result[context.Context]. If the Result contains an error, that error is returned immediately. If the Result contains a context, the ReaderResult is executed with that context.

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • r: The Result[context.Context] to provide to the ReaderResult

Returns:

  • A function that takes a ReaderResult and returns its Result

Example:

rr := readerresult.Of(42)
ctxResult := result.Of[error](t.Context())
runWithCtxResult := readerresult.ReadEither[int](ctxResult)
result := runWithCtxResult(rr) // Right(42)

func ReadIO added in v2.1.14

func ReadIO[A any](r IO[context.Context]) func(ReaderResult[A]) IOResult[A]

ReadIO executes a ReaderResult by providing it with a context obtained from an IO computation.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This allows the context itself to be obtained through an IO operation.

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • r: The IO computation that produces a context.Context

Returns:

  • A function that takes a ReaderResult and returns an IOResult

Example:

getCtx := func() context.Context { return t.Context() }
rr := readerresult.Of(42)
runWithIO := readerresult.ReadIO[int](getCtx)
ioResult := runWithIO(rr)
result := ioResult() // Right(42)

func ReadIOEither added in v2.1.14

func ReadIOEither[A any](r IOResult[context.Context]) func(ReaderResult[A]) IOResult[A]

ReadIOEither executes a ReaderResult by providing it with a context obtained from an IOResult computation. If the IOResult contains an error, that error is returned immediately.

IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations with error handling. This allows the context itself to be obtained through an IO operation that may fail.

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • r: The IOResult computation that produces a context.Context

Returns:

  • A function that takes a ReaderResult and returns an IOResult

Example:

getCtx := func() result.Result[context.Context] {
    return result.Of[error](t.Context())
}
rr := readerresult.Of(42)
runWithIOResult := readerresult.ReadIOEither[int](getCtx)
ioResult := runWithIOResult(rr)
result := ioResult() // Right(42)

func ReadIOResult added in v2.1.14

func ReadIOResult[A any](r IOResult[context.Context]) func(ReaderResult[A]) IOResult[A]

ReadIOResult is an alias for ReadIOEither. It executes a ReaderResult by providing it with a context obtained from an IOResult computation.

IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations with error handling.

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • r: The IOResult computation that produces a context.Context

Returns:

  • A function that takes a ReaderResult and returns an IOResult

Example:

getCtx := func() result.Result[context.Context] {
    return result.Of[error](t.Context())
}
rr := readerresult.Of(42)
runWithIOResult := readerresult.ReadIOResult[int](getCtx)
ioResult := runWithIOResult(rr)
result := ioResult() // Right(42)

func ReadResult added in v2.1.10

func ReadResult[A any](r Result[context.Context]) func(ReaderResult[A]) Result[A]

ReadResult executes a ReaderResult by providing it with a Result[context.Context]. This is an alias for ReadEither.

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • r: The Result[context.Context] to provide to the ReaderResult

Returns:

  • A function that takes a ReaderResult and returns its Result

Example:

rr := readerresult.Of(42)
ctxResult := result.Of[error](t.Context())
runWithCtxResult := readerresult.ReadResult[int](ctxResult)
result := runWithCtxResult(rr) // Right(42)

func SequenceReader

func SequenceReader[R, A any](ma ReaderResult[Reader[R, A]]) reader.Kleisli[context.Context, R, Result[A]]

SequenceReader swaps the order of environment parameters when the inner computation is a Reader.

This function is specialized for the context.Context-based ReaderResult monad. It takes a ReaderResult that produces a Reader and returns a reader.Kleisli that produces Results. The context.Context is implicitly used as the outer environment type.

Type Parameters:

  • R: The inner environment type (becomes outer after flip)
  • A: The success value type

Parameters:

  • ma: A ReaderResult that takes context.Context and may produce a Reader[R, A]

Returns:

  • A reader.Kleisli[context.Context, R, Result[A]], which is func(context.Context) func(R) Result[A]

The function preserves error handling from the outer ReaderResult layer. If the outer computation fails, the error is propagated to the inner Result.

Note: This is an inline wrapper around readerresult.SequenceReader, specialized for context.Context as the outer environment type.

Example:

type Database struct {
    ConnectionString string
}

// Original: takes context, may fail, produces Reader[Database, string]
original := func(ctx context.Context) result.Result[reader.Reader[Database, string]] {
    if ctx.Err() != nil {
        return result.Error[reader.Reader[Database, string]](ctx.Err())
    }
    return result.Ok[error](func(db Database) string {
        return fmt.Sprintf("Query on %s", db.ConnectionString)
    })
}

// Sequenced: takes context first, then Database
sequenced := SequenceReader(original)

ctx := t.Context()
db := Database{ConnectionString: "localhost:5432"}

// Apply context first to get a function that takes database
dbReader := sequenced(ctx)
// Then apply database to get the final result
result := dbReader(db)
// result is Result[string]

Use Cases:

  • Dependency injection: Flip parameter order to inject context first, then dependencies
  • Testing: Separate context handling from business logic for easier testing
  • Composition: Enable point-free style by fixing the context parameter first

func TraverseReader

func TraverseReader[R, A, B any](
	f reader.Kleisli[R, A, B],
) func(ReaderResult[A]) Kleisli[R, B]

TraverseReader transforms a value using a Reader function and swaps environment parameter order.

This function combines mapping and parameter flipping in a single operation. It takes a Reader function (pure computation without error handling) and returns a function that: 1. Maps a ReaderResult[A] to ReaderResult[B] using the provided Reader function 2. Flips the parameter order so R comes before context.Context

Type Parameters:

  • R: The inner environment type (becomes outer after flip)
  • A: The input value type
  • B: The output value type

Parameters:

  • f: A reader.Kleisli[R, A, B], which is func(R) func(A) B - a pure Reader function

Returns:

  • A function that takes ReaderResult[A] and returns Kleisli[R, B]
  • Kleisli[R, B] is func(R) ReaderResult[B], which is func(R) func(context.Context) Result[B]

The function preserves error handling from the input ReaderResult. If the input computation fails, the error is propagated without applying the transformation function.

Note: This is a wrapper around readerresult.TraverseReader, specialized for context.Context.

Example:

type Config struct {
    MaxRetries int
}

// A pure Reader function that depends on Config
formatMessage := func(cfg Config) func(int) string {
    return func(value int) string {
        return fmt.Sprintf("Value: %d, MaxRetries: %d", value, cfg.MaxRetries)
    }
}

// Original computation that may fail
computation := func(ctx context.Context) result.Result[int] {
    if ctx.Err() != nil {
        return result.Error[int](ctx.Err())
    }
    return result.Ok[error](42)
}

// Create a traversal that applies formatMessage and flips parameters
traverse := TraverseReader[Config, int, string](formatMessage)

// Apply to the computation
flipped := traverse(computation)

// Now we can provide Config first, then context
cfg := Config{MaxRetries: 3}
ctx := t.Context()

result := flipped(cfg)(ctx)
// result is Result[string] containing "Value: 42, MaxRetries: 3"

Use Cases:

  • Dependency injection: Inject configuration/dependencies before context
  • Testing: Separate pure business logic from context handling
  • Composition: Build pipelines where dependencies are fixed before execution
  • Point-free style: Enable partial application by fixing dependencies first

func Uncurry1

func Uncurry1[T1, A any](f Kleisli[T1, A]) func(context.Context, T1) (A, error)

Uncurry1 converts a Kleisli arrow back into an idiomatic Go function with context as the first parameter. This is useful for interfacing with code that expects standard Go function signatures.

Type Parameters:

  • T1: The type of the parameter
  • A: The return type

Parameters:

  • f: A Kleisli arrow

Returns:

  • A Go function with context as the first parameter

Example:

// Kleisli arrow
getUserKleisli := func(id int) readerresult.ReaderResult[User] {
    return func(ctx context.Context) result.Result[User] {
        if ctx.Err() != nil {
            return result.Error[User](ctx.Err())
        }
        return result.Of(User{ID: id, Name: "Alice"})
    }
}

// Convert back to idiomatic Go function
getUserByID := readerresult.Uncurry1(getUserKleisli)

// Use as a normal Go function
user, err := getUserByID(t.Context(), 123)
if err != nil {
    log.Fatal(err)
}
fmt.Println(user.Name) // "Alice"

func Uncurry2

func Uncurry2[T1, T2, A any](f func(T1) Kleisli[T2, A]) func(context.Context, T1, T2) (A, error)

Uncurry2 converts a curried function back into an idiomatic Go function with context as the first parameter. This is useful for interfacing with code that expects standard Go function signatures.

Type Parameters:

  • T1: The type of the first parameter
  • T2: The type of the second parameter
  • A: The return type

Parameters:

  • f: A curried function

Returns:

  • A Go function with context as the first parameter

Example:

// Curried function
updateUserCurried := func(id int) func(name string) readerresult.ReaderResult[User] {
    return func(name string) readerresult.ReaderResult[User] {
        return func(ctx context.Context) result.Result[User] {
            if ctx.Err() != nil {
                return result.Error[User](ctx.Err())
            }
            return result.Of(User{ID: id, Name: name})
        }
    }
}

// Convert back to idiomatic Go function
updateUser := readerresult.Uncurry2(updateUserCurried)

// Use as a normal Go function
user, err := updateUser(t.Context(), 123, "Bob")
if err != nil {
    log.Fatal(err)
}
fmt.Println(user.Name) // "Bob"

func Uncurry3

func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) Kleisli[T3, A]) func(context.Context, T1, T2, T3) (A, error)

Uncurry3 converts a curried function back into an idiomatic Go function with context as the first parameter. This is useful for interfacing with code that expects standard Go function signatures.

Type Parameters:

  • T1: The type of the first parameter
  • T2: The type of the second parameter
  • T3: The type of the third parameter
  • A: The return type

Parameters:

  • f: A curried function

Returns:

  • A Go function with context as the first parameter

Example:

// Curried function
createOrderCurried := func(userID int) func(productID int) func(quantity int) readerresult.ReaderResult[Order] {
    return func(productID int) func(quantity int) readerresult.ReaderResult[Order] {
        return func(quantity int) readerresult.ReaderResult[Order] {
            return func(ctx context.Context) result.Result[Order] {
                if ctx.Err() != nil {
                    return result.Error[Order](ctx.Err())
                }
                return result.Of(Order{UserID: userID, ProductID: productID, Quantity: quantity})
            }
        }
    }
}

// Convert back to idiomatic Go function
createOrder := readerresult.Uncurry3(createOrderCurried)

// Use as a normal Go function
order, err := createOrder(t.Context(), 123, 456, 2)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Order: User=%d, Product=%d, Qty=%d\n", order.UserID, order.ProductID, order.Quantity)

Types

type Either

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

Either represents a value that can be either a Left (error) or Right (success). This is specialized to use error as the Left type. This is an alias for either.Either[error, A].

Type Parameters:

  • A: The type of the Right (success) value

Example:

success := either.Right[error, int](42)           // Right(42)
failure := either.Left[int](errors.New("failed")) // Left(error)

type Endomorphism

type Endomorphism[A any] = endomorphism.Endomorphism[A]

Endomorphism represents a function that transforms a value to the same type. This is an alias for endomorphism.Endomorphism[A].

Type Parameters:

  • A: The type of the value

Signature:

type Endomorphism[A any] = func(A) A

Example:

increment := func(x int) int { return x + 1 }
// increment is an Endomorphism[int]

type IO added in v2.1.14

type IO[A any] = io.IO[A]

IO represents a side-effectful computation that produces a value of type A. This is an alias for io.IO[A].

IMPORTANT: IO operations have side effects (file I/O, network calls, etc.). Combining IO with ReaderResult makes sense because ReaderResult is already effectful due to its dependency on context.Context.

Type Parameters:

  • A: The type of the value produced by the IO operation

Signature:

type IO[A any] = func() A

Example:

readConfig := func() Config {
    // Side effect: read from file
    data, _ := os.ReadFile("config.json")
    return parseConfig(data)
}
// readConfig is an IO[Config]

type IOResult added in v2.1.14

type IOResult[A any] = ioresult.IOResult[A]

IOResult represents a side-effectful computation that can fail with an error. This combines IO (side effects) with Result (error handling). This is an alias for ioresult.IOResult[A].

IMPORTANT: IOResult operations have side effects and can fail. Combining IOResult with ReaderResult makes sense because both are effectful.

Type Parameters:

  • A: The type of the success value

Signature:

type IOResult[A any] = func() Result[A]

Example:

readConfig := func() result.Result[Config] {
    // Side effect: read from file
    data, err := os.ReadFile("config.json")
    if err != nil {
        return result.Error[Config](err)
    }
    return result.Of(parseConfig(data))
}
// readConfig is an IOResult[Config]

type Kleisli

type Kleisli[A, B any] = reader.Reader[A, ReaderResult[B]]

Kleisli represents a function that takes a value of type A and returns a ReaderResult[B]. This is the fundamental building block for composing ReaderResult computations.

Type Parameters:

  • A: The input type
  • B: The output type (wrapped in ReaderResult)

Signature:

type Kleisli[A, B any] = func(A) ReaderResult[B]

Example:

getUserByID := func(id int) readerresult.ReaderResult[User] {
    return func(ctx context.Context) result.Result[User] {
        // Fetch user from database
        return result.Of(User{ID: id, Name: "Alice"})
    }
}
// getUserByID is a Kleisli[int, User]

func ApS

func ApS[S1, S2, T any](
	setter func(T) func(S1) S2,
	fa ReaderResult[T],
) Kleisli[ReaderResult[S1], S2]

ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently (using Applicative rather than Monad). This allows independent EFFECTFUL computations to be combined without one depending on the result of the other.

IMPORTANT: ApS is for EFFECTFUL FUNCTIONS that depend on context.Context. The ReaderResult parameter is effectful because it depends on context.Context.

Unlike Bind, which sequences operations, ApS can be used when operations are independent and can conceptually run in parallel.

Example:

type State struct {
    UserID   string
    TenantID string
}

// These operations are independent and can be combined with ApS
getUserID := func(ctx context.Context) either.Either[error, string] {
    return either.Right[error](ctx.Value("userID").(string))
}
getTenantID := func(ctx context.Context) either.Either[error, string] {
    return either.Right[error](ctx.Value("tenantID").(string))
}

result := F.Pipe2(
    readereither.Do(State{}),
    readereither.ApS(
        func(uid string) func(State) State {
            return func(s State) State { s.UserID = uid; return s }
        },
        getUserID,
    ),
    readereither.ApS(
        func(tid string) func(State) State {
            return func(s State) State { s.TenantID = tid; return s }
        },
        getTenantID,
    ),
)

func ApSL

func ApSL[S, T any](
	lens Lens[S, T],
	fa ReaderResult[T],
) Kleisli[ReaderResult[S], S]

ApSL is a variant of ApS that uses a lens to focus on a specific field in the state. Instead of providing a setter function, you provide a lens that knows how to get and set the field. This is more convenient when working with nested structures.

Parameters:

  • lens: A lens that focuses on a field of type T within state S
  • fa: A ReaderResult computation that produces a value of type T

Returns:

  • A function that transforms ReaderResult[S] to ReaderResult[S] by setting the focused field

Example:

type Person struct {
    Name string
    Age  int
}

ageLens := lens.MakeLens(
    func(p Person) int { return p.Age },
    func(p Person, a int) Person { p.Age = a; return p },
)

getAge := func(ctx context.Context) either.Either[error, int] {
    return either.Right[error](30)
}

result := F.Pipe1(
    readereither.Do(Person{Name: "Alice", Age: 25}),
    readereither.ApSL(ageLens, getAge),
)

func Bind

func Bind[S1, S2, T any](
	setter func(T) func(S1) S2,
	f Kleisli[S1, T],
) Kleisli[ReaderResult[S1], S2]

Bind attaches the result of an EFFECTFUL computation to a context [S1] to produce a context [S2]. This enables sequential composition where each step can depend on the results of previous steps and access the context.Context from the environment.

IMPORTANT: Bind is for EFFECTFUL FUNCTIONS that depend on context.Context. The function parameter takes state and returns a ReaderResult[T], which is effectful because it depends on context.Context (can be cancelled, has deadlines, carries values).

For PURE FUNCTIONS (side-effect free), use:

  • BindResultK: For pure functions with errors (State -> (Value, error))
  • Let: For pure functions without errors (State -> Value)

The setter function takes the result of the computation and returns a function that updates the context from S1 to S2.

Example:

type State struct {
    UserID   string
    TenantID string
}

result := F.Pipe2(
    readereither.Do(State{}),
    readereither.Bind(
        func(uid string) func(State) State {
            return func(s State) State { s.UserID = uid; return s }
        },
        func(s State) readereither.ReaderResult[string] {
            return func(ctx context.Context) either.Either[error, string] {
                if uid, ok := ctx.Value("userID").(string); ok {
                    return either.Right[error](uid)
                }
                return either.Left[string](errors.New("no userID"))
            }
        },
    ),
    readereither.Bind(
        func(tid string) func(State) State {
            return func(s State) State { s.TenantID = tid; return s }
        },
        func(s State) readereither.ReaderResult[string] {
            // This can access s.UserID from the previous step
            return func(ctx context.Context) either.Either[error, string] {
                return either.Right[error]("tenant-" + s.UserID)
            }
        },
    ),
)

func BindL

func BindL[S, T any](
	lens Lens[S, T],
	f Kleisli[T, T],
) Kleisli[ReaderResult[S], S]

BindL is a variant of Bind that uses a lens to focus on a specific field in the state. It combines the lens-based field access with monadic composition for EFFECTFUL computations.

IMPORTANT: BindL is for EFFECTFUL FUNCTIONS that depend on context.Context. The function parameter returns a ReaderResult, which is effectful.

It allows you to: 1. Extract a field value using the lens 2. Use that value in an effectful computation that may fail 3. Update the field with the result

Parameters:

  • lens: A lens that focuses on a field of type T within state S
  • f: A function that takes the current field value and returns a ReaderResult computation

Returns:

  • A function that transforms ReaderResult[S] to ReaderResult[S]

Example:

type Counter struct {
    Value int
}

valueLens := lens.MakeLens(
    func(c Counter) int { return c.Value },
    func(c Counter, v int) Counter { c.Value = v; return c },
)

increment := func(v int) readereither.ReaderResult[int] {
    return func(ctx context.Context) either.Either[error, int] {
        if v >= 100 {
            return either.Left[int](errors.New("value too large"))
        }
        return either.Right[error](v + 1)
    }
}

result := F.Pipe1(
    readereither.Of[error](Counter{Value: 42}),
    readereither.BindL(valueLens, increment),
)

func Curry1

func Curry1[T1, A any](f func(context.Context, T1) (A, error)) Kleisli[T1, A]

Curry1 converts a Go function with context and one parameter into a Kleisli arrow. This enables functional composition of single-parameter functions.

Type Parameters:

  • T1: The type of the first parameter
  • A: The return type of the function

Parameters:

  • f: A function that takes a context and one parameter, returning a value and error

Returns:

  • A Kleisli arrow that can be composed with other ReaderResult operations

Example:

// Idiomatic Go function
getUserByID := func(ctx context.Context, id int) (User, error) {
    if ctx.Err() != nil {
        return User{}, ctx.Err()
    }
    return User{ID: id, Name: "Alice"}, nil
}

// Convert to Kleisli for functional composition
getUserKleisli := readerresult.Curry1(getUserByID)

// Use in a pipeline
pipeline := F.Pipe1(
    readerresult.Of(123),
    readerresult.Chain(getUserKleisli),
)
result := pipeline(t.Context()) // Right(User{ID: 123, Name: "Alice"})

func From1

func From1[T1, A any](f func(context.Context, T1) (A, error)) Kleisli[T1, A]

func FromPredicate

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

FromPredicate creates a Kleisli arrow that validates a value using a predicate. If the predicate returns true, the value is wrapped in Right. If the predicate returns false, the onFalse function is called to generate an error.

Type Parameters:

  • A: The type of the value to validate

Parameters:

  • pred: The predicate function to test the value
  • onFalse: The function to generate an error when the predicate fails

Returns:

  • A Kleisli arrow that validates the value

Example:

isPositive := readerresult.FromPredicate(
    func(x int) bool { return x > 0 },
    func(x int) error { return fmt.Errorf("%d is not positive", x) },
)
result1 := isPositive(42)(t.Context()) // Right(42)
result2 := isPositive(-1)(t.Context()) // Left(error("-1 is not positive"))

func Let

func Let[S1, S2, T any](
	setter func(T) func(S1) S2,
	f func(S1) T,
) Kleisli[ReaderResult[S1], S2]

Let attaches the result of a PURE computation to a context [S1] to produce a context [S2].

IMPORTANT: Let is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context. The function parameter takes state and returns a value directly, with no errors or effects.

For EFFECTFUL FUNCTIONS (that need context.Context), use:

  • Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])

For PURE FUNCTIONS with error handling, use:

  • BindResultK: For pure functions with errors (State -> (Value, error))

func LetL

func LetL[S, T any](
	lens Lens[S, T],
	f Endomorphism[T],
) Kleisli[ReaderResult[S], S]

LetL is a variant of Let that uses a lens to focus on a specific field in the state. It applies a PURE transformation to the focused field without any effects.

IMPORTANT: LetL is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context. The function parameter is a pure endomorphism (T -> T) with no errors or effects.

Parameters:

  • lens: A lens that focuses on a field of type T within state S
  • f: A pure function that transforms the field value

Returns:

  • A function that transforms ReaderResult[S] to ReaderResult[S]

Example:

type Counter struct {
    Value int
}

valueLens := lens.MakeLens(
    func(c Counter) int { return c.Value },
    func(c Counter, v int) Counter { c.Value = v; return c },
)

double := func(v int) int { return v * 2 }

result := F.Pipe1(
    readereither.Of[error](Counter{Value: 21}),
    readereither.LetL(valueLens, double),
)
// result when executed will be Right(Counter{Value: 42})

func LetTo

func LetTo[S1, S2, T any](
	setter func(T) func(S1) S2,
	b T,
) Kleisli[ReaderResult[S1], S2]

LetTo attaches a constant value to a context [S1] to produce a context [S2]. This is a PURE operation (side-effect free) that simply sets a field to a constant value.

func LetToL

func LetToL[S, T any](
	lens Lens[S, T],
	b T,
) Kleisli[ReaderResult[S], S]

LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state. It sets the focused field to a constant value. This is a PURE operation (side-effect free).

Parameters:

  • lens: A lens that focuses on a field of type T within state S
  • b: The constant value to set

Returns:

  • A function that transforms ReaderResult[S] to ReaderResult[S]

Example:

type Config struct {
    Debug   bool
    Timeout int
}

debugLens := lens.MakeLens(
    func(c Config) bool { return c.Debug },
    func(c Config, d bool) Config { c.Debug = d; return c },
)

result := F.Pipe1(
    readereither.Of[error](Config{Debug: true, Timeout: 30}),
    readereither.LetToL(debugLens, false),
)
// result when executed will be Right(Config{Debug: false, Timeout: 30})

func OrElse

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

OrElse recovers from a Left (error) by providing an alternative computation with access to context.Context. If the ReaderResult is Right, it returns the value unchanged. If the ReaderResult is Left, it applies the provided function to the error value, which returns a new ReaderResult that replaces the original.

This is useful for error recovery, fallback logic, or chaining alternative computations that need access to the context (for cancellation, deadlines, or values).

Example:

// Recover with context-aware fallback
recover := readerresult.OrElse(func(err error) readerresult.ReaderResult[int] {
    if err.Error() == "not found" {
        return func(ctx context.Context) result.Result[int] {
            // Could check ctx.Err() here
            return result.Of(42)
        }
    }
    return readerresult.Left[int](err)
})

OrElse recovers from a Left (error) by providing an alternative computation. If the ReaderResult is Right, it returns the value unchanged. If the ReaderResult is Left, it applies the provided function to the error value, which returns a new ReaderResult that replaces the original.

This is useful for error recovery, fallback logic, or chaining alternative computations in the context of Reader computations with context.Context.

Example:

// Recover from specific errors with fallback values
recover := readerresult.OrElse(func(err error) readerresult.ReaderResult[int] {
    if err.Error() == "not found" {
        return readerresult.Of[int](0) // default value
    }
    return readerresult.Left[int](err) // propagate other errors
})
result := recover(readerresult.Left[int](errors.New("not found")))(ctx) // Right(0)
result := recover(readerresult.Of(42))(ctx) // Right(42) - unchanged

func SLog

func SLog[A any](message string) Kleisli[Result[A], A]

SLog creates a Kleisli arrow that logs a Result value at INFO level using the logger from the context. This is a convenience function that uses SLogWithCallback with default settings.

The Result value is logged and then returned unchanged, making this function suitable for use in functional pipelines for debugging or monitoring purposes.

This function logs both successful values and errors:

  • Success values are logged with the key "value"
  • Error values are logged with the key "error"

Type Parameters:

  • A: The type of the success value in the Result

Parameters:

  • message: The log message to display

Returns:

  • A Kleisli arrow that takes a Result[A] and returns a ReaderResult[A] The returned ReaderResult, when executed with a context, logs the Result at INFO level and returns it unchanged

Example - Logging a successful computation:

ctx := t.Context()

// Simple value logging
res := result.Of(42)
logged := SLog[int]("Processing number")(res)(ctx)
// Logs: level=INFO msg="Processing number" value=42
// logged == result.Of(42)

Example - Logging in a pipeline:

type User struct {
    ID   int
    Name string
}

fetchUser := func(id int) result.Result[User] {
    return result.Of(User{ID: id, Name: "Alice"})
}

processUser := func(user User) result.Result[string] {
    return result.Of(fmt.Sprintf("Processed: %s", user.Name))
}

ctx := t.Context()

// Log at each step
userResult := fetchUser(123)
logged1 := SLog[User]("Fetched user")(userResult)(ctx)
// Logs: level=INFO msg="Fetched user" value={ID:123 Name:Alice}

processed := result.Chain(processUser)(logged1)
logged2 := SLog[string]("Processed user")(processed)(ctx)
// Logs: level=INFO msg="Processed user" value="Processed: Alice"

Example - Logging errors:

err := errors.New("database connection failed")
errResult := result.Left[User](err)
logged := SLog[User]("Database operation")(errResult)(ctx)
// Logs: level=INFO msg="Database operation" error="database connection failed"
// logged still contains the error

Example - Using with context logger:

// Set up a custom logger in the context
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
ctx := logging.WithLogger(logger)(t.Context())

res := result.Of("important data")
logged := SLog[string]("Critical operation")(res)(ctx)
// Uses the logger from context to log the message

Note: The function uses logging.GetLoggerFromContext to retrieve the logger, which falls back to the global logger if no logger is found in the context.

func SLogWithCallback

func SLogWithCallback[A any](
	logLevel slog.Level,
	cb func(context.Context) *slog.Logger,
	message string) Kleisli[Result[A], A]

SLogWithCallback creates a Kleisli arrow that logs a Result value using a custom logger callback and log level. The Result value is logged and then returned unchanged, making this function suitable for use in functional pipelines.

This function logs both successful values and errors:

  • Success values are logged with the key "value"
  • Error values are logged with the key "error"

The logging is performed as a side effect while preserving the Result value, allowing it to be used in the middle of a computation pipeline without interrupting the flow.

Type Parameters:

  • A: The type of the success value in the Result

Parameters:

  • logLevel: The slog.Level at which to log (e.g., LevelInfo, LevelDebug, LevelError)
  • cb: A callback function that retrieves a *slog.Logger from the context
  • message: The log message to display

Returns:

  • A Kleisli arrow that takes a Result[A] and returns a ReaderResult[A] The returned ReaderResult, when executed with a context, logs the Result and returns it unchanged

Example:

type User struct {
    ID   int
    Name string
}

// Custom logger callback
getLogger := func(ctx context.Context) *slog.Logger {
    return slog.Default()
}

// Create a logging function for debug level
logDebug := SLogWithCallback[User](slog.LevelDebug, getLogger, "User data")

// Use in a pipeline
ctx := t.Context()
user := result.Of(User{ID: 123, Name: "Alice"})
logged := logDebug(user)(ctx) // Logs: level=DEBUG msg="User data" value={ID:123 Name:Alice}
// logged still contains the User value

Example with error:

err := errors.New("user not found")
userResult := result.Left[User](err)
logged := logDebug(userResult)(ctx) // Logs: level=DEBUG msg="User data" error="user not found"
// logged still contains the error

func TailRec

func TailRec[A, B any](f Kleisli[A, Trampoline[A, B]]) Kleisli[A, B]

TailRec implements tail-recursive computation for ReaderResult with context cancellation support.

TailRec takes a Kleisli function that returns Trampoline[A, B] and converts it into a stack-safe, tail-recursive computation. The function repeatedly applies the Kleisli until it produces a Land value.

The implementation includes a short-circuit mechanism that checks for context cancellation on each iteration. If the context is canceled (ctx.Err() != nil), the computation immediately returns a Left result containing the context's cause error, preventing unnecessary computation.

Type Parameters:

  • A: The input type for the recursive step
  • B: The final result type

Parameters:

  • f: A Kleisli function that takes an A and returns a ReaderResult containing Trampoline[A, B]. When the result is Bounce(a), recursion continues with the new value 'a'. When the result is Land(b), recursion terminates with the final value 'b'.

Returns:

  • A Kleisli function that performs the tail-recursive computation in a stack-safe manner.

Behavior:

  • On each iteration, checks if the context has been canceled (short circuit)
  • If canceled, returns result.Left[B](context.Cause(ctx))
  • If the step returns Left[B](error), propagates the error
  • If the step returns Right[A](Bounce(a)), continues recursion with new value 'a'
  • If the step returns Right[A](Land(b)), terminates with success value 'b'

Example - Factorial computation with context:

type State struct {
    n   int
    acc int
}

factorialStep := func(state State) ReaderResult[tailrec.Trampoline[State, int]] {
    return func(ctx context.Context) result.Result[tailrec.Trampoline[State, int]] {
        if state.n <= 0 {
            return result.Of(tailrec.Land[State](state.acc))
        }
        return result.Of(tailrec.Bounce[int](State{state.n - 1, state.acc * state.n}))
    }
}

factorial := TailRec(factorialStep)
result := factorial(State{5, 1})(ctx) // Returns result.Of(120)

Example - Context cancellation:

ctx, cancel := context.WithCancel(t.Context())
cancel() // Cancel immediately

computation := TailRec(someStep)
result := computation(initialValue)(ctx)
// Returns result.Left[B](context.Cause(ctx)) without executing any steps

func TraverseArray

func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B]

TraverseArray transforms an array

func TraverseArrayWithIndex

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

TraverseArrayWithIndex transforms an array

func WithContextK

func WithContextK[A, B any](f Kleisli[A, B]) Kleisli[A, B]

WithContextK wraps a Kleisli arrow with context cancellation checking. This is a higher-order function that takes a Kleisli arrow and returns a new Kleisli arrow that checks for context cancellation before executing.

IMPORTANT: This function composes the Kleisli arrow with WithContext, ensuring that the resulting ReaderResult checks for cancellation before execution. This is particularly useful when building pipelines of Kleisli arrows where you want cancellation checking at each step.

Type Parameters:

  • A: The input type of the Kleisli arrow
  • B: The output type of the Kleisli arrow

Parameters:

  • f: The Kleisli arrow to wrap with cancellation checking

Returns:

  • A new Kleisli arrow that checks for cancellation before executing f

Example:

// Define a Kleisli arrow
processUser := func(id int) readerresult.ReaderResult[User] {
    return func(ctx context.Context) result.Result[User] {
        // Expensive database operation
        return fetchUserFromDB(ctx, id)
    }
}

// Wrap with cancellation checking
safeProcessUser := readerresult.WithContextK(processUser)

// Use in a pipeline
pipeline := F.Pipe1(
    readerresult.Of(123),
    readerresult.Chain(safeProcessUser),
)

// If context is cancelled, processUser never executes
ctx, cancel := context.WithCancel(context.Background())
cancel()
result := pipeline(ctx) // Left(context.Canceled)

Example with multiple steps:

getUserK := readerresult.WithContextK(func(id int) readerresult.ReaderResult[User] {
    return func(ctx context.Context) result.Result[User] {
        return fetchUser(ctx, id)
    }
})

getOrdersK := readerresult.WithContextK(func(user User) readerresult.ReaderResult[[]Order] {
    return func(ctx context.Context) result.Result[[]Order] {
        return fetchOrders(ctx, user.ID)
    }
})

// Each step checks for cancellation
pipeline := F.Pipe2(
    readerresult.Of(123),
    readerresult.Chain(getUserK),
    readerresult.Chain(getOrdersK),
)

// If context is cancelled at any point, remaining steps don't execute
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
result := pipeline(ctx)

Use cases:

  • Building cancellation-aware pipelines
  • Ensuring each step in a chain respects cancellation
  • Implementing timeout-aware multi-step operations
  • Preventing cascading failures in long pipelines

type Lens

type Lens[S, T any] = lens.Lens[S, T]

Lens is an optic that focuses on a part of a data structure that is always present. This is an alias for lens.Lens[S, T].

Type Parameters:

  • S: The source type
  • T: The target type

Example:

// A lens that focuses on the Name field of a User
nameLens := lens.Lens[User, string]{...}

type Operator

type Operator[A, B any] = Kleisli[ReaderResult[A], B]

Operator represents a function that transforms one ReaderResult into another. This is a specialized Kleisli where the input is itself a ReaderResult.

Type Parameters:

  • A: The input ReaderResult's success type
  • B: The output ReaderResult's success type

Signature:

type Operator[A, B any] = func(ReaderResult[A]) ReaderResult[B]

Example:

mapToString := readerresult.Map(func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
// mapToString is an Operator[int, string]

func BindTo

func BindTo[S1, T any](
	setter func(T) S1,
) Operator[T, S1]

BindTo initializes a new state [S1] from a value [T]

func BindToP

func BindToP[S1, T any](
	setter Prism[S1, T],
) Operator[T, S1]

func Chain

func Chain[A, B any](f Kleisli[A, B]) Operator[A, B]

Chain creates an operator that sequences two ReaderResult computations. This is the curried version where the Kleisli function is provided first.

Type Parameters:

  • A: The success type of the first ReaderResult
  • B: The success type of the second ReaderResult

Parameters:

  • f: The Kleisli function that takes the success value and returns a new ReaderResult

Returns:

  • An Operator that sequences the computations

Example:

toUpper := readerresult.Chain(func(s string) readerresult.ReaderResult[string] {
    return readerresult.Of(strings.ToUpper(s))
})
rr := readerresult.Of("hello")
result := toUpper(rr)(t.Context()) // Right("HELLO")

func ChainFirst

func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A]

ChainFirst creates an operator that sequences two ReaderResult computations, executing the second for its side effects but returning the value from the first. This is the curried version where the Kleisli function is provided first.

IMPORTANT: Combining with IO operations makes sense because ReaderResult already has side effects due to context.Context (cancellation, deadlines, values). ChainFirst executes both computations for their side effects, which is natural when working with effectful computations.

Type Parameters:

  • A: The success type of the first ReaderResult (returned value)
  • B: The success type of the second ReaderResult (discarded)

Parameters:

  • f: The Kleisli function that takes the success value and returns a second ReaderResult

Returns:

  • An Operator that executes both computations but returns the first's value

Example:

logValue := readerresult.ChainFirst(func(x int) readerresult.ReaderResult[string] {
    return func(ctx context.Context) result.Result[string] {
        fmt.Printf("Value: %d\n", x)
        return result.Of("logged")
    }
})
result := logValue(readerresult.Of(42))(t.Context()) // Prints "Value: 42", returns Right(42)

func ChainFirstIOK added in v2.1.14

func ChainFirstIOK[A, B any](f io.Kleisli[A, B]) Operator[A, A]

ChainFirstIOK creates an operator that sequences a ReaderResult with an IO computation for its side effects, but returns the original value. This is the curried version.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This function executes the IO operation for its side effects while preserving the original value.

Type Parameters:

  • A: The success type of the ReaderResult (returned value)
  • B: The success type of the IO computation (discarded)

Parameters:

  • f: The IO Kleisli function for side effects

Returns:

  • An Operator that executes both but returns the original value

Example:

logIO := readerresult.ChainFirstIOK(func(x int) func() string {
    return func() string {
        fmt.Printf("Processing: %d\n", x)
        return "logged"
    }
})
result := logIO(readerresult.Of(42))(t.Context()) // Prints "Processing: 42", returns Right(42)

func ChainFirstLeft added in v2.1.14

func ChainFirstLeft[A, B any](f Kleisli[error, B]) Operator[A, A]

ChainFirstLeft executes a computation on the Left (error) value for its side effects, but preserves the original error. This is useful for error logging or metrics.

Type Parameters:

  • A: The success type of the ReaderResult
  • B: The success type of the error handler (discarded)

Parameters:

  • f: The Kleisli function that handles the error

Returns:

  • An Operator that executes the error handler but preserves the original error

Example:

logError := readerresult.ChainFirstLeft[int](func(err error) readerresult.ReaderResult[string] {
    return func(ctx context.Context) result.Result[string] {
        fmt.Printf("Error occurred: %v\n", err)
        return result.Of("logged")
    }
})
rr := readerresult.Left[int](errors.New("failed"))
result := logError(rr)(t.Context()) // Prints "Error occurred: failed", returns Left(error("failed"))

func ChainFirstLeftIOK added in v2.1.14

func ChainFirstLeftIOK[A, B any](f io.Kleisli[error, B]) Operator[A, A]

ChainFirstLeftIOK executes an IO computation on the Left (error) value for its side effects, but preserves the original error. This is useful for error logging or metrics with IO operations.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This allows error handling with side effects like logging to external systems.

Type Parameters:

  • A: The success type of the ReaderResult
  • B: The success type of the IO error handler (discarded)

Parameters:

  • f: The IO Kleisli function that handles the error

Returns:

  • An Operator that executes the IO error handler but preserves the original error

Example:

logErrorIO := readerresult.ChainFirstLeftIOK[int](func(err error) func() string {
    return func() string {
        fmt.Printf("Error: %v\n", err)
        return "logged"
    }
})
rr := readerresult.Left[int](errors.New("failed"))
result := logErrorIO(rr)(t.Context()) // Prints "Error: failed", returns Left(error("failed"))

func ChainIOEitherK added in v2.1.14

func ChainIOEitherK[A, B any](f ioresult.Kleisli[A, B]) Operator[A, B]

ChainIOEitherK creates an operator that sequences a ReaderResult with an IOResult computation.

IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context, and IOResult has side effects from IO operations with error handling. This combination provides context-aware error handling with IO operations.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the IOResult computation

Parameters:

  • f: The IOResult Kleisli function

Returns:

  • An Operator that sequences the computations

Example:

ioResultOp := readerresult.ChainIOEitherK(func(x int) func() result.Result[string] {
    return func() result.Result[string] {
        if x > 0 {
            return result.Of(fmt.Sprintf("positive: %d", x))
        }
        return result.Error[string](errors.New("not positive"))
    }
})
result := ioResultOp(readerresult.Of(42))(t.Context()) // Right("positive: 42")

func ChainIOK added in v2.1.14

func ChainIOK[A, B any](f io.Kleisli[A, B]) Operator[A, B]

ChainIOK creates an operator that sequences a ReaderResult with an IO computation. This is the curried version where the IO Kleisli function is provided first.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context (cancellation, deadlines, values), and IO has side effects from IO operations. This combination allows context-aware error handling with IO operations.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the IO computation

Parameters:

  • f: The IO Kleisli function that takes the success value and returns an IO computation

Returns:

  • An Operator that sequences the computations

Example:

logIO := readerresult.ChainIOK(func(x int) func() string {
    return func() string {
        fmt.Printf("Value: %d\n", x)
        return "logged"
    }
})
result := logIO(readerresult.Of(42))(t.Context()) // Prints "Value: 42", returns Right("logged")

func ChainIOResultK added in v2.1.14

func ChainIOResultK[A, B any](f ioresult.Kleisli[A, B]) Operator[A, B]

ChainIOResultK is an alias for ChainIOEitherK. It creates an operator that sequences a ReaderResult with an IOResult computation.

IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations. This provides context-aware error handling with IO operations.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the IOResult computation

Parameters:

  • f: The IOResult Kleisli function

Returns:

  • An Operator that sequences the computations

Example:

ioResultOp := readerresult.ChainIOResultK(func(x int) func() result.Result[string] {
    return func() result.Result[string] {
        return result.Of(fmt.Sprintf("value: %d", x))
    }
})
result := ioResultOp(readerresult.Of(42))(t.Context()) // Right("value: 42")

func ChainTo

func ChainTo[A, B any](b ReaderResult[B]) Operator[A, B]

ChainTo creates an operator that sequences two ReaderResult computations where the second ignores the first's success value. This is the curried version where the second ReaderResult is provided first, returning a function that can be applied to any first ReaderResult.

IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, ChainTo WILL execute the first ReaderResult to allow any side effects to occur, then discard the success result and execute the second ReaderResult with the same context. If the first computation fails, the error is returned immediately without executing the second computation.

Type Parameters:

  • A: The success type of the first ReaderResult (will be discarded if successful)
  • B: The success type of the second ReaderResult

Parameters:

  • b: The second ReaderResult to execute after the first succeeds

Returns:

  • An Operator that executes the first ReaderResult, then b if successful

Example:

logEnd := func(ctx context.Context) result.Result[string] {
    fmt.Println("ending")
    return result.Of("done")
}
thenLogEnd := readerresult.ChainTo[int, string](logEnd)

logStart := func(ctx context.Context) result.Result[int] {
    fmt.Println("starting")
    return result.Of(1)
}
pipeline := thenLogEnd(logStart)
result := pipeline(t.Context()) // Prints "starting" then "ending", returns Right("done")

Example - In a functional pipeline:

step1 := func(ctx context.Context) result.Result[int] {
    fmt.Println("step 1")
    return result.Of(1)
}
step2 := func(ctx context.Context) result.Result[string] {
    fmt.Println("step 2")
    return result.Of("complete")
}
pipeline := F.Pipe1(
    step1,
    readerresult.ChainTo[int, string](step2),
)
output := pipeline(t.Context()) // Prints "step 1" then "step 2", returns Right("complete")

func Contramap added in v2.1.6

func Contramap[A any](f func(context.Context) (context.Context, context.CancelFunc)) Operator[A, A]

Contramap changes the context during the execution of a ReaderResult. This is the contravariant functor operation that transforms the input context.

See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor

Contramap is an alias for Local and is useful for adapting a ReaderResult to work with a modified context by providing a function that transforms the context.

Type Parameters:

  • A: The success type (unchanged)

Parameters:

  • f: Function to transform the context, returning a new context and CancelFunc

Returns:

  • An Operator that takes a ReaderResult[A] and returns a ReaderResult[A]

func FilterOrElse added in v2.1.0

func FilterOrElse[A any](pred Predicate[A], onFalse func(A) error) Operator[A, A]

FilterOrElse filters a ReaderResult value based on a predicate. This is a convenience wrapper around readerresult.FilterOrElse that fixes the context type to context.Context.

If the predicate returns true for the Right value, it passes through unchanged. If the predicate returns false, it transforms the Right value into a Left (error) using onFalse. Left values are passed through unchanged.

Parameters:

  • pred: A predicate function that tests the Right value
  • onFalse: A function that converts the failing value into an error

Returns:

  • An Operator that filters ReaderResult values based on the predicate

Example:

// Validate that a number is positive
isPositive := N.MoreThan(0)
onNegative := func(n int) error { return fmt.Errorf("%d is not positive", n) }

filter := readerresult.FilterOrElse(isPositive, onNegative)
result := filter(readerresult.Right(42))(t.Context())

func Flap

func Flap[B, A any](a A) Operator[func(A) B, B]

Flap creates an operator that applies a value to a ReaderResult containing a function. This is the curried version where the value is provided first. Flap is the reverse of Ap - instead of applying a function to a value, it applies a value to a function.

Type Parameters:

  • B: The output type
  • A: The input type

Parameters:

  • a: The value to apply to the function

Returns:

  • An Operator that applies the value to a ReaderResult containing a function

Example:

applyTo42 := readerresult.Flap[string](42)
fabRR := readerresult.Of(func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
result := applyTo42(fabRR)(t.Context()) // Right("value: 42")

func Local added in v2.1.6

func Local[A any](f func(context.Context) (context.Context, context.CancelFunc)) Operator[A, A]

Local changes the context during the execution of a ReaderResult. This allows you to modify the context before passing it to a ReaderResult computation.

See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor

Local is particularly useful for:

  • Adding values to the context
  • Setting timeouts or deadlines
  • Modifying context metadata

The function f returns both a new context and a CancelFunc. The CancelFunc is automatically called (via defer) after the ReaderResult computation completes to ensure proper cleanup.

Type Parameters:

  • A: The result type (unchanged)

Parameters:

  • f: Function to transform the context, returning a new context and CancelFunc

Returns:

  • An Operator that takes a ReaderResult[A] and returns a ReaderResult[A]

func Map

func Map[A, B any](f func(A) B) Operator[A, B]

Map creates an operator that applies a function to the success value of a ReaderResult. This is the curried version where the function is provided first.

Type Parameters:

  • A: The input success type
  • B: The output success type

Parameters:

  • f: The function to apply to the success value

Returns:

  • An Operator that applies the function to a ReaderResult

Example:

toString := readerresult.Map(func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
rr := readerresult.Of(42)
result := toString(rr)(t.Context()) // Right("value: 42")

func MapTo

func MapTo[A, B any](b B) Operator[A, B]

MapTo creates an operator that executes a ReaderResult computation, discards its success value, and returns a constant value. This is the curried version where the constant value is provided first, returning a function that can be applied to any ReaderResult.

IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, MapTo WILL execute the input ReaderResult to allow any side effects to occur, then discard the success result and return the constant value. If the computation fails, the error is preserved.

Type Parameters:

  • A: The success type of the input ReaderResult (will be discarded if successful)
  • B: The type of the constant value to return on success

Parameters:

  • b: The constant value to return on success

Returns:

  • An Operator that executes a ReaderResult[A], preserves errors, but replaces success with b

Example:

logStep := func(ctx context.Context) result.Result[int] {
    fmt.Println("step executed")
    return result.Of(42)
}
toDone := readerresult.MapTo[int, string]("done")
pipeline := toDone(logStep)
result := pipeline(t.Context()) // Prints "step executed", returns Right("done")

Example - In a functional pipeline:

step1 := func(ctx context.Context) result.Result[int] {
    fmt.Println("processing")
    return result.Of(1)
}
pipeline := F.Pipe1(
    step1,
    readerresult.MapTo[int, string]("complete"),
)
output := pipeline(t.Context()) // Prints "processing", returns Right("complete")

func Promap added in v2.1.6

func Promap[A, B any](f func(context.Context) (context.Context, context.CancelFunc), g func(A) B) Operator[A, B]

Promap is the profunctor map operation that transforms both the input and output of a context-based ReaderResult. It applies f to the input context (contravariantly) and g to the output value (covariantly).

See: https://github.com/fantasyland/fantasy-land?tab=readme-ov-file#profunctor

This operation allows you to:

  • Modify the context before passing it to the ReaderResult (via f)
  • Transform the success value after the computation completes (via g)

The function f returns both a new context and a CancelFunc that should be called to release resources. The error type is fixed as error and remains unchanged through the transformation.

Type Parameters:

  • A: The original success type produced by the ReaderResult
  • B: The new output success type

Parameters:

  • f: Function to transform the input context (contravariant)
  • g: Function to transform the output success value from A to B (covariant)

Returns:

  • An Operator that takes a ReaderResult[A] and returns a ReaderResult[B]

func TapIOK added in v2.1.14

func TapIOK[A, B any](f io.Kleisli[A, B]) Operator[A, A]

TapIOK is an alias for ChainFirstIOK. It creates an operator that sequences a ReaderResult with an IO computation for its side effects, but returns the original value.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. Tap executes the IO operation for its side effects while preserving the original value.

Type Parameters:

  • A: The success type of the ReaderResult (returned value)
  • B: The success type of the IO computation (discarded)

Parameters:

  • f: The IO Kleisli function for side effects

Returns:

  • An Operator that executes both but returns the original value

Example:

tapLog := readerresult.TapIOK(func(x int) func() string {
    return func() string {
        fmt.Printf("Tapping: %d\n", x)
        return "logged"
    }
})
result := tapLog(readerresult.Of(42))(t.Context()) // Prints "Tapping: 42", returns Right(42)

func TapLeftIOK added in v2.1.14

func TapLeftIOK[A, B any](f io.Kleisli[error, B]) Operator[A, A]

TapLeftIOK is an alias for ChainFirstLeftIOK. It executes an IO computation on the Left (error) value for its side effects, but preserves the original error.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. Tap allows error handling with side effects while preserving the error.

Type Parameters:

  • A: The success type of the ReaderResult
  • B: The success type of the IO error handler (discarded)

Parameters:

  • f: The IO Kleisli function that handles the error

Returns:

  • An Operator that executes the IO error handler but preserves the original error

Example:

tapErrorIO := readerresult.TapLeftIOK[int](func(err error) func() string {
    return func() string {
        fmt.Printf("Tapping error: %v\n", err)
        return "logged"
    }
})
rr := readerresult.Left[int](errors.New("failed"))
result := tapErrorIO(rr)(t.Context()) // Prints "Tapping error: failed", returns Left(error("failed"))

func TapSLog

func TapSLog[A any](message string) Operator[A, A]

type Option

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

Option represents an optional value that may or may not be present. This is an alias for option.Option[A].

Type Parameters:

  • A: The type of the value that may be present

Example:

opt := option.Some(42)           // Option[int] with value
none := option.None[int]()       // Option[int] without value

type Predicate added in v2.1.0

type Predicate[A any] = predicate.Predicate[A]

Predicate represents a function that tests a value and returns a boolean. This is an alias for predicate.Predicate[A].

Type Parameters:

  • A: The type of the value to test

Signature:

type Predicate[A any] = func(A) bool

Example:

isPositive := func(x int) bool { return x > 0 }
// isPositive is a Predicate[int]

type Prism

type Prism[S, T any] = prism.Prism[S, T]

Prism is an optic that focuses on a part of a data structure that may or may not be present. This is an alias for prism.Prism[S, T].

Type Parameters:

  • S: The source type
  • T: The target type

Example:

// A prism that extracts an int from a string if it's a valid number
intPrism := prism.Prism[string, int]{...}

type Reader

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

Reader represents a computation that depends on an environment R to produce a value A. This is an alias for reader.Reader[R, A].

Type Parameters:

  • R: The type of the environment/context
  • A: The type of the produced value

Example:

type Config struct { Port int }
getPort := func(cfg Config) int { return cfg.Port }
// getPort is a Reader[Config, int]

type ReaderResult

type ReaderResult[A any] = readereither.ReaderEither[context.Context, error, A]

ReaderResult is a specialization of the Reader monad for the typical Go scenario. It represents an effectful computation that:

  • Depends on context.Context (for cancellation, deadlines, values)
  • Can fail with an error
  • Produces a value of type A on success

IMPORTANT: This is an EFFECTFUL type because context.Context is effectful. The computation's behavior can change based on context state (cancelled, timed out, etc.).

Type Parameters:

  • A: The type of the success value

Signature:

type ReaderResult[A any] = func(context.Context) Result[A]

Example:

getUserByID := func(ctx context.Context) result.Result[User] {
    if ctx.Err() != nil {
        return result.Error[User](ctx.Err())
    }
    // Fetch user from database
    return result.Of(User{ID: 123, Name: "Alice"})
}
// getUserByID is a ReaderResult[User]

func Ask

func Ask() ReaderResult[context.Context]

Ask returns a ReaderResult that provides access to the context.Context. This allows you to read the context within a ReaderResult computation.

Returns:

  • A ReaderResult that returns the context.Context as its success value

Example:

rr := readerresult.Ask()
result := rr(t.Context()) // Right(t.Context())

// Use in a chain to access context
pipeline := F.Pipe2(
    readerresult.Ask(),
    readerresult.Chain(func(ctx context.Context) readerresult.ReaderResult[string] {
        if deadline, ok := ctx.Deadline(); ok {
            return readerresult.Of(fmt.Sprintf("deadline: %v", deadline))
        }
        return readerresult.Of("no deadline")
    }),
)

func Curry0

func Curry0[A any](f func(context.Context) (A, error)) ReaderResult[A]

Curry0 converts a Go function with context and no additional parameters into a ReaderResult. This is useful for adapting context-aware functions to the ReaderResult monad.

Type Parameters:

  • A: The return type of the function

Parameters:

  • f: A function that takes a context and returns a value and error

Returns:

  • A ReaderResult that wraps the function

Example:

// Idiomatic Go function
getConfig := func(ctx context.Context) (Config, error) {
    // Check context cancellation
    if ctx.Err() != nil {
        return Config{}, ctx.Err()
    }
    return Config{Value: 42}, nil
}

// Convert to ReaderResult for functional composition
configRR := readerresult.Curry0(getConfig)
result := configRR(t.Context()) // Right(Config{Value: 42})

func Do

func Do[S any](
	empty S,
) ReaderResult[S]

Do creates an empty context of type [S] to be used with the Bind operation. This is the starting point for do-notation style composition.

Example:

type State struct {
    UserID   string
    TenantID string
}
result := readereither.Do(State{})

func FromEither

func FromEither[A any](e Either[A]) ReaderResult[A]

FromEither lifts an Either value into a ReaderResult. The Either value is returned as-is, ignoring the context.

Type Parameters:

  • A: The success type of the Either value

Parameters:

  • e: The Either value to lift

Returns:

  • A ReaderResult that returns the Either value

Example:

either := result.Of[error](42)
rr := readerresult.FromEither(either)
result := rr(t.Context()) // Right(42)

func FromIO added in v2.1.14

func FromIO[A any](t io.IO[A]) ReaderResult[A]

FromIO lifts a pure IO computation into a ReaderResult. The IO computation is executed when the ReaderResult is run, ignoring the context.

IMPORTANT: While IO represents a side-effectful computation, combining it with ReaderResult makes sense because ReaderResult already has side effects due to its context.Context dependency. The context can be cancelled, has deadlines, and carries values - all side effects. Therefore, adding IO operations (which also have side effects) is a natural fit.

Type Parameters:

  • A: The success type of the IO computation

Parameters:

  • t: The IO computation to lift

Returns:

  • A ReaderResult that executes the IO computation and wraps the result in Right

Example:

ioOp := func() int { return 42 }
rr := readerresult.FromIO(ioOp)
result := rr(t.Context()) // Right(42)

func FromIOResult added in v2.1.14

func FromIOResult[A any](t ioresult.IOResult[A]) ReaderResult[A]

FromIOResult lifts an IOResult computation into a ReaderResult. The IOResult computation is executed when the ReaderResult is run, ignoring the context.

IMPORTANT: Combining IOResult with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context (cancellation, deadlines, values), and IOResult has side effects from IO operations. This combination allows you to work with context-aware error handling while performing IO operations.

Type Parameters:

  • A: The success type of the IOResult computation

Parameters:

  • t: The IOResult computation to lift

Returns:

  • A ReaderResult that executes the IOResult computation

Example:

ioResultOp := func() result.Result[int] {
    return result.Of(42)
}
rr := readerresult.FromIOResult(ioResultOp)
result := rr(t.Context()) // Right(42)

func FromReader

func FromReader[A any](r Reader[context.Context, A]) ReaderResult[A]

FromReader lifts a Reader computation into a ReaderResult. The Reader computation receives the context and its result is wrapped in Right.

Type Parameters:

  • A: The success type of the Reader computation

Parameters:

  • r: The Reader computation to lift

Returns:

  • A ReaderResult that executes the Reader and wraps the result in Right

Example:

reader := func(ctx context.Context) int {
    return 42
}
rr := readerresult.FromReader(reader)
result := rr(t.Context()) // Right(42)

func Left

func Left[A any](l error) ReaderResult[A]

Left creates a ReaderResult that always returns a Left (error) value.

Type Parameters:

  • A: The success type (not used, as this always returns an error)

Parameters:

  • l: The error value to return

Returns:

  • A ReaderResult that always returns Left(l)

Example:

rr := readerresult.Left[int](errors.New("failed"))
result := rr(t.Context()) // Left(error("failed"))

func MonadAp

func MonadAp[A, B any](fab ReaderResult[func(A) B], fa ReaderResult[A]) ReaderResult[B]

MonadAp applies a ReaderResult containing a function to a ReaderResult containing a value. This is the monadic version that takes both ReaderResults as parameters.

Type Parameters:

  • A: The input type
  • B: The output type

Parameters:

  • fab: The ReaderResult containing the function
  • fa: The ReaderResult containing the value

Returns:

  • A ReaderResult with the function applied to the value

Example:

fabRR := readerresult.Of(func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
faRR := readerresult.Of(42)
result := readerresult.MonadAp(fabRR, faRR)(t.Context()) // Right("value: 42")

func MonadChain

func MonadChain[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[B]

MonadChain sequences two ReaderResult computations, passing the success value from the first to the second. This is the monadic version that takes both the ReaderResult and the Kleisli function as parameters.

Type Parameters:

  • A: The success type of the first ReaderResult
  • B: The success type of the second ReaderResult

Parameters:

  • ma: The first ReaderResult to execute
  • f: The Kleisli function that takes the success value and returns a new ReaderResult

Returns:

  • A ReaderResult that sequences both computations

Example:

rr := readerresult.Of(42)
chained := readerresult.MonadChain(rr, func(x int) readerresult.ReaderResult[string] {
    return readerresult.Of(fmt.Sprintf("value: %d", x))
})
result := chained(t.Context()) // Right("value: 42")

func MonadChainEitherK

func MonadChainEitherK[A, B any](ma ReaderResult[A], f func(A) Either[B]) ReaderResult[B]

MonadChainEitherK sequences a ReaderResult with a function that returns an Either. This is the monadic version that takes both the ReaderResult and the function as parameters.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the output Either

Parameters:

  • ma: The ReaderResult to execute
  • f: The function that takes the success value and returns an Either

Returns:

  • A ReaderResult that sequences both computations

Example:

rr := readerresult.Of(42)
chained := readerresult.MonadChainEitherK(rr, func(x int) result.Result[string] {
    if x > 0 {
        return result.Of(fmt.Sprintf("positive: %d", x))
    }
    return result.Error[string](errors.New("not positive"))
})
result := chained(t.Context()) // Right("positive: 42")

func MonadChainFirst

func MonadChainFirst[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[A]

MonadChainFirst sequences two ReaderResult computations, executing the second for its side effects but returning the value from the first. This is the monadic version that takes both the ReaderResult and the Kleisli function as parameters.

IMPORTANT: Combining with IO operations makes sense because ReaderResult already has side effects due to context.Context (cancellation, deadlines, values). ChainFirst executes both computations for their side effects, which is natural when working with effectful computations.

Type Parameters:

  • A: The success type of the first ReaderResult (returned value)
  • B: The success type of the second ReaderResult (discarded)

Parameters:

  • ma: The first ReaderResult to execute
  • f: The Kleisli function that takes the success value and returns a second ReaderResult

Returns:

  • A ReaderResult that executes both computations but returns the first's value

Example:

rr := readerresult.Of(42)
withLogging := readerresult.MonadChainFirst(rr, func(x int) readerresult.ReaderResult[string] {
    return func(ctx context.Context) result.Result[string] {
        fmt.Printf("Value: %d\n", x)
        return result.Of("logged")
    }
})
result := withLogging(t.Context()) // Prints "Value: 42", returns Right(42)

func MonadChainFirstIOK added in v2.1.14

func MonadChainFirstIOK[A, B any](ma ReaderResult[A], f io.Kleisli[A, B]) ReaderResult[A]

MonadChainFirstIOK sequences a ReaderResult with an IO computation for its side effects, but returns the original value. This is the monadic version.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. This function executes the IO operation for its side effects (like logging or metrics) while preserving the original value, which is natural when working with effectful computations.

Type Parameters:

  • A: The success type of the ReaderResult (returned value)
  • B: The success type of the IO computation (discarded)

Parameters:

  • ma: The ReaderResult to execute
  • f: The IO Kleisli function for side effects

Returns:

  • A ReaderResult that executes both but returns the original value

Example:

rr := readerresult.Of(42)
withLog := readerresult.MonadChainFirstIOK(rr, func(x int) func() string {
    return func() string {
        fmt.Printf("Processing: %d\n", x)
        return "logged"
    }
})
result := withLog(t.Context()) // Prints "Processing: 42", returns Right(42)

func MonadChainIOK added in v2.1.14

func MonadChainIOK[A, B any](ma ReaderResult[A], f io.Kleisli[A, B]) ReaderResult[B]

MonadChainIOK sequences a ReaderResult with an IO computation, lifting the IO into ReaderResult. This is the monadic version that takes both the ReaderResult and the IO Kleisli function as parameters.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. ReaderResult has side effects from context.Context (cancellation, deadlines, values), and IO has side effects from IO operations. This combination allows context-aware error handling with IO operations.

Type Parameters:

  • A: The success type of the input ReaderResult
  • B: The success type of the IO computation

Parameters:

  • ma: The ReaderResult to execute
  • f: The IO Kleisli function that takes the success value and returns an IO computation

Returns:

  • A ReaderResult that sequences both computations

Example:

rr := readerresult.Of(42)
withIO := readerresult.MonadChainIOK(rr, func(x int) func() string {
    return func() string {
        fmt.Printf("Value: %d\n", x)
        return "done"
    }
})
result := withIO(t.Context()) // Prints "Value: 42", returns Right("done")

func MonadChainTo

func MonadChainTo[A, B any](ma ReaderResult[A], b ReaderResult[B]) ReaderResult[B]

MonadChainTo sequences two ReaderResult computations where the second ignores the first's success value. This is the monadic version that takes both ReaderResults as parameters.

IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, MonadChainTo WILL execute the first ReaderResult to allow any side effects to occur, then discard the success result and execute the second ReaderResult with the same context. If the first computation fails, the error is returned immediately without executing the second computation.

Type Parameters:

  • A: The success type of the first ReaderResult (will be discarded if successful)
  • B: The success type of the second ReaderResult

Parameters:

  • ma: The first ReaderResult to execute (side effects will occur, success value discarded)
  • b: The second ReaderResult to execute if ma succeeds

Returns:

  • A ReaderResult that executes ma, then b if ma succeeds, returning b's result

Example:

logStart := func(ctx context.Context) result.Result[int] {
    fmt.Println("starting")
    return result.Of(1)
}
logEnd := func(ctx context.Context) result.Result[string] {
    fmt.Println("ending")
    return result.Of("done")
}
r := readerresult.MonadChainTo(logStart, logEnd)
result := r(t.Context()) // Prints "starting" then "ending", returns Right("done")

func MonadFlap

func MonadFlap[B, A any](fab ReaderResult[func(A) B], a A) ReaderResult[B]

MonadFlap applies a value to a ReaderResult containing a function. This is the monadic version that takes both the ReaderResult and the value as parameters. Flap is the reverse of Ap - instead of applying a function to a value, it applies a value to a function.

Type Parameters:

  • B: The output type
  • A: The input type

Parameters:

  • fab: The ReaderResult containing the function
  • a: The value to apply to the function

Returns:

  • A ReaderResult with the value applied to the function

Example:

fabRR := readerresult.Of(func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
result := readerresult.MonadFlap(fabRR, 42)(t.Context()) // Right("value: 42")

func MonadMap

func MonadMap[A, B any](fa ReaderResult[A], f func(A) B) ReaderResult[B]

MonadMap applies a function to the success value of a ReaderResult. This is the monadic version that takes both the ReaderResult and the function as parameters.

Type Parameters:

  • A: The input success type
  • B: The output success type

Parameters:

  • fa: The ReaderResult to map over
  • f: The function to apply to the success value

Returns:

  • A ReaderResult with the function applied to the success value

Example:

rr := readerresult.Of(42)
mapped := readerresult.MonadMap(rr, func(x int) string {
    return fmt.Sprintf("value: %d", x)
})
result := mapped(t.Context()) // Right("value: 42")

func MonadMapTo

func MonadMapTo[A, B any](ma ReaderResult[A], b B) ReaderResult[B]

MonadMapTo executes a ReaderResult computation, discards its success value, and returns a constant value. This is the monadic version that takes both the ReaderResult and the constant value as parameters.

IMPORTANT: ReaderResult represents a side-effectful computation because it depends on context.Context, which is effectful (can be cancelled, has deadlines, carries values). For this reason, MonadMapTo WILL execute the original ReaderResult to allow any side effects to occur, then discard the success result and return the constant value. If the original computation fails, the error is preserved.

Type Parameters:

  • A: The success type of the first ReaderResult (will be discarded if successful)
  • B: The type of the constant value to return on success

Parameters:

  • ma: The ReaderResult to execute (side effects will occur, success value discarded)
  • b: The constant value to return if ma succeeds

Returns:

  • A ReaderResult that executes ma, preserves errors, but replaces success values with b

Example:

type Config struct { Counter int }
increment := func(ctx context.Context) result.Result[int] {
    // Side effect: log the operation
    fmt.Println("incrementing")
    return result.Of(5)
}
r := readerresult.MonadMapTo(increment, "done")
result := r(t.Context()) // Prints "incrementing", returns Right("done")

func MonadTapIOK added in v2.1.14

func MonadTapIOK[A, B any](ma ReaderResult[A], f io.Kleisli[A, B]) ReaderResult[A]

MonadTapIOK is an alias for MonadChainFirstIOK. It sequences a ReaderResult with an IO computation for its side effects, but returns the original value.

IMPORTANT: Combining IO with ReaderResult makes sense because both represent side-effectful computations. Tap executes the IO operation for its side effects while preserving the original value.

Type Parameters:

  • A: The success type of the ReaderResult (returned value)
  • B: The success type of the IO computation (discarded)

Parameters:

  • ma: The ReaderResult to execute
  • f: The IO Kleisli function for side effects

Returns:

  • A ReaderResult that executes both but returns the original value

Example:

rr := readerresult.Of(42)
withLog := readerresult.MonadTapIOK(rr, func(x int) func() string {
    return func() string {
        fmt.Printf("Tapping: %d\n", x)
        return "logged"
    }
})
result := withLog(t.Context()) // Prints "Tapping: 42", returns Right(42)

func Of

func Of[A any](a A) ReaderResult[A]

Of creates a ReaderResult that always returns a Right (success) value. This is the pointed functor constructor.

Type Parameters:

  • A: The success type

Parameters:

  • a: The success value to return

Returns:

  • A ReaderResult that always returns Right(a)

Example:

rr := readerresult.Of(42)
result := rr(t.Context()) // Right(42)

func Retrying

func Retrying[A any](
	policy R.RetryPolicy,
	action Kleisli[R.RetryStatus, A],
	check Predicate[Result[A]],
) ReaderResult[A]

Retrying retries a ReaderResult computation according to a retry policy with context awareness.

This function implements a retry mechanism for operations that depend on a context.Context and can fail (Result). It respects context cancellation, meaning that if the context is cancelled during retry delays, the operation will stop immediately and return the cancellation error.

The retry loop will continue until one of the following occurs:

  • The action succeeds and the check function returns false (no retry needed)
  • The retry policy returns None (retry limit reached)
  • The check function returns false (indicating success or a non-retryable failure)
  • The context is cancelled (returns context.Canceled or context.DeadlineExceeded)

Type Parameters:

  • A: The type of the success value

Parameters:

  • policy: A RetryPolicy that determines when and how long to wait between retries. The policy receives a RetryStatus on each iteration and returns an optional delay. If it returns None, retrying stops. Common policies include LimitRetries, ExponentialBackoff, and CapDelay from the retry package.

  • action: A Kleisli arrow that takes a RetryStatus and returns a ReaderResult[A]. This function is called on each retry attempt and receives information about the current retry state (iteration number, cumulative delay, etc.). The action depends on a context.Context and produces a Result[A].

  • check: A predicate function that examines the Result[A] and returns true if the operation should be retried, or false if it should stop. This allows you to distinguish between retryable failures (e.g., network timeouts) and permanent failures (e.g., invalid input).

Returns:

  • A ReaderResult[A] that, when executed with a context, will perform the retry logic with context cancellation support and return the final result.

Example:

// Create a retry policy: exponential backoff with a cap, limited to 5 retries
policy := M.Concat(
    retry.LimitRetries(5),
    retry.CapDelay(10*time.Second, retry.ExponentialBackoff(100*time.Millisecond)),
)(retry.Monoid)

// Action that fetches data
fetchData := func(status retry.RetryStatus) ReaderResult[string] {
    return func(ctx context.Context) Result[string] {
        if ctx.Err() != nil {
            return result.Left[string](ctx.Err())
        }
        if status.IterNumber < 3 {
            return result.Left[string](fmt.Errorf("temporary error"))
        }
        return result.Of("success")
    }
}

// Check function: retry on any error except context cancellation
shouldRetry := func(r Result[string]) bool {
    return result.IsLeft(r) && !errors.Is(result.GetLeft(r), context.Canceled)
}

// Create the retrying computation
retryingFetch := Retrying(policy, fetchData, shouldRetry)

// Execute with a cancellable context
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
finalResult := retryingFetch(ctx)
func Right[A any](r A) ReaderResult[A]

Right creates a ReaderResult that always returns a Right (success) value. This is an alias for Of.

Type Parameters:

  • A: The success type

Parameters:

  • r: The success value to return

Returns:

  • A ReaderResult that always returns Right(r)

Example:

rr := readerresult.Right(42)
result := rr(t.Context()) // Right(42)

func SequenceArray

func SequenceArray[A any](ma []ReaderResult[A]) ReaderResult[[]A]

SequenceArray converts a homogeneous sequence of either into an either of sequence

func SequenceT1

func SequenceT1[A any](a ReaderResult[A]) ReaderResult[tuple.Tuple1[A]]

SequenceT1 converts a single ReaderResult into a ReaderResult containing a 1-tuple. This is primarily useful for consistency in generic code or when you need to wrap a single value in a tuple structure.

Type Parameters:

  • A: The type of the value in the ReaderResult

Parameters:

  • a: The ReaderResult to wrap in a tuple

Returns:

  • A ReaderResult containing a Tuple1 with the value from the input

Example:

rr := readerresult.Of(42)
tupled := readerresult.SequenceT1(rr)
result := tupled(context.Background())
// result is Right(Tuple1{F1: 42})

func SequenceT2

func SequenceT2[A, B any](a ReaderResult[A], b ReaderResult[B]) ReaderResult[tuple.Tuple2[A, B]]

SequenceT2 combines two ReaderResults into a single ReaderResult containing a 2-tuple. Both ReaderResults are executed with the same context. If either fails, the entire sequence fails.

Type Parameters:

  • A: The type of the first value
  • B: The type of the second value

Parameters:

  • a: The first ReaderResult
  • b: The second ReaderResult

Returns:

  • A ReaderResult containing a Tuple2 with both values

Example:

getName := readerresult.Of("Alice")
getAge := readerresult.Of(30)
combined := readerresult.SequenceT2(getName, getAge)
result := combined(context.Background())
// result is Right(Tuple2{F1: "Alice", F2: 30})

Example with error:

getName := readerresult.Of("Alice")
getAge := readerresult.Left[int](errors.New("age not found"))
combined := readerresult.SequenceT2(getName, getAge)
result := combined(context.Background())
// result is Left(error("age not found"))

func SequenceT3

func SequenceT3[A, B, C any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C]) ReaderResult[tuple.Tuple3[A, B, C]]

SequenceT3 combines three ReaderResults into a single ReaderResult containing a 3-tuple. All ReaderResults are executed sequentially with the same context. If any fails, the entire sequence fails immediately.

Type Parameters:

  • A: The type of the first value
  • B: The type of the second value
  • C: The type of the third value

Parameters:

  • a: The first ReaderResult
  • b: The second ReaderResult
  • c: The third ReaderResult

Returns:

  • A ReaderResult containing a Tuple3 with all three values

Example:

getUserID := readerresult.Of(123)
getUserName := readerresult.Of("Alice")
getUserEmail := readerresult.Of("alice@example.com")
combined := readerresult.SequenceT3(getUserID, getUserName, getUserEmail)
result := combined(context.Background())
// result is Right(Tuple3{F1: 123, F2: "Alice", F3: "alice@example.com"})

Example with context-aware operations:

fetchUser := func(ctx context.Context) result.Result[string] {
    if ctx.Err() != nil {
        return result.Error[string](ctx.Err())
    }
    return result.Of("Alice")
}
fetchAge := func(ctx context.Context) result.Result[int] {
    return result.Of(30)
}
fetchCity := func(ctx context.Context) result.Result[string] {
    return result.Of("NYC")
}
combined := readerresult.SequenceT3(fetchUser, fetchAge, fetchCity)
result := combined(context.Background())
// result is Right(Tuple3{F1: "Alice", F2: 30, F3: "NYC"})

func SequenceT4

func SequenceT4[A, B, C, D any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], d ReaderResult[D]) ReaderResult[tuple.Tuple4[A, B, C, D]]

SequenceT4 combines four ReaderResults into a single ReaderResult containing a 4-tuple. All ReaderResults are executed sequentially with the same context. If any fails, the entire sequence fails immediately without executing the remaining ones.

Type Parameters:

  • A: The type of the first value
  • B: The type of the second value
  • C: The type of the third value
  • D: The type of the fourth value

Parameters:

  • a: The first ReaderResult
  • b: The second ReaderResult
  • c: The third ReaderResult
  • d: The fourth ReaderResult

Returns:

  • A ReaderResult containing a Tuple4 with all four values

Example:

getID := readerresult.Of(123)
getName := readerresult.Of("Alice")
getEmail := readerresult.Of("alice@example.com")
getAge := readerresult.Of(30)
combined := readerresult.SequenceT4(getID, getName, getEmail, getAge)
result := combined(context.Background())
// result is Right(Tuple4{F1: 123, F2: "Alice", F3: "alice@example.com", F4: 30})

Example with early failure:

getID := readerresult.Of(123)
getName := readerresult.Left[string](errors.New("name not found"))
getEmail := readerresult.Of("alice@example.com")  // Not executed
getAge := readerresult.Of(30)                      // Not executed
combined := readerresult.SequenceT4(getID, getName, getEmail, getAge)
result := combined(context.Background())
// result is Left(error("name not found"))
// getEmail and getAge are never executed due to early failure

Example building a complex structure:

type UserProfile struct {
    ID    int
    Name  string
    Email string
    Age   int
}

fetchUserData := readerresult.SequenceT4(
    fetchUserID(userID),
    fetchUserName(userID),
    fetchUserEmail(userID),
    fetchUserAge(userID),
)

buildProfile := readerresult.Map(func(t tuple.Tuple4[int, string, string, int]) UserProfile {
    return UserProfile{
        ID:    t.F1,
        Name:  t.F2,
        Email: t.F3,
        Age:   t.F4,
    }
})

userProfile := F.Pipe1(fetchUserData, buildProfile)
result := userProfile(context.Background())

func WithContext

func WithContext[A any](ma ReaderResult[A]) ReaderResult[A]

WithContext wraps an existing ReaderResult and performs a context check for cancellation before delegating to the wrapped computation. This provides early cancellation detection, allowing computations to fail fast when the context has been cancelled or has exceeded its deadline.

IMPORTANT: This function checks for context cancellation BEFORE executing the wrapped ReaderResult. If the context is already cancelled or has exceeded its deadline, the computation returns immediately with the cancellation error without executing the wrapped ReaderResult.

The function uses context.Cause(ctx) to extract the cancellation reason, which may be:

  • context.Canceled: The context was explicitly cancelled
  • context.DeadlineExceeded: The context's deadline was exceeded
  • A custom error: If the context was cancelled with a cause (Go 1.20+)

Type Parameters:

  • A: The success type of the ReaderResult

Parameters:

  • ma: The ReaderResult to wrap with cancellation checking

Returns:

  • A ReaderResult that checks for cancellation before executing ma

Example:

// Create a long-running computation
longComputation := func(ctx context.Context) result.Result[int] {
    time.Sleep(5 * time.Second)
    return result.Of(42)
}

// Wrap with cancellation check
safeLongComputation := readerresult.WithContext(longComputation)

// Cancel the context before execution
ctx, cancel := context.WithCancel(context.Background())
cancel()

// The computation returns immediately with cancellation error
result := safeLongComputation(ctx)
// result is Left(context.Canceled) - longComputation never executes

Example with timeout:

fetchData := func(ctx context.Context) result.Result[string] {
    // Simulate slow operation
    time.Sleep(2 * time.Second)
    return result.Of("data")
}

safeFetch := readerresult.WithContext(fetchData)

// Context with 1 second timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

time.Sleep(1500 * time.Millisecond) // Wait for timeout

result := safeFetch(ctx)
// result is Left(context.DeadlineExceeded) - fetchData never executes

Use cases:

  • Wrapping expensive computations to enable early cancellation
  • Preventing unnecessary work when context is already cancelled
  • Implementing timeout-aware operations
  • Building cancellation-aware pipelines

type Result

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

Result represents a computation that can either succeed with a value or fail with an error. This is an alias for result.Result[A], which is equivalent to Either[error, A].

Type Parameters:

  • A: The type of the success value

Example:

success := result.Of[error](42)                // Right(42)
failure := result.Error[int](errors.New("failed")) // Left(error)

type Trampoline

type Trampoline[A, B any] = tailrec.Trampoline[A, B]

Trampoline represents a computation that can be executed in a stack-safe manner using tail recursion elimination. This is an alias for tailrec.Trampoline[A, B].

Type Parameters:

  • A: The input type
  • B: The output type

Example:

// A tail-recursive factorial computation
factorial := tailrec.Trampoline[int, int]{...}

Directories

Path Synopsis
package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error
package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error

Jump to

Keyboard shortcuts

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