readerio

package
v2.0.0 Latest Latest
Warning

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

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

Documentation

Overview

Package readerio provides the ReaderIO monad, which combines the Reader and IO monads.

ReaderIO[R, A] represents a computation that:

  • Requires an environment of type R (Reader aspect)
  • Performs side effects (IO aspect)
  • Produces a value of type A

This monad is particularly useful for dependency injection patterns and logging scenarios, where you need to:

  • Access configuration or context throughout your application
  • Perform side effects like logging, file I/O, or network calls
  • Maintain functional composition and testability

# Logging Use Case ReaderIO is especially well-suited for logging because it allows you to:

  • Pass a logger through your computation chain without explicit parameter threading
  • Compose logging operations with other side effects
  • Test logging behavior by providing mock loggers in the environment

Key functions for logging scenarios:

  • Ask: Retrieve the entire environment (e.g., a logger instance)
  • Asks: Extract a specific value from the environment (e.g., logger.Info method)
  • ChainIOK: Chain logging operations that return IO effects
  • MonadChain: Sequence multiple logging and computation steps

Example logging usage:

type Env struct {
    Logger *log.Logger
}

// Log a message using the environment's logger
logInfo := func(msg string) readerio.ReaderIO[Env, func()] {
    return readerio.Asks(func(env Env) io.IO[func()] {
        return io.Of(func() { env.Logger.Println(msg) })
    })
}

// Compose logging with computation
computation := F.Pipe3(
    readerio.Of[Env](42),
    readerio.Chain(func(n int) readerio.ReaderIO[Env, int] {
        return F.Pipe1(
            logInfo(fmt.Sprintf("Processing: %d", n)),
            readerio.Map[Env](func(func()) int { return n * 2 }),
        )
    }),
    readerio.ChainIOK(func(result int) io.IO[int] {
        return io.Of(result)
    }),
)

// Execute with environment
env := Env{Logger: log.New(os.Stdout, "APP: ", log.LstdFlags)}
result := computation(env)() // Logs "Processing: 42" and returns 84

Core Operations

The package provides standard monadic operations:

  • Of: Lift a pure value into ReaderIO
  • Map: Transform the result value
  • Chain: Sequence dependent computations
  • Ap: Apply a function in ReaderIO context

Integration

Convert between different contexts:

  • FromIO: Lift an IO action into ReaderIO
  • FromReader: Lift a Reader into ReaderIO
  • ChainIOK: Chain with IO-returning functions

Performance

  • Memoize: Cache computation results (use with caution for context-dependent values)
  • Defer: Ensure fresh computation on each execution

Copyright (c) 2025 IBM Corp. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApFirst

func ApFirst[A, R, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, A]

ApFirst combines two effectful actions, keeping only the result of the first.

func ApS

func ApS[R, S1, S2, T any](
	setter func(T) func(S1) S2,
	fa ReaderIO[R, T],
) func(ReaderIO[R, S1]) ReaderIO[R, S2]

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

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

Example:

type State struct {
    Host string
    Port int
}
type Config struct {
    DefaultHost string
    DefaultPort int
}

// These operations are independent and can be combined with ApS
getHost := readerio.Asks(func(c Config) io.IO[string] {
    return io.Of(c.DefaultHost)
})
getPort := readerio.Asks(func(c Config) io.IO[int] {
    return io.Of(c.DefaultPort)
})

result := F.Pipe2(
    readerio.Do[Config](State{}),
    readerio.ApS(
        func(host string) func(State) State {
            return func(s State) State { s.Host = host; return s }
        },
        getHost,
    ),
    readerio.ApS(
        func(port int) func(State) State {
            return func(s State) State { s.Port = port; return s }
        },
        getPort,
    ),
)

func ApSL

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

ApSL attaches a value to a context using a lens-based setter. This is a convenience function that combines ApS with a lens, allowing you to use optics to update nested structures in a more composable way.

The lens parameter provides both the getter and setter for a field within the structure S. This eliminates the need to manually write setter functions.

Example:

type State struct {
    Host string
    Port int
}
type Config struct {
    DefaultHost string
    DefaultPort int
}

portLens := lens.MakeLens(
    func(s State) int { return s.Port },
    func(s State, p int) State { s.Port = p; return s },
)

getPort := readerio.Asks(func(c Config) io.IO[int] {
    return io.Of(c.DefaultPort)
})
result := F.Pipe2(
    readerio.Of[Config](State{Host: "localhost"}),
    readerio.ApSL(portLens, getPort),
)

func ApSecond

func ApSecond[A, R, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, B]

ApSecond combines two effectful actions, keeping only the result of the second.

func Bind

func Bind[R, S1, S2, T any](
	setter func(T) func(S1) S2,
	f func(S1) ReaderIO[R, T],
) func(ReaderIO[R, S1]) ReaderIO[R, S2]

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

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

Example:

type State struct {
    Host string
    Port int
}
type Config struct {
    DefaultHost string
    DefaultPort int
}

result := F.Pipe2(
    readerio.Do[Config](State{}),
    readerio.Bind(
        func(host string) func(State) State {
            return func(s State) State { s.Host = host; return s }
        },
        func(s State) readerio.ReaderIO[Config, string] {
            return readerio.Asks(func(c Config) io.IO[string] {
                return io.Of(c.DefaultHost)
            })
        },
    ),
    readerio.Bind(
        func(port int) func(State) State {
            return func(s State) State { s.Port = port; return s }
        },
        func(s State) readerio.ReaderIO[Config, int] {
            // This can access s.Host from the previous step
            return readerio.Asks(func(c Config) io.IO[int] {
                return io.Of(c.DefaultPort)
            })
        },
    ),
)

func BindL

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

BindL is a variant of Bind that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.

The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a ReaderIO computation that produces an updated value.

Example:

type State struct {
    Host string
    Port int
}
type Config struct {
    DefaultHost string
    DefaultPort int
}

portLens := lens.MakeLens(
    func(s State) int { return s.Port },
    func(s State, p int) State { s.Port = p; return s },
)

result := F.Pipe2(
    readerio.Do[Config](State{Host: "localhost"}),
    readerio.BindL(portLens, func(port int) readerio.ReaderIO[Config, int] {
        return readerio.Asks(func(c Config) io.IO[int] {
            return io.Of(c.DefaultPort)
        })
    }),
)

func BindTo

func BindTo[R, S1, T any](
	setter func(T) S1,
) func(ReaderIO[R, T]) ReaderIO[R, S1]

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

func Eq

func Eq[R, A any](e EQ.Eq[A]) func(r R) EQ.Eq[ReaderIO[R, A]]

Eq implements the equals predicate for values contained in the IO monad

func From0

func From0[F ~func(R) IO[A], R, A any](f func(R) IO[A]) func() ReaderIO[R, A]

func From1

func From1[F ~func(R, T1) IO[A], R, T1, A any](f func(R, T1) IO[A]) func(T1) ReaderIO[R, A]

func From2

func From2[F ~func(R, T1, T2) IO[A], R, T1, T2, A any](f func(R, T1, T2) IO[A]) func(T1, T2) ReaderIO[R, A]

func From3

func From3[F ~func(R, T1, T2, T3) IO[A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) IO[A]) func(T1, T2, T3) ReaderIO[R, A]

func Let

func Let[R, S1, S2, T any](
	setter func(T) func(S1) S2,
	f func(S1) T,
) func(ReaderIO[R, S1]) ReaderIO[R, S2]

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

func LetL

func LetL[R, S, T any](
	lens L.Lens[S, T],
	f func(T) T,
) func(ReaderIO[R, S]) ReaderIO[R, S]

LetL is a variant of Let that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.

The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a new value (without wrapping in a ReaderIO).

Example:

type State struct {
    Host string
    Port int
}

portLens := lens.MakeLens(
    func(s State) int { return s.Port },
    func(s State, p int) State { s.Port = p; return s },
)

result := F.Pipe2(
    readerio.Do[any](State{Host: "localhost", Port: 8080}),
    readerio.LetL(portLens, func(port int) int {
        return port + 1
    }),
)

func LetTo

func LetTo[R, S1, S2, T any](
	setter func(T) func(S1) S2,
	b T,
) func(ReaderIO[R, S1]) ReaderIO[R, S2]

LetTo attaches the a value to a context [S1] to produce a context [S2]

func LetToL

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

LetToL is a variant of LetTo that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.

The lens parameter provides both a getter and setter for a field of type T within the context S. The value b is set directly to the focused field.

Example:

type State struct {
    Host string
    Port int
}

portLens := lens.MakeLens(
    func(s State) int { return s.Port },
    func(s State, p int) State { s.Port = p; return s },
)

result := F.Pipe2(
    readerio.Do[any](State{Host: "localhost"}),
    readerio.LetToL(portLens, 8080),
)

func Read

func Read[A, R any](r R) func(ReaderIO[R, A]) IO[A]

Read executes a ReaderIO with a given environment, returning the resulting IO. This is useful for providing the environment dependency and obtaining an IO action that can be executed later.

Type Parameters:

  • A: Result type
  • R: Reader environment type

Parameters:

  • r: The environment to provide to the ReaderIO

Returns:

  • A function that converts a ReaderIO into an IO by applying the environment

Example:

rio := readerio.Of[Config](42)
config := Config{Value: 10, Name: "test"}
ioAction := readerio.Read[int](config)(rio)
result := ioAction() // Returns 42

func Traverse

func Traverse[R2, R1, A, B any](
	f Kleisli[R1, A, B],
) func(ReaderIO[R2, A]) Kleisli[R2, R1, B]

func TraverseArray

func TraverseArray[R, A, B any](f func(A) ReaderIO[R, B]) func([]A) ReaderIO[R, []B]

TraverseArray transforms each element of an array using a function that returns a ReaderIO, then collects the results into a single ReaderIO containing an array.

All transformations are executed sequentially.

Type parameters:

  • R: The context type
  • A: The input element type
  • B: The output element type

Parameters:

  • f: A function that transforms each element into a ReaderIO

Returns:

A function that takes an array and returns a ReaderIO of an array

Example:

fetchUsers := TraverseArray(func(id int) ReaderIO[Config, User] {
    return fetchUser(id)
})
result := fetchUsers([]int{1, 2, 3})
// result(cfg)() returns [user1, user2, user3]

func TraverseArrayWithIndex

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

TraverseArrayWithIndex is like TraverseArray but the transformation function also receives the index.

This is useful when the transformation depends on the element's position in the array.

Type parameters:

  • R: The context type
  • A: The input element type
  • B: The output element type

Parameters:

  • f: A function that transforms each element and its index into a ReaderIO

Returns:

A function that takes an array and returns a ReaderIO of an array

Example:

processWithIndex := TraverseArrayWithIndex(func(i int, val string) ReaderIO[Config, string] {
    return Of[Config](fmt.Sprintf("%d: %s", i, val))
})

func TraverseReader

func TraverseReader[R2, R1, A, B any](
	f reader.Kleisli[R1, A, B],
) func(ReaderIO[R2, A]) Kleisli[R2, R1, B]

Types

type Consumer

type Consumer[A any] = consumer.Consumer[A]

type Either

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

type IO

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

IO represents a lazy computation that performs side effects and produces a value of type A. It's an alias for io.IO[A] and encapsulates effectful operations.

type Kleisli

type Kleisli[R, A, B any] = Reader[A, ReaderIO[R, B]]

Kleisli represents a Kleisli arrow for the ReaderIO monad. It's a function from A to ReaderIO[R, B], which allows composition of monadic functions. This is the fundamental building block for chaining operations in the ReaderIO context.

func LogGo

func LogGo[R, A any](prefix string) Kleisli[R, A, A]

LogGo constructs a logger function using Go template syntax for formatting. The prefix string is parsed as a Go template and executed with a context struct containing both the reader context (R) and the value (A) as fields .R and .A. Both successful output and template errors are logged using log.Println.

Type Parameters:

  • R: Reader context type
  • A: Value type

Parameters:

  • prefix: Go template string with access to .R (context) and .A (value)

Returns:

  • A Kleisli arrow that logs the formatted output and returns the original value

Example:

type Config struct {
    AppName string
}
type User struct {
    Name string
    Age  int
}
result := pipe.Pipe2(
    fetchUser(),
    readerio.ChainFirst(readerio.LogGo[Config, User]("[{{.R.AppName}}] User: {{.A.Name}}, Age: {{.A.Age}}")),
    processUser,
)(Config{AppName: "MyApp"})()

func Logf

func Logf[R, A any](prefix string) Kleisli[R, A, A]

Logf constructs a logger function that can be used with ChainFirst or similar operations. The prefix string contains the format string for both the reader context (R) and the value (A). It uses log.Printf to output the formatted message.

Type Parameters:

  • R: Reader context type
  • A: Value type

Parameters:

  • prefix: Format string that accepts two arguments: the reader context and the value

Returns:

  • A Kleisli arrow that logs the context and value, then returns the original value

Example:

type Config struct {
    AppName string
}
result := pipe.Pipe2(
    fetchUser(),
    readerio.ChainFirst(readerio.Logf[Config, User]("[%v] User: %+v")),
    processUser,
)(Config{AppName: "MyApp"})()

func PrintGo

func PrintGo[R, A any](prefix string) Kleisli[R, A, A]

PrintGo constructs a printer function using Go template syntax for formatting. The prefix string is parsed as a Go template and executed with a context struct containing both the reader context (R) and the value (A) as fields .R and .A. Successful output is printed to stdout using fmt.Println, while template errors are printed to stderr using fmt.Fprintln.

Type Parameters:

  • R: Reader context type
  • A: Value type

Parameters:

  • prefix: Go template string with access to .R (context) and .A (value)

Returns:

  • A Kleisli arrow that prints the formatted output and returns the original value

Example:

type Config struct {
    Verbose bool
}
type Data struct {
    ID    int
    Value string
}
result := pipe.Pipe2(
    fetchData(),
    readerio.ChainFirst(readerio.PrintGo[Config, Data]("{{if .R.Verbose}}[VERBOSE] {{end}}Data: {{.A.ID}} - {{.A.Value}}")),
    processData,
)(Config{Verbose: true})()

func Printf

func Printf[R, A any](prefix string) Kleisli[R, A, A]

Printf constructs a printer function that can be used with ChainFirst or similar operations. The prefix string contains the format string for both the reader context (R) and the value (A). Unlike Logf, this prints to stdout without log prefixes.

Type Parameters:

  • R: Reader context type
  • A: Value type

Parameters:

  • prefix: Format string that accepts two arguments: the reader context and the value

Returns:

  • A Kleisli arrow that prints the context and value, then returns the original value

Example:

type Config struct {
    Debug bool
}
result := pipe.Pipe2(
    fetchData(),
    readerio.ChainFirst(readerio.Printf[Config, Data]("[%v] Data: %+v\n")),
    processData,
)(Config{Debug: true})()

func Sequence

func Sequence[R1, R2, A any](ma ReaderIO[R2, ReaderIO[R1, A]]) Kleisli[R2, R1, A]

Sequence swaps the order of nested environment parameters in a ReaderIO computation.

This function takes a ReaderIO that produces another ReaderIO and returns a reader.Kleisli that reverses the order of the environment parameters. The result is a curried function that takes R2 first, then R1, and produces an IO[A].

Type Parameters:

  • R1: The first environment type (becomes inner after flip)
  • R2: The second environment type (becomes outer after flip)
  • A: The result type

Parameters:

  • ma: A ReaderIO that takes R2 and produces a ReaderIO[R1, A]

Returns:

  • A reader.Kleisli[R2, R1, IO[A]], which is func(R2) func(R1) IO[A]

The function preserves IO effects at both levels. The transformation can be visualized as:

Before: R2 -> IO[R1 -> IO[A]]
After:  R2 -> (R1 -> IO[A])

Example:

type Database struct {
    ConnectionString string
}
type Config struct {
    Timeout int
}

// Original: takes Config with IO, produces ReaderIO[Database, string]
original := func(cfg Config) io.IO[ReaderIO[Database, string]] {
    return io.Of(func(db Database) io.IO[string] {
        return io.Of(fmt.Sprintf("Query on %s with timeout %d",
            db.ConnectionString, cfg.Timeout))
    })
}

// Sequenced: takes Database first, then Config
sequenced := Sequence(original)

db := Database{ConnectionString: "localhost:5432"}
cfg := Config{Timeout: 30}

// Apply database first to get a function that takes config
configReader := sequenced(db)
// Then apply config to get the final IO result
result := configReader(cfg)
// result is IO[string]

func SequenceReader

func SequenceReader[R1, R2, A any](ma ReaderIO[R2, Reader[R1, A]]) Kleisli[R2, R1, A]

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

This function is similar to Sequence but specialized for the case where the innermost computation is a pure Reader (without IO effects) rather than another ReaderIO. It takes a ReaderIO that produces a Reader and returns a reader.Kleisli that produces IO effects.

This function is useful when you have a computation that depends on two environments where the outer environment is wrapped in IO (ReaderIO) and the inner is pure (Reader), and you need to change the order in which they are applied. The result moves the IO effect to the inner level.

Type Parameters:

  • R1: The first environment type (from inner Reader, becomes outer after flip)
  • R2: The second environment type (from outer ReaderIO, becomes inner after flip)
  • A: The result type

Parameters:

  • ma: A ReaderIO[R2, Reader[R1, A]] - a computation that takes R2 with IO effects and produces a pure Reader[R1, A]

Returns:

  • A reader.Kleisli[R2, R1, IO[A]], which is func(R2) func(R1) IO[A]

The transformation can be visualized as:

Before: R2 -> IO[R1 -> A]
After:  R2 -> (R1 -> IO[A])

Note the key difference from Sequence:

  • Sequence: Both levels have IO effects (ReaderIO[R2, ReaderIO[R1, A]] -> ReaderIO[R1, ReaderIO[R2, A]])
  • SequenceReader: IO moves from outer to inner (ReaderIO[R2, Reader[R1, A]] -> Reader[R1, ReaderIO[R2, A]])

Example:

type Database struct { ConnectionString string }
type Config struct { Timeout int }

// Original: takes Database with IO, returns pure Reader[Config, string]
query := func(db Database) io.IO[reader.Reader[Config, string]] {
    return io.Of(func(cfg Config) string {
        return fmt.Sprintf("Query on %s with timeout %d", db.ConnectionString, cfg.Timeout)
    })
}

// Sequenced: takes Config first, then Database
sequenced := readerio.SequenceReader(query)

db := Database{ConnectionString: "localhost:5432"}
cfg := Config{Timeout: 30}

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

Use cases:

  • Reordering dependencies when you want to defer IO effects
  • Adapting functions where the IO effect should be associated with a different parameter
  • Building pipelines that need pure outer layers with effectful inner computations
  • Optimizing by controlling which environment access triggers IO effects

func TailRec

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

TailRec implements stack-safe tail recursion for the ReaderIO monad.

This function enables recursive computations that depend on an environment (Reader aspect) and perform side effects (IO aspect) without risking stack overflow. It uses an iterative loop to execute the recursion, making it safe for deep or unbounded recursion.

How It Works

TailRec takes a Kleisli arrow that returns Trampoline[A, B]:

  • Bounce(A): Continue recursion with the new state A
  • Land(B): Terminate recursion and return the final result B

The function iteratively applies the Kleisli arrow, passing the environment R to each iteration, until a Land(B) value is produced. This combines:

  • Environment dependency (Reader monad): Access to configuration, context, or dependencies
  • Side effects (IO monad): Logging, file I/O, network calls, etc.
  • Stack safety: Iterative execution prevents stack overflow

Type Parameters

  • R: The environment type (Reader context) - e.g., Config, Logger, Database connection
  • A: The state type that changes during recursion
  • B: The final result type when recursion terminates

Parameters

  • f: A Kleisli arrow (A => ReaderIO[R, Either[A, B]]) that:
  • Takes the current state A
  • Returns a ReaderIO that depends on environment R
  • Produces Either[A, B] to control recursion flow

Returns

A Kleisli arrow (A => ReaderIO[R, B]) that:

  • Takes an initial state A
  • Returns a ReaderIO that requires environment R
  • Produces the final result B after recursion completes

Comparison with Other Monads

Unlike IOEither and IOOption tail recursion:

  • No error channel (like IOEither's Left error case)
  • No failure case (like IOOption's None case)
  • Adds environment dependency that's available throughout recursion
  • Environment R is passed to every recursive step

Use Cases

  1. Environment-dependent recursive algorithms: - Recursive computations that need configuration at each step - Algorithms that log progress using an environment-provided logger - Recursive operations that access shared resources from environment

  2. Stateful computations with context: - Tree traversals that need environment context - Graph algorithms with configuration-dependent behavior - Recursive parsers with environment-based rules

  3. Recursive operations with side effects: - File system traversals with logging - Network operations with retry configuration - Database operations with connection pooling

Example: Factorial with Logging

type Env struct {
    Logger func(string)
}

// Factorial that logs each step
factorialStep := func(state struct{ n, acc int }) readerio.ReaderIO[Env, either.Either[struct{ n, acc int }, int]] {
    return func(env Env) io.IO[either.Either[struct{ n, acc int }, int]] {
        return func() either.Either[struct{ n, acc int }, int] {
            if state.n <= 0 {
                env.Logger(fmt.Sprintf("Factorial complete: %d", state.acc))
                return either.Right[struct{ n, acc int }](state.acc)
            }
            env.Logger(fmt.Sprintf("Computing: %d * %d", state.n, state.acc))
            return either.Left[int](struct{ n, acc int }{state.n - 1, state.acc * state.n})
        }
    }
}

factorial := readerio.TailRec(factorialStep)
env := Env{Logger: func(msg string) { fmt.Println(msg) }}
result := factorial(struct{ n, acc int }{5, 1})(env)() // Returns 120, logs each step

Example: Countdown with Configuration

type Config struct {
    MinValue int
    Step     int
}

countdownStep := func(n int) readerio.ReaderIO[Config, either.Either[int, int]] {
    return func(cfg Config) io.IO[either.Either[int, int]] {
        return func() either.Either[int, int] {
            if n <= cfg.MinValue {
                return either.Right[int](n)
            }
            return either.Left[int](n - cfg.Step)
        }
    }
}

countdown := readerio.TailRec(countdownStep)
config := Config{MinValue: 0, Step: 2}
result := countdown(10)(config)() // Returns 0 (10 -> 8 -> 6 -> 4 -> 2 -> 0)

Stack Safety

The iterative implementation ensures that even deeply recursive computations (thousands or millions of iterations) will not cause stack overflow:

// Safe for very large inputs
sumToZero := readerio.TailRec(func(n int) readerio.ReaderIO[Env, tailrec.Trampoline[int, int]] {
    return func(env Env) io.IO[tailrec.Trampoline[int, int]] {
        return func() tailrec.Trampoline[int, int] {
            if n <= 0 {
                return tailrec.Land[int](0)
            }
            return tailrec.Bounce[int](n - 1)
        }
    }
})
result := sumToZero(1000000)(env)() // Safe, no stack overflow

Performance Considerations

  • Each iteration creates a new IO action by calling f(a)(r)()
  • The environment R is passed to every iteration
  • For performance-critical code, consider if the environment access is necessary
  • Memoization of environment-derived values may improve performance

See Also

  • [ioeither.TailRec]: Tail recursion with error handling
  • [iooption.TailRec]: Tail recursion with optional results
  • Chain: For sequencing ReaderIO computations
  • Ask: For accessing the environment
  • Asks: For extracting values from the environment

type Operator

type Operator[R, A, B any] = Kleisli[R, ReaderIO[R, A], B]

Operator is a specialized Kleisli arrow that operates on ReaderIO values. It transforms a ReaderIO[R, A] into a ReaderIO[R, B], making it useful for building pipelines of ReaderIO operations. This is commonly used for middleware-style transformations and operation composition.

func After

func After[R, A any](timestamp time.Time) Operator[R, A, A]

After creates an operation that passes after the given time.Time

func Ap

func Ap[B, R, A any](fa ReaderIO[R, A]) Operator[R, func(A) B, B]

Ap creates a function that applies a ReaderIO value to a ReaderIO function. This is the curried version suitable for use in pipelines.

Type Parameters:

  • B: Result type
  • R: Reader environment type
  • A: Input value type

Parameters:

  • fa: ReaderIO containing a value of type A

Returns:

  • An Operator that applies the value to a function

Example:

result := F.Pipe1(
    readerio.Of[Config](N.Mul(2)),
    readerio.Ap[int](readerio.Of[Config](5)),
)(config)() // Returns 10

func Chain

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

Chain creates a function that sequences ReaderIO computations. This is the curried version suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • f: Function that takes a value and returns a ReaderIO

Returns:

  • An Operator that chains ReaderIO computations

Example:

result := F.Pipe1(
    readerio.Of[Config](5),
    readerio.Chain(func(n int) readerio.ReaderIO[Config, int] {
        return readerio.Of[Config](n * 2)
    }),
)(config)() // Returns 10

func ChainConsumer

func ChainConsumer[R, A any](c Consumer[A]) Operator[R, A, struct{}]

func ChainFirst

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

ChainFirst creates a function that sequences ReaderIO computations but returns the first result. This is the curried version of MonadChainFirst, suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Intermediate value type (discarded)

Parameters:

  • f: Function that produces a side-effect ReaderIO

Returns:

  • An Operator that sequences computations while preserving the original value

Example:

result := F.Pipe1(
    readerio.Of[Config](42),
    readerio.ChainFirst(func(n int) readerio.ReaderIO[Config, string] {
        return readerio.Of[Config](fmt.Sprintf("Logged: %d", n))
    }),
)(config)() // Returns 42

func ChainFirstConsumer

func ChainFirstConsumer[R, A any](c Consumer[A]) Operator[R, A, A]

func ChainFirstIOK

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

ChainFirstIOK creates a function that chains a ReaderIO with an IO operation but keeps the original value. This is the curried version of MonadChainFirstIOK, suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: IO result type (discarded)

Parameters:

  • f: Function that takes a value and returns an IO

Returns:

  • An Operator that chains with IO while preserving the original value

Example:

result := F.Pipe1(
    readerio.Of[Config](42),
    readerio.ChainFirstIOK(func(n int) io.IO[string] {
        return io.Of(fmt.Sprintf("Logged: %d", n))
    }),
)(config)() // Returns 42

func ChainFirstReaderK

func ChainFirstReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A]

ChainFirstReaderK creates a function that chains a Reader but keeps the original value. This is the curried version of MonadChainFirstReaderK, suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Reader result type (discarded)

Parameters:

  • f: Function that produces a Reader

Returns:

  • An Operator that chains Reader-returning functions while preserving the original value

Example:

result := F.Pipe1(
    readerio.Of[Config](42),
    readerio.ChainFirstReaderK(func(n int) reader.Reader[Config, string] {
        return func(c Config) string { return fmt.Sprintf("Logged: %d", n) }
    }),
)(config)() // Returns 42

func ChainIOK

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

ChainIOK creates a function that chains a ReaderIO with an IO operation. This is the curried version suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • f: Function that takes a value and returns an IO

Returns:

  • An Operator that chains ReaderIO with IO

Example:

result := F.Pipe1(
    readerio.Of[Config](5),
    readerio.ChainIOK(func(n int) io.IO[int] {
        return io.Of(n * 2)
    }),
)(config)() // Returns 10

func ChainReaderK

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

ChainReaderK creates a function that chains a ReaderIO with a Reader-returning function. This is the curried version of MonadChainReaderK, suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • f: Function that produces a Reader

Returns:

  • An Operator that chains Reader-returning functions

Example:

result := F.Pipe1(
    readerio.Of[Config](5),
    readerio.ChainReaderK(func(n int) reader.Reader[Config, int] {
        return func(c Config) int { return n + c.Value }
    }),
)(config)()

func Delay

func Delay[R, A any](delay time.Duration) Operator[R, A, A]

Delay creates an operation that passes in the value after some delay

func Flap

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

Flap creates a function that applies a value to a ReaderIO function. This is the curried version of MonadFlap, suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Result type

Parameters:

  • a: The value to apply

Returns:

  • An Operator that applies the value to a ReaderIO function

Example:

result := F.Pipe1(
    readerio.Of[Config](N.Mul(2)),
    readerio.Flap[Config](5),
)(config)() // Returns 10

func Map

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

Map creates a function that applies a transformation to a ReaderIO value. This is the curried version suitable for use in pipelines.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • f: The transformation function

Returns:

  • An Operator that transforms ReaderIO[R, A] to ReaderIO[R, B]

Example:

result := F.Pipe1(
    readerio.Of[Config](5),
    readerio.Map[Config](N.Mul(2)),
)(config)() // Returns 10

func MapTo

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

MapTo creates an operator that executes a ReaderIO computation, discards its result, and returns a constant value. This is the curried version of MonadMapTo, suitable for use in pipelines.

IMPORTANT: ReaderIO represents a side-effectful computation (IO effects). For this reason, MapTo WILL execute the input ReaderIO to allow any side effects to occur (such as logging, file I/O, network calls, etc.), then discard the result and return the constant value. The side effects are preserved even though the result value is discarded.

Type Parameters:

  • R: Reader environment type
  • A: Input value type (result will be discarded after execution)
  • B: Output value type (constant to return)

Parameters:

  • b: The constant value to return after executing the ReaderIO

Returns:

  • An Operator that executes a ReaderIO for its side effects, then returns b

Example:

logStep := func(r Config) io.IO[int] {
    return io.Of(func() int {
        fmt.Println("Step executed") // Side effect
        return 42
    })
}
result := F.Pipe1(
    logStep,
    readerio.MapTo[Config, int]("complete"),
)(config)() // Prints "Step executed", returns "complete"

func Tap

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

Tap creates a function that executes a side-effect computation but returns the original value. This is the curried version of MonadTap, an alias for ChainFirst.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Side effect value type (discarded)

Parameters:

  • f: Function that produces a side-effect ReaderIO

Returns:

  • An Operator that taps ReaderIO computations

Example:

result := F.Pipe1(
    readerio.Of[Config](42),
    readerio.Tap(func(n int) readerio.ReaderIO[Config, func()] {
        return readerio.FromIO[Config](io.Of(func() { fmt.Println(n) }))
    }),
)(config)() // Returns 42, prints 42

func TapIOK

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

TapIOK creates a function that chains a ReaderIO with an IO operation but keeps the original value. This is the curried version of MonadTapIOK, an alias for ChainFirstIOK.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: IO result type (discarded)

Parameters:

  • f: Function that takes a value and returns an IO for side effects

Returns:

  • An Operator that taps with IO-returning functions

Example:

result := F.Pipe1(
    readerio.Of[Config](42),
    readerio.TapIOK(func(n int) io.IO[func()] {
        return io.Of(func() { fmt.Println(n) })
    }),
)(config)() // Returns 42, prints 42

func TapReaderK

func TapReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A]

TapReaderK creates a function that chains a Reader but keeps the original value. This is the curried version of MonadTapReaderK, an alias for ChainFirstReaderK.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Reader result type (discarded)

Parameters:

  • f: Function that produces a Reader for side effects

Returns:

  • An Operator that taps with Reader-returning functions

Example:

result := F.Pipe1(
    readerio.Of[Config](42),
    readerio.TapReaderK(func(n int) reader.Reader[Config, func()] {
        return func(c Config) func() { return func() { fmt.Println(n) } }
    }),
)(config)() // Returns 42, prints 42

func WithLock

func WithLock[R, A any](lock func() context.CancelFunc) Operator[R, A, A]

WithLock executes the provided IO operation in the scope of a lock

type Reader

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

Reader represents a computation that depends on an environment of type R and produces a value of type A. It's an alias for reader.Reader[R, A] and is used for dependency injection patterns.

type ReaderIO

type ReaderIO[R, A any] = Reader[R, IO[A]]

ReaderIO combines Reader and IO monads. It represents a computation that: 1. Depends on an environment of type R (Reader aspect) 2. Performs side effects and produces a value of type A (IO aspect) This is useful for operations that need both dependency injection and effect management.

func Ask

func Ask[R any]() ReaderIO[R, R]

Ask retrieves the current environment. This is the fundamental operation for accessing the Reader context.

Type Parameters:

  • R: Reader environment type

Returns:

  • A ReaderIO that returns the environment

Example:

type Config struct { Port int }
rio := readerio.Ask[Config]()
config := Config{Port: 8080}
result := rio(config)() // Returns Config{Port: 8080}

func Asks

func Asks[R, A any](r Reader[R, A]) ReaderIO[R, A]

Asks retrieves a value derived from the environment using a Reader function. This allows you to extract specific information from the environment.

Type Parameters:

  • R: Reader environment type
  • A: Result type

Parameters:

  • r: Function that extracts a value from the environment

Returns:

  • A ReaderIO that applies the function to the environment

Example:

type Config struct { Port int }
rio := readerio.Asks(func(c Config) io.IO[int] {
    return io.Of(c.Port)
})
result := rio(Config{Port: 8080})() // Returns 8080

func Bracket

func Bracket[
	R, A, B, ANY any](

	acquire ReaderIO[R, A],
	use Kleisli[R, A, B],
	release func(A, B) ReaderIO[R, ANY],
) ReaderIO[R, B]

func Defer

func Defer[R, A any](gen func() ReaderIO[R, A]) ReaderIO[R, A]

Defer creates a ReaderIO by calling a generator function each time it's executed. This allows for lazy evaluation and ensures a fresh computation on each invocation. Useful for operations that should not be cached or memoized.

Type Parameters:

  • R: Reader environment type
  • A: Result type

Parameters:

  • gen: Generator function that creates a new ReaderIO on each call

Returns:

  • A ReaderIO that calls the generator function on each execution

Example:

counter := 0
rio := readerio.Defer(func() readerio.ReaderIO[Config, int] {
    counter++
    return readerio.Of[Config](counter)
})
result1 := rio(config)() // Returns 1
result2 := rio(config)() // Returns 2 (fresh computation)

func Do

func Do[R, S any](
	empty S,
) ReaderIO[R, S]

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

Example:

type State struct {
    Host string
    Port int
}
type Config struct {
    DefaultHost string
    DefaultPort int
}
result := readerio.Do[Config](State{})

func Flatten

func Flatten[R, A any](mma ReaderIO[R, ReaderIO[R, A]]) ReaderIO[R, A]

Flatten removes one level of nesting from a ReaderIO structure. Converts ReaderIO[R, ReaderIO[R, A]] to ReaderIO[R, A]. This is also known as "join" in monad terminology.

Type Parameters:

  • R: Reader environment type
  • A: Result type

Parameters:

  • mma: A nested ReaderIO structure

Returns:

  • A flattened ReaderIO with one less level of nesting

Example:

nested := readerio.Of[Config](readerio.Of[Config](42))
flattened := readerio.Flatten(nested)
result := flattened(config)() // Returns 42

func FromIO

func FromIO[R, A any](t IO[A]) ReaderIO[R, A]

FromIO converts an IO action to a ReaderIO that ignores the environment. This lifts a pure IO computation into the ReaderIO context.

Type Parameters:

  • R: Reader environment type
  • A: Result type

Parameters:

  • t: The IO action to lift

Returns:

  • A ReaderIO that executes the IO action regardless of the environment

Example:

ioAction := io.Of(42)
readerIO := readerio.FromIO[Config](ioAction)
result := readerIO(config)() // Returns 42

func FromReader

func FromReader[R, A any](r Reader[R, A]) ReaderIO[R, A]

FromReader converts a Reader to a ReaderIO by lifting the pure computation into IO. This allows you to use Reader computations in a ReaderIO context.

Type Parameters:

  • R: Reader environment type
  • A: Result type

Parameters:

  • r: The Reader to convert

Returns:

  • A ReaderIO that wraps the Reader computation in IO

Example:

reader := func(config Config) int { return config.Port }
readerIO := readerio.FromReader(reader)
result := readerIO(config)() // Returns config.Port

func Memoize

func Memoize[R, A any](rdr ReaderIO[R, A]) ReaderIO[R, A]

Memoize computes the value of the provided ReaderIO monad lazily but exactly once. The first execution caches the result, and subsequent executions return the cached value.

IMPORTANT: The context used to compute the value is the context of the first call. Do not use this method if the value has a functional dependency on the content of the context, as subsequent calls with different contexts will still return the memoized result from the first call.

Type Parameters:

  • R: Reader environment type
  • A: Result type

Parameters:

  • rdr: The ReaderIO to memoize

Returns:

  • A ReaderIO that caches its result after the first execution

Example:

expensive := readerio.Of[Config](computeExpensiveValue())
memoized := readerio.Memoize(expensive)
result1 := memoized(config)() // Computes the value
result2 := memoized(config)() // Returns cached value (no recomputation)

func MonadAp

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

MonadAp applies a function wrapped in a ReaderIO to a value wrapped in a ReaderIO. This is the applicative apply operation for ReaderIO.

Type Parameters:

  • B: Result type
  • R: Reader environment type
  • A: Input value type

Parameters:

  • fab: ReaderIO containing a function from A to B
  • fa: ReaderIO containing a value of type A

Returns:

  • A ReaderIO containing the result of applying the function to the value

Example:

fabIO := readerio.Of[Config](N.Mul(2))

faIO := readerio.Of[Config](5)
result := readerio.MonadAp(fabIO, faIO)(config)() // Returns 10

func MonadApFirst

func MonadApFirst[A, R, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, A]

MonadApFirst combines two effectful actions, keeping only the result of the first.

func MonadApPar

func MonadApPar[B, R, A any](fab ReaderIO[R, func(A) B], fa ReaderIO[R, A]) ReaderIO[R, B]

MonadApPar is like MonadAp but allows parallel execution of effects where possible.

Type Parameters:

  • B: Result type
  • R: Reader environment type
  • A: Input value type

Parameters:

  • fab: ReaderIO containing a function from A to B
  • fa: ReaderIO containing a value of type A

Returns:

  • A ReaderIO containing the result, with potential parallel execution

func MonadApSecond

func MonadApSecond[A, R, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, B]

MonadApSecond combines two effectful actions, keeping only the result of the second.

func MonadApSeq

func MonadApSeq[B, R, A any](fab ReaderIO[R, func(A) B], fa ReaderIO[R, A]) ReaderIO[R, B]

MonadApSeq is like MonadAp but ensures sequential execution of effects.

Type Parameters:

  • B: Result type
  • R: Reader environment type
  • A: Input value type

Parameters:

  • fab: ReaderIO containing a function from A to B
  • fa: ReaderIO containing a value of type A

Returns:

  • A ReaderIO containing the result, with sequential execution guaranteed

func MonadChain

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

MonadChain sequences two ReaderIO computations, where the second depends on the result of the first. This is the monadic bind operation for ReaderIO.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • ma: The first ReaderIO computation
  • f: Function that takes the result of ma and returns a new ReaderIO

Returns:

  • A ReaderIO that sequences both computations

Example:

rio1 := readerio.Of[Config](5)
result := readerio.MonadChain(rio1, func(n int) readerio.ReaderIO[Config, int] {
    return readerio.Of[Config](n * 2)
})

func MonadChainFirst

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

MonadChainFirst sequences two ReaderIO computations but returns the result of the first. The second computation is executed for its side effects only (e.g., logging, validation).

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Intermediate value type (discarded)

Parameters:

  • ma: The first ReaderIO computation
  • f: Function that produces the second ReaderIO (for side effects)

Returns:

  • A ReaderIO with the result of the first computation

Example:

rio := readerio.Of[Config](42)
result := readerio.MonadChainFirst(rio, func(n int) readerio.ReaderIO[Config, string] {
    // Log the value but don't change the result
    return readerio.Of[Config](fmt.Sprintf("Logged: %d", n))
})
value := result(config)() // Returns 42, but logging happened

func MonadChainFirstIOK

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

MonadChainFirstIOK chains a ReaderIO with an IO-returning function but keeps the original value. The IO computation is executed for its side effects only.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: IO result type (discarded)

Parameters:

  • ma: The ReaderIO computation
  • f: Function that takes a value and returns an IO

Returns:

  • A ReaderIO with the original value after executing the IO

Example:

rio := readerio.Of[Config](42)
result := readerio.MonadChainFirstIOK(rio, func(n int) io.IO[string] {
    return io.Of(fmt.Sprintf("Logged: %d", n))
})
value := result(config)() // Returns 42

func MonadChainFirstReaderK

func MonadChainFirstReaderK[R, A, B any](ma ReaderIO[R, A], f reader.Kleisli[R, A, B]) ReaderIO[R, A]

MonadChainFirstReaderK chains a function that returns a Reader but keeps the original value. The Reader computation is executed for its side effects only.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Reader result type (discarded)

Parameters:

  • ma: The ReaderIO to chain from
  • f: Function that produces a Reader

Returns:

  • A ReaderIO with the original value after executing the Reader

Example:

rio := readerio.Of[Config](42)
result := readerio.MonadChainFirstReaderK(rio, func(n int) reader.Reader[Config, string] {
    return func(c Config) string { return fmt.Sprintf("Logged: %d", n) }
})
value := result(config)() // Returns 42

func MonadChainIOK

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

MonadChainIOK chains a ReaderIO with a function that returns an IO. This is useful for integrating IO operations into a ReaderIO pipeline.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • ma: The ReaderIO computation
  • f: Function that takes a value and returns an IO

Returns:

  • A ReaderIO that sequences the computation with the IO operation

Example:

rio := readerio.Of[Config](5)
result := readerio.MonadChainIOK(rio, func(n int) io.IO[int] {
    return io.Of(n * 2)
})

func MonadChainReaderK

func MonadChainReaderK[R, A, B any](ma ReaderIO[R, A], f reader.Kleisli[R, A, B]) ReaderIO[R, B]

MonadChainReaderK chains a ReaderIO with a function that returns a Reader. The Reader is lifted into the ReaderIO context, allowing composition of Reader and ReaderIO operations.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • ma: The ReaderIO to chain from
  • f: Function that produces a Reader

Returns:

  • A new ReaderIO with the chained Reader computation

Example:

rio := readerio.Of[Config](5)
result := readerio.MonadChainReaderK(rio, func(n int) reader.Reader[Config, int] {
    return func(c Config) int { return n + c.Value }
})

func MonadFlap

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

MonadFlap applies a value to a function wrapped in a ReaderIO. This is the "flipped" version of MonadAp, where the value comes second.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Result type

Parameters:

  • fab: ReaderIO containing a function from A to B
  • a: The value to apply to the function

Returns:

  • A ReaderIO containing the result of applying the value to the function

Example:

fabIO := readerio.Of[Config](N.Mul(2))
result := readerio.MonadFlap(fabIO, 5)(config)() // Returns 10

func MonadMap

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

MonadMap applies a function to the value inside a ReaderIO context. This is the monadic version that takes the ReaderIO as the first parameter.

Type Parameters:

  • R: Reader environment type
  • A: Input value type
  • B: Output value type

Parameters:

  • fa: The ReaderIO containing the value to transform
  • f: The transformation function

Returns:

  • A new ReaderIO with the transformed value

Example:

rio := readerio.Of[Config](5)
doubled := readerio.MonadMap(rio, N.Mul(2))
result := doubled(config)() // Returns 10

func MonadMapTo

func MonadMapTo[R, A, B any](fa ReaderIO[R, A], b B) ReaderIO[R, B]

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

IMPORTANT: ReaderIO represents a side-effectful computation (IO effects). For this reason, MonadMapTo WILL execute the original ReaderIO to allow any side effects to occur (such as logging, file I/O, network calls, etc.), then discard the result and return the constant value. The side effects are preserved even though the result value is discarded.

Type Parameters:

  • R: Reader environment type
  • A: Input value type (result will be discarded after execution)
  • B: Output value type (constant to return)

Parameters:

  • fa: The ReaderIO to execute (side effects will occur, result discarded)
  • b: The constant value to return after executing fa

Returns:

  • A new ReaderIO that executes fa for its side effects, then returns b

Example:

logAndCompute := func(r Config) io.IO[int] {
    return io.Of(func() int {
        fmt.Println("Computing...") // Side effect
        return 42
    })
}
replaced := readerio.MonadMapTo(logAndCompute, "done")
result := replaced(config)() // Prints "Computing...", returns "done"

func MonadTap

func MonadTap[R, A, B any](ma ReaderIO[R, A], f Kleisli[R, A, B]) ReaderIO[R, A]

MonadTap executes a side-effect computation but returns the original value. This is an alias for MonadChainFirst and is useful for operations like logging or validation that should not affect the main computation flow.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Side effect value type (discarded)

Parameters:

  • ma: The ReaderIO to tap
  • f: Function that produces a side-effect ReaderIO

Returns:

  • A ReaderIO with the original value after executing the side effect

Example:

result := readerio.MonadTap(
    readerio.Of[Config](42),
    func(n int) readerio.ReaderIO[Config, func()] {
        return readerio.FromIO[Config](io.Of(func() { fmt.Println(n) }))
    },
)

func MonadTapIOK

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

MonadTapIOK chains a ReaderIO with an IO-returning function but keeps the original value. This is an alias for MonadChainFirstIOK and is useful for side effects like logging.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: IO result type (discarded)

Parameters:

  • ma: The ReaderIO to tap
  • f: Function that takes a value and returns an IO for side effects

Returns:

  • A ReaderIO with the original value after executing the IO

Example:

result := readerio.MonadTapIOK(
    readerio.Of[Config](42),
    func(n int) io.IO[func()] {
        return io.Of(func() { fmt.Println(n) })
    },
)

func MonadTapReaderK

func MonadTapReaderK[R, A, B any](ma ReaderIO[R, A], f reader.Kleisli[R, A, B]) ReaderIO[R, A]

MonadTapReaderK chains a function that returns a Reader but keeps the original value. This is an alias for MonadChainFirstReaderK and is useful for side effects.

Type Parameters:

  • R: Reader environment type
  • A: Input and output value type
  • B: Reader result type (discarded)

Parameters:

  • ma: The ReaderIO to tap
  • f: Function that produces a Reader for side effects

Returns:

  • A ReaderIO with the original value after executing the Reader

Example:

result := readerio.MonadTapReaderK(
    readerio.Of[Config](42),
    func(n int) reader.Reader[Config, func()] {
        return func(c Config) func() { return func() { fmt.Println(n) } }
    },
)

func Of

func Of[R, A any](a A) ReaderIO[R, A]

Of creates a ReaderIO that returns a pure value, ignoring the environment. This is the monadic return/pure operation for ReaderIO.

Type Parameters:

  • R: Reader environment type
  • A: Value type

Parameters:

  • a: The value to wrap

Returns:

  • A ReaderIO that always returns the given value

Example:

rio := readerio.Of[Config](42)
result := rio(config)() // Returns 42

func Retrying

func Retrying[R, A any](
	policy retry.RetryPolicy,
	action Kleisli[R, retry.RetryStatus, A],
	check func(A) bool,
) ReaderIO[R, A]

func SequenceArray

func SequenceArray[R, A any](ma []ReaderIO[R, A]) ReaderIO[R, []A]

SequenceArray converts an array of ReaderIO into a ReaderIO of an array.

This is useful when you have multiple independent computations and want to execute them all and collect their results.

Type parameters:

  • R: The context type
  • A: The element type

Parameters:

  • ma: An array of ReaderIO computations

Returns:

A ReaderIO that produces an array of results

Example:

computations := []ReaderIO[Config, int]{
    fetchCount("users"),
    fetchCount("posts"),
    fetchCount("comments"),
}
result := SequenceArray(computations)
// result(cfg)() returns [userCount, postCount, commentCount]

func SequenceT1

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

func SequenceT2

func SequenceT2[R, A, B any](a ReaderIO[R, A], b ReaderIO[R, B]) ReaderIO[R, T.Tuple2[A, B]]

func SequenceT3

func SequenceT3[R, A, B, C any](a ReaderIO[R, A], b ReaderIO[R, B], c ReaderIO[R, C]) ReaderIO[R, T.Tuple3[A, B, C]]

func SequenceT4

func SequenceT4[R, A, B, C, D any](a ReaderIO[R, A], b ReaderIO[R, B], c ReaderIO[R, C], d ReaderIO[R, D]) ReaderIO[R, T.Tuple4[A, B, C, D]]

type Trampoline

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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