Documentation
¶
Overview ¶
Package reader provides the Reader monad implementation for functional programming in Go.
The Reader monad is used to pass a shared environment or configuration through a computation without explicitly threading it through every function call. It represents a computation that depends on some external context of type R and produces a value of type A.
Fantasy Land Specification ¶
This implementation corresponds to the Fantasy Land Reader type: https://github.com/fantasyland/fantasy-land
Implemented Fantasy Land algebras:
- Functor: https://github.com/fantasyland/fantasy-land#functor
- Apply: https://github.com/fantasyland/fantasy-land#apply
- Applicative: https://github.com/fantasyland/fantasy-land#applicative
- Chain: https://github.com/fantasyland/fantasy-land#chain
- Monad: https://github.com/fantasyland/fantasy-land#monad
Core Concept ¶
A Reader[R, A] is simply a function from R to A: func(R) A - R is the environment/context type (read-only) - A is the result type
Key Benefits ¶
- Dependency Injection: Pass configuration or dependencies implicitly
- Composition: Combine readers that share the same environment
- Testability: Easy to test by providing different environments
- Avoid Threading: No need to pass context through every function
Basic Usage ¶
// Define a configuration type
type Config struct {
Host string
Port int
}
// Create readers that depend on Config
getHost := reader.Asks(func(c Config) string { return c.Host })
getPort := reader.Asks(func(c Config) int { return c.Port })
// Compose readers
getURL := reader.Map(func(host string) string {
return "http://" + host
})(getHost)
// Run the reader with a config
config := Config{Host: "localhost", Port: 8080}
url := getURL(config) // "http://localhost"
Common Operations ¶
- Ask: Get the current environment
- Asks: Project a value from the environment
- Map: Transform the result value
- Chain: Sequence computations that depend on previous results
- Local: Modify the environment for a sub-computation
Monadic Operations ¶
The Reader type implements the Functor, Applicative, and Monad type classes:
- Functor: Map over the result value
- Applicative: Combine multiple readers with independent computations
- Monad: Chain readers where later computations depend on earlier results
Related Packages ¶
- reader/generic: Generic implementations for custom reader types
- readerio: Reader combined with IO effects
- readereither: Reader combined with Either for error handling
- readerioeither: Reader combined with IO and Either
Index ¶
- func ApplicativeMonoid[R, A any](_of func(A) func(R) A, ...) M.Monoid[func(R) A]
- func ApplySemigroup[R, A any](_map func(func(R) A, func(A) func(A) A) func(R, func(A) A), ...) S.Semigroup[func(R) A]
- func Curry2[R, T1, T2, A any](f func(R, T1, T2) A) func(T1) func(T2) Reader[R, A]
- func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1) func(T2) func(T3) Reader[R, A]
- func Curry4[R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1) func(T2) func(T3) func(T4) Reader[R, A]
- func From0[F ~func(C) R, C, R any](f F) func() Reader[C, R]
- func From1[F ~func(C, T0) R, T0, C, R any](f F) func(T0) Reader[C, R]
- func From10[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) R, ...](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Reader[C, R]
- func From2[F ~func(C, T0, T1) R, T0, T1, C, R any](f F) func(T0, T1) Reader[C, R]
- func From3[F ~func(C, T0, T1, T2) R, T0, T1, T2, C, R any](f F) func(T0, T1, T2) Reader[C, R]
- func From4[F ~func(C, T0, T1, T2, T3) R, T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) Reader[C, R]
- func From5[F ~func(C, T0, T1, T2, T3, T4) R, T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) Reader[C, R]
- func From6[F ~func(C, T0, T1, T2, T3, T4, T5) R, T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) Reader[C, R]
- func From7[F ~func(C, T0, T1, T2, T3, T4, T5, T6) R, T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) Reader[C, R]
- func From8[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) R, ...](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) Reader[C, R]
- func From9[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) R, ...](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Reader[C, R]
- func Read[A, E any](e E) func(Reader[E, A]) A
- func Traverse[R2, R1, A, B any](f Kleisli[R1, A, B]) func(Reader[R2, A]) Kleisli[R2, R1, B]
- func TraverseRecord[K comparable, R, A, B any](f Kleisli[R, A, B]) func(map[K]A) Reader[R, map[K]B]
- func TraverseRecordWithIndex[K comparable, R, A, B any](f func(K, A) Reader[R, B]) func(map[K]A) Reader[R, map[K]B]
- func Uncurry0[R, A any](f Reader[R, A]) func(R) A
- func Uncurry1[R, T1, A any](f Kleisli[R, T1, A]) func(R, T1) A
- func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) Reader[R, A]) func(R, T1, T2) A
- func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) Reader[R, A]) func(R, T1, T2, T3) A
- func Uncurry4[R, T1, T2, T3, T4, A any](f func(T1) func(T2) func(T3) func(T4) Reader[R, A]) func(R, T1, T2, T3, T4) A
- type Kleisli
- func Compose[C, R, B any](ab Reader[R, B]) Kleisli[R, Reader[B, C], C]
- func Curry1[R, T1, A any](f func(R, T1) A) Kleisli[R, T1, A]
- func Local[A, R2, R1 any](f func(R2) R1) Kleisli[R2, Reader[R1, A], A]
- func Promap[E, A, D, B any](f func(D) E, g func(A) B) Kleisli[D, Reader[E, A], B]
- func ReduceArray[R, A, B any](reduce func(B, A) B, initial B) Kleisli[R, []Reader[R, A], B]
- func ReduceArrayM[R, A any](m monoid.Monoid[A]) Kleisli[R, []Reader[R, A], A]
- func Sequence[R1, R2, A any](ma Reader[R2, Reader[R1, A]]) Kleisli[R2, R1, A]
- func TraverseArray[R, A, B any](f Kleisli[R, A, B]) Kleisli[R, []A, []B]
- func TraverseArrayWithIndex[R, A, B any](f func(int, A) Reader[R, B]) Kleisli[R, []A, []B]
- func TraverseReduceArray[R, A, B, C any](trfrm Kleisli[R, A, B], reduce func(C, B) C, initial C) Kleisli[R, []A, C]
- func TraverseReduceArrayM[R, A, B any](trfrm Kleisli[R, A, B], m monoid.Monoid[B]) Kleisli[R, []A, B]
- type Operator
- func Ap[B, R, A any](fa Reader[R, A]) Operator[R, func(A) B, B]
- func ApS[R, S1, S2, T any](setter func(T) func(S1) S2, fa Reader[R, T]) Operator[R, S1, S2]
- func ApSL[R, S, T any](lens L.Lens[S, T], fa Reader[R, T]) Operator[R, S, S]
- func Bind[R, S1, S2, T any](setter func(T) func(S1) S2, f Kleisli[R, S1, T]) Operator[R, S1, S2]
- func BindL[R, S, T any](lens L.Lens[S, T], f Kleisli[R, T, T]) Operator[R, S, S]
- func BindTo[R, S1, T any](setter func(T) S1) Operator[R, T, S1]
- func Chain[R, A, B any](f Kleisli[R, A, B]) Operator[R, A, B]
- func ChainTo[A, R, B any](b Reader[R, B]) Operator[R, A, B]
- func Flap[R, B, A any](a A) Operator[R, func(A) B, B]
- func Let[R, S1, S2, T any](setter func(T) func(S1) S2, f func(S1) T) Operator[R, S1, S2]
- func LetL[R, S, T any](lens L.Lens[S, T], f func(T) T) Operator[R, S, S]
- func LetTo[R, S1, S2, T any](setter func(T) func(S1) S2, b T) Operator[R, S1, S2]
- func LetToL[R, S, T any](lens L.Lens[S, T], b T) Operator[R, S, S]
- func Map[E, A, B any](f func(A) B) Operator[E, A, B]
- func MapTo[E, A, B any](b B) Operator[E, A, B]
- type Reader
- func Ask[R any]() Reader[R, R]
- func Asks[R, A any](f Reader[R, A]) Reader[R, A]
- func AsksReader[R, A any](f Kleisli[R, R, A]) Reader[R, A]
- func Bracket[R, A, B, ANY any](acquire Reader[R, A], use Kleisli[R, A, B], release func(A, B) Reader[R, ANY]) Reader[R, B]
- func Curry0[R, A any](f func(R) A) Reader[R, A]
- func Do[R, S any](empty S) Reader[R, S]
- func First[A, B, C any](pab Reader[A, B]) Reader[T.Tuple2[A, C], T.Tuple2[B, C]]
- func Flatten[R, A any](mma Reader[R, Reader[R, A]]) Reader[R, A]
- func MonadAp[B, R, A any](fab Reader[R, func(A) B], fa Reader[R, A]) Reader[R, B]
- func MonadChain[R, A, B any](ma Reader[R, A], f Kleisli[R, A, B]) Reader[R, B]
- func MonadChainTo[A, R, B any](_ Reader[R, A], b Reader[R, B]) Reader[R, B]
- func MonadFlap[R, B, A any](fab Reader[R, func(A) B], a A) Reader[R, B]
- func MonadMap[E, A, B any](fa Reader[E, A], f func(A) B) Reader[E, B]
- func MonadMapTo[E, A, B any](_ Reader[E, A], b B) Reader[E, B]
- func MonadReduceArray[R, A, B any](as []Reader[R, A], reduce func(B, A) B, initial B) Reader[R, B]
- func MonadReduceArrayM[R, A any](as []Reader[R, A], m monoid.Monoid[A]) Reader[R, A]
- func MonadTraverseArray[R, A, B any](ma []A, f Kleisli[R, A, B]) Reader[R, []B]
- func MonadTraverseRecord[K comparable, R, A, B any](ma map[K]A, f Kleisli[R, A, B]) Reader[R, map[K]B]
- func MonadTraverseRecordWithIndex[K comparable, R, A, B any](ma map[K]A, f func(K, A) Reader[R, B]) Reader[R, map[K]B]
- func MonadTraverseReduceArray[R, A, B, C any](as []A, trfrm Kleisli[R, A, B], reduce func(C, B) C, initial C) Reader[R, C]
- func MonadTraverseReduceArrayM[R, A, B any](as []A, trfrm Kleisli[R, A, B], m monoid.Monoid[B]) Reader[R, B]
- func Of[R, A any](a A) Reader[R, A]
- func Second[A, B, C any](pbc Reader[B, C]) Reader[T.Tuple2[A, B], T.Tuple2[A, C]]
- func SequenceArray[R, A any](ma []Reader[R, A]) Reader[R, []A]
- func SequenceRecord[K comparable, R, A any](ma map[K]Reader[R, A]) Reader[R, map[K]A]
- func SequenceT1[R, A any](a Reader[R, A]) Reader[R, T.Tuple1[A]]
- func SequenceT2[R, A, B any](a Reader[R, A], b Reader[R, B]) Reader[R, T.Tuple2[A, B]]
- func SequenceT3[R, A, B, C any](a Reader[R, A], b Reader[R, B], c Reader[R, C]) Reader[R, T.Tuple3[A, B, C]]
- func SequenceT4[R, A, B, C, D any](a Reader[R, A], b Reader[R, B], c Reader[R, C], d Reader[R, D]) Reader[R, T.Tuple4[A, B, C, D]]
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplicativeMonoid ¶
func ApplicativeMonoid[R, A any]( _of func(A) func(R) A, _map func(func(R) A, func(A) func(A) A) func(R, func(A) A), _ap func(func(R, func(A) A), func(R) A) func(R) A, m M.Monoid[A], ) M.Monoid[func(R) A]
ApplicativeMonoid lifts a Monoid[A] into a Monoid[Reader[R, A]]. This allows you to combine Readers that produce monoid values, with an empty/identity Reader.
The _of parameter is the Of operation (pure/return) for the Reader type. The _map and _ap parameters are the Map and Ap operations for the Reader type.
Example:
type Config struct { Prefix string }
// Using the string concatenation monoid
stringMonoid := monoid.MakeMonoid("", func(a, b string) string { return a + b })
readerMonoid := reader.ApplicativeMonoid(
reader.Of[Config, string],
reader.MonadMap[Config, string, func(string) string],
reader.MonadAp[string, Config, string],
stringMonoid,
)
r1 := reader.Asks(func(c Config) string { return c.Prefix })
r2 := reader.Of[Config]("hello")
combined := readerMonoid.Concat(r1, r2)
result := combined(Config{Prefix: ">> "}) // ">> hello"
empty := readerMonoid.Empty()(Config{Prefix: "any"}) // ""
func ApplySemigroup ¶
func ApplySemigroup[R, A any]( _map func(func(R) A, func(A) func(A) A) func(R, func(A) A), _ap func(func(R, func(A) A), func(R) A) func(R) A, s S.Semigroup[A], ) S.Semigroup[func(R) A]
ApplySemigroup lifts a Semigroup[A] into a Semigroup[Reader[R, A]]. This allows you to combine two Readers that produce semigroup values by combining their results using the semigroup's concat operation.
The _map and _ap parameters are the Map and Ap operations for the Reader type, typically obtained from the reader package.
Example:
type Config struct { Multiplier int }
// Using the additive semigroup for integers
intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b })
readerSemigroup := reader.ApplySemigroup(
reader.MonadMap[Config, int, func(int) int],
reader.MonadAp[int, Config, int],
intSemigroup,
)
r1 := reader.Of[Config](5)
r2 := reader.Of[Config](3)
combined := readerSemigroup.Concat(r1, r2)
result := combined(Config{Multiplier: 1}) // 8
func Curry2 ¶
Curry2 converts a function with context as first parameter and 2 other parameters into a curried function returning a Reader.
Example:
type Config struct { Sep string }
join := func(c Config, a, b string) string { return a + c.Sep + b }
curried := reader.Curry2(join)
r := curried("hello")("world")
result := r(Config{Sep: "-"}) // "hello-world"
func Curry3 ¶
Curry3 converts a function with context as first parameter and 3 other parameters into a curried function returning a Reader.
Example:
type Config struct { Format string }
format := func(c Config, a, b, d string) string {
return fmt.Sprintf(c.Format, a, b, d)
}
curried := reader.Curry3(format)
r := curried("a")("b")("c")
result := r(Config{Format: "%s-%s-%s"}) // "a-b-c"
func Curry4 ¶
func Curry4[R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1) func(T2) func(T3) func(T4) Reader[R, A]
Curry4 converts a function with context as first parameter and 4 other parameters into a curried function returning a Reader.
Example:
type Config struct { Multiplier int }
sum := func(c Config, a, b, d, e int) int {
return (a + b + d + e) * c.Multiplier
}
curried := reader.Curry4(sum)
r := curried(1)(2)(3)(4)
result := r(Config{Multiplier: 10}) // 100
func From0 ¶
From0 converts a function with 1 parameters returning a [R] into a function with 0 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From1 ¶
From1 converts a function with 2 parameters returning a [R] into a function with 1 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From10 ¶
func From10[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) R, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Reader[C, R]
From10 converts a function with 11 parameters returning a [R] into a function with 10 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From2 ¶
From2 converts a function with 3 parameters returning a [R] into a function with 2 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From3 ¶
From3 converts a function with 4 parameters returning a [R] into a function with 3 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From4 ¶
func From4[F ~func(C, T0, T1, T2, T3) R, T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) Reader[C, R]
From4 converts a function with 5 parameters returning a [R] into a function with 4 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From5 ¶
func From5[F ~func(C, T0, T1, T2, T3, T4) R, T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) Reader[C, R]
From5 converts a function with 6 parameters returning a [R] into a function with 5 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From6 ¶
func From6[F ~func(C, T0, T1, T2, T3, T4, T5) R, T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) Reader[C, R]
From6 converts a function with 7 parameters returning a [R] into a function with 6 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From7 ¶
func From7[F ~func(C, T0, T1, T2, T3, T4, T5, T6) R, T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) Reader[C, R]
From7 converts a function with 8 parameters returning a [R] into a function with 7 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From8 ¶
func From8[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) R, T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) Reader[C, R]
From8 converts a function with 9 parameters returning a [R] into a function with 8 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func From9 ¶
func From9[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) R, T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Reader[C, R]
From9 converts a function with 10 parameters returning a [R] into a function with 9 parameters returning a [Reader[C, R]] The first parameter is considered to be the context [C] of the reader
func Read ¶
Read applies a context to a Reader to obtain its value. This is the "run" operation that executes a Reader with a specific environment.
Example:
type Config struct { Port int }
getPort := reader.Asks(func(c Config) int { return c.Port })
run := reader.Read(Config{Port: 8080})
port := run(getPort) // 8080
func Traverse ¶
Traverse applies a Kleisli arrow to a value wrapped in a Reader, then sequences the result. It transforms a Reader[R2, A] into a function that takes R1 and returns Reader[R2, B], where the transformation from A to B is defined by a Kleisli arrow that depends on R1.
This is useful when you have a Reader computation that produces a value, and you want to apply another Reader computation to that value, but with a different environment type. The result is a function that takes the second environment and returns a Reader that takes the first environment.
Type Parameters:
- R2: The first environment type (outer Reader)
- R1: The second environment type (inner Reader/Kleisli)
- A: The input value type
- B: The output value type
Parameters:
- f: A Kleisli arrow from A to B that depends on environment R1
Returns:
- A function that takes a Reader[R2, A] and returns a Kleisli[R2, R1, B]
The signature can be understood as:
- Input: Reader[R2, A] (a computation that produces A given R2)
- Output: func(R1) Reader[R2, B] (a function that takes R1 and produces a computation that produces B given R2)
Example:
type Database struct { ConnectionString string }
type Config struct { TableName string }
// A Reader that gets a user ID from the database
getUserID := func(db Database) int {
// Simulate database query
return 42
}
// A Kleisli arrow that takes a user ID and returns a Reader that formats it with config
formatUser := func(id int) reader.Reader[Config, string] {
return func(c Config) string {
return fmt.Sprintf("User %d from table %s", id, c.TableName)
}
}
// Traverse applies formatUser to the result of getUserID
traversed := reader.Traverse(formatUser)(getUserID)
// Now we can apply both environments
config := Config{TableName: "users"}
db := Database{ConnectionString: "localhost:5432"}
result := traversed(config)(db) // "User 42 from table users"
The Traverse operation is particularly useful when:
- You need to compose computations that depend on different environments
- You want to apply a transformation that itself requires environmental context
- You're building pipelines where each stage has its own configuration
func TraverseRecord ¶
func TraverseRecord[K comparable, R, A, B any](f Kleisli[R, A, B]) func(map[K]A) Reader[R, map[K]B]
func TraverseRecordWithIndex ¶
func TraverseRecordWithIndex[K comparable, R, A, B any](f func(K, A) Reader[R, B]) func(map[K]A) Reader[R, map[K]B]
func Uncurry0 ¶
Uncurry0 converts a Reader back into a regular function with context as first parameter.
Example:
type Config struct { Value int }
r := reader.Of[Config](42)
f := reader.Uncurry0(r)
result := f(Config{Value: 0}) // 42
func Uncurry1 ¶
func Uncurry1[R, T1, A any](f Kleisli[R, T1, A]) func(R, T1) A
Uncurry1 converts a curried function returning a Reader back into a regular function with context as first parameter.
Example:
type Config struct { Prefix string }
curried := func(s string) reader.Reader[Config, string] {
return reader.Asks(func(c Config) string { return c.Prefix + s })
}
f := reader.Uncurry1(curried)
result := f(Config{Prefix: ">> "}, "hello") // ">> hello"
func Uncurry2 ¶
Uncurry2 converts a curried function with 2 parameters returning a Reader back into a regular function with context as first parameter.
Example:
type Config struct { Sep string }
curried := func(a string) func(string) reader.Reader[Config, string] {
return func(b string) reader.Reader[Config, string] {
return reader.Asks(func(c Config) string { return a + c.Sep + b })
}
}
f := reader.Uncurry2(curried)
result := f(Config{Sep: "-"}, "hello", "world") // "hello-world"
Types ¶
type Kleisli ¶
func Compose ¶
Compose composes two Readers sequentially, where the output environment of the first becomes the input environment of the second.
Example:
type Config struct { Port int }
type Env struct { Config Config }
getConfig := func(e Env) Config { return e.Config }
getPort := func(c Config) int { return c.Port }
getPortFromEnv := reader.Compose(getConfig)(getPort)
func Curry1 ¶
func Curry1[R, T1, A any](f func(R, T1) A) Kleisli[R, T1, A]
Curry1 converts a function with context as first parameter into a curried function returning a Reader. The context parameter is moved to the end (Reader position).
Example:
type Config struct { Prefix string }
addPrefix := func(c Config, s string) string { return c.Prefix + s }
curried := reader.Curry1(addPrefix)
r := curried("hello")
result := r(Config{Prefix: ">> "}) // ">> hello"
func Local ¶
Local changes the value of the local context during the execution of the action `ma`. This is similar to Contravariant's contramap and allows you to modify the environment before passing it to a Reader.
Example:
type DetailedConfig struct { Host string; Port int }
type SimpleConfig struct { Host string }
getHost := func(c SimpleConfig) string { return c.Host }
simplify := func(d DetailedConfig) SimpleConfig { return SimpleConfig{Host: d.Host} }
r := reader.Local(simplify)(getHost)
result := r(DetailedConfig{Host: "localhost", Port: 8080}) // "localhost"
func Promap ¶
Promap is the profunctor map operation that transforms both the input and output of a Reader. It applies f to the input (contravariantly) and g to the output (covariantly).
Example:
type Config struct { Port int }
type Env struct { Config Config }
getPort := func(c Config) int { return c.Port }
extractConfig := func(e Env) Config { return e.Config }
toString := strconv.Itoa
r := reader.Promap(extractConfig, toString)(getPort)
result := r(Env{Config: Config{Port: 8080}}) // "8080"
func ReduceArray ¶
ReduceArray returns a curried function that reduces an array of Readers to a single Reader. This is the curried version where the reduction function and initial value are provided first, returning a function that takes the array of Readers.
Parameters:
- reduce: Binary function that combines accumulated value with each Reader's result
- initial: Starting value for the reduction
Returns:
- A function that takes an array of Readers and returns a Reader of the reduced result
Example:
type Config struct { Multiplier int }
product := func(acc, val int) int { return acc * val }
reducer := reader.ReduceArray[Config](product, 1)
readers := []reader.Reader[Config, int]{
reader.Asks(func(c Config) int { return c.Multiplier * 2 }),
reader.Asks(func(c Config) int { return c.Multiplier * 3 }),
}
r := reducer(readers)
result := r(Config{Multiplier: 5}) // 150 (10 * 15)
func ReduceArrayM ¶
ReduceArrayM returns a curried function that reduces an array of Readers using a Monoid. This is the curried version where the Monoid is provided first, returning a function that takes the array of Readers.
The Monoid provides both the binary operation (Concat) and the identity element (Empty) for the reduction.
Parameters:
- m: Monoid that defines how to combine the Reader results
Returns:
- A function that takes an array of Readers and returns a Reader of the reduced result
Example:
type Config struct { Scale int }
intMultMonoid := monoid.MakeMonoid(func(a, b int) int { return a * b }, 1)
reducer := reader.ReduceArrayM[Config](intMultMonoid)
readers := []reader.Reader[Config, int]{
reader.Asks(func(c Config) int { return c.Scale }),
reader.Asks(func(c Config) int { return c.Scale * 2 }),
}
r := reducer(readers)
result := r(Config{Scale: 3}) // 18 (3 * 6)
func Sequence ¶
Sequence swaps the order of the two environment parameters in a Kleisli arrow. It transforms a function that takes A and returns Reader[R2, B] into a function that takes R2 and returns Reader[R1, A].
This is useful when you need to change the order of dependencies or when composing functions that expect their parameters in a different order.
Type Parameters:
- R1: The first environment type (becomes second after flip)
- R2: The second environment type (becomes first after flip)
- A: The result type
Parameters:
- ma: A Kleisli arrow from R2 to Reader[R1, A]
Returns:
- A Kleisli arrow from R1 to Reader[R2, A] with swapped environment parameters
Example:
type Config struct { Host string }
type Port int
// Original: takes Port, returns Reader[Config, string]
makeURL := func(port Port) reader.Reader[Config, string] {
return func(c Config) string {
return fmt.Sprintf("%s:%d", c.Host, port)
}
}
// Sequenced: takes Config, returns Reader[Port, string]
sequenced := reader.Sequence(makeURL)
result := sequenced(Config{Host: "localhost"})(Port(8080))
// result: "localhost:8080"
The Sequence operation is particularly useful when:
- You need to partially apply environments in a different order
- You're composing functions that expect parameters in reverse order
- You want to curry multi-parameter functions differently
func TraverseArray ¶
func TraverseArray[R, A, B any](f Kleisli[R, A, B]) Kleisli[R, []A, []B]
TraverseArray transforms each element of an array using a function that returns a Reader, then collects the results into a single Reader containing an array.
This is useful for performing a Reader computation on each element of an array where all computations share the same environment.
Example:
type Config struct { Multiplier int }
multiply := func(n int) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return n * c.Multiplier })
}
transform := reader.TraverseArray(multiply)
r := transform([]int{1, 2, 3})
result := r(Config{Multiplier: 10}) // [10, 20, 30]
func TraverseArrayWithIndex ¶
TraverseArrayWithIndex transforms each element of an array using a function that takes both the index and the element, returning a Reader. The results are collected into a single Reader containing an array.
This is useful when the transformation needs to know the position of each element.
Example:
type Config struct { Prefix string }
addIndexPrefix := func(i int, s string) reader.Reader[Config, string] {
return reader.Asks(func(c Config) string {
return fmt.Sprintf("%s[%d]:%s", c.Prefix, i, s)
})
}
transform := reader.TraverseArrayWithIndex(addIndexPrefix)
r := transform([]string{"a", "b", "c"})
result := r(Config{Prefix: "item"}) // ["item[0]:a", "item[1]:b", "item[2]:c"]
func TraverseReduceArray ¶
func TraverseReduceArray[R, A, B, C any](trfrm Kleisli[R, A, B], reduce func(C, B) C, initial C) Kleisli[R, []A, C]
TraverseReduceArray returns a curried function that transforms and reduces an array. This is the curried version where the transformation function, reduce function, and initial value are provided first, returning a function that takes the array.
First, each element is transformed using the provided Kleisli function into a Reader. Then, the Reader results are reduced using the provided reduction function.
Parameters:
- trfrm: Function that transforms each element into a Reader
- reduce: Binary function that combines accumulated value with each transformed result
- initial: Starting value for the reduction
Returns:
- A function that takes an array and returns a Reader of the reduced result
Example:
type Config struct { Base int }
addBase := func(n int) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return n + c.Base })
}
product := func(acc, val int) int { return acc * val }
transformer := reader.TraverseReduceArray(addBase, product, 1)
r := transformer([]int{2, 3, 4})
result := r(Config{Base: 10}) // 2184 (12 * 13 * 14)
func TraverseReduceArrayM ¶
func TraverseReduceArrayM[R, A, B any](trfrm Kleisli[R, A, B], m monoid.Monoid[B]) Kleisli[R, []A, B]
TraverseReduceArrayM returns a curried function that transforms and reduces an array using a Monoid. This is the curried version where the transformation function and Monoid are provided first, returning a function that takes the array.
First, each element is transformed using the provided Kleisli function into a Reader. Then, the Reader results are reduced using the Monoid's binary operation and identity element.
Parameters:
- trfrm: Function that transforms each element into a Reader
- m: Monoid that defines how to combine the transformed results
Returns:
- A function that takes an array and returns a Reader of the reduced result
Example:
type Config struct { Factor int }
scale := func(n int) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return n * c.Factor })
}
intProdMonoid := monoid.MakeMonoid(func(a, b int) int { return a * b }, 1)
transformer := reader.TraverseReduceArrayM(scale, intProdMonoid)
r := transformer([]int{2, 3, 4})
result := r(Config{Factor: 5}) // 3000 (10 * 15 * 20)
type Operator ¶
Operator represents a transformation from one Reader to another. It takes a Reader[R, A] and produces a Reader[R, B], where both readers share the same environment type R.
This type is commonly used for operations like Map, Chain, and other transformations that convert readers while preserving the environment type.
Type Parameters:
- R: The shared environment/context type
- A: The input Reader's result type
- B: The output Reader's result type
Example:
type Config struct { Multiplier int }
// An operator that transforms int readers to string readers
intToString := reader.Map[Config, int, string](strconv.Itoa)
getNumber := reader.Asks(func(c Config) int { return c.Multiplier })
getString := intToString(getNumber)
result := getString(Config{Multiplier: 42}) // "42"
func Ap ¶
Ap applies a Reader containing a function to a Reader containing a value. This is the Applicative operation for combining independent computations.
Example:
type Config struct { X, Y int }
add := func(x int) func(int) int { return func(y int) int { return x + y } }
getX := reader.Map(add)(reader.Asks(func(c Config) int { return c.X }))
getY := reader.Asks(func(c Config) int { return c.Y })
getSum := reader.Ap(getY)(getX)
sum := getSum(Config{X: 3, Y: 4}) // 7
func ApS ¶
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 is useful when you have independent computations that can be combined without one depending on the result of the other.
Example:
type State struct {
Host string
Port int
}
type Config struct {
Host string
Port int
}
getPort := reader.Asks(func(c Config) int { return c.Port })
addPort := reader.ApS(
func(port int) func(State) State {
return func(s State) State { s.Port = port; return s }
},
getPort,
)
func ApSL ¶
ApSL attaches a value to a context using a lens-based setter. This is a convenience function that combines ApS with a lens, allowing you to use optics to update nested structures in a more composable way.
The lens parameter provides both the getter and setter for a field within the structure S. This eliminates the need to manually write setter functions.
Example:
type State struct {
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 := reader.Asks(func(c Config) int { return c.DefaultPort })
result := F.Pipe2(
reader.Of[Config](State{Host: "localhost"}),
reader.ApSL(portLens, getPort),
)
func Bind ¶
func Bind[R, S1, S2, T any]( setter func(T) func(S1) S2, f Kleisli[R, S1, T], ) Operator[R, S1, S2]
Bind attaches the result of a computation to a context [S1] to produce a context [S2]. This enables building up complex computations in a pipeline 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 { Value int }
type Config struct { Increment int }
addIncrement := reader.Bind(
func(inc int) func(State) State {
return func(s State) State { return State{Value: s.Value + inc} }
},
func(s State) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return c.Increment })
},
)
func BindL ¶
BindL is a variant of Bind that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.
The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a Reader computation that produces an updated value.
Example:
type State struct {
Config ConfigData
Status string
}
type ConfigData struct {
Host string
Port int
}
type Env struct {
DefaultHost string
DefaultPort int
}
configLens := lens.MakeLens(
func(s State) ConfigData { return s.Config },
func(s State, c ConfigData) State { s.Config = c; return s },
)
result := F.Pipe2(
reader.Do[Env](State{}),
reader.BindL(configLens, func(cfg ConfigData) reader.Reader[Env, ConfigData] {
return reader.Asks(func(e Env) ConfigData {
return ConfigData{Host: e.DefaultHost, Port: e.DefaultPort}
})
}),
)
func BindTo ¶
func BindTo[R, S1, T any]( setter func(T) S1, ) Operator[R, T, S1]
BindTo initializes a new state [S1] from a value [T]. This is typically used to start a binding chain by wrapping an initial Reader value into a state structure.
Example:
type State struct { Name string }
type Config struct { DefaultName string }
getName := reader.Asks(func(c Config) string { return c.DefaultName })
initState := reader.BindTo(func(name string) State {
return State{Name: name}
})
result := initState(getName)
func Chain ¶
func Chain[R, A, B any](f Kleisli[R, A, B]) Operator[R, A, B]
Chain sequences two Reader computations where the second depends on the result of the first. This is the Monad operation that enables dependent computations.
Example:
type Config struct { UserId int }
getUser := reader.Asks(func(c Config) int { return c.UserId })
getUserName := func(id int) reader.Reader[Config, string] {
return reader.Of[Config](fmt.Sprintf("User%d", id))
}
r := reader.Chain(getUserName)(getUser)
name := r(Config{UserId: 42}) // "User42"
func ChainTo ¶
ChainTo creates an operator that completely ignores any Reader and returns a specific Reader. This is the curried version where the second Reader is provided first, returning a function that can be applied to any first Reader (which will be ignored).
IMPORTANT: Readers are pure functions with no side effects. This operator does NOT compose or evaluate the input Reader - it completely ignores it and returns the specified Reader directly. The input Reader is neither executed during composition nor when the resulting Reader runs.
Type Parameters:
- A: The result type of the first Reader (completely ignored)
- R: The environment type
- B: The result type of the second Reader
Parameters:
- b: The Reader to return (ignoring any input Reader)
Returns:
- An Operator that takes a Reader[R, A] and returns Reader[R, B]
Example:
type Config struct { Counter int; Message string }
getMessage := func(c Config) string { return c.Message }
// Create an operator that ignores any Reader and returns getMessage
thenGetMessage := reader.ChainTo[int, Config, string](getMessage)
increment := func(c Config) int { return c.Counter + 1 }
pipeline := thenGetMessage(increment)
result := pipeline(Config{Counter: 5, Message: "done"}) // "done" (increment was never evaluated)
Example - In a functional pipeline:
type Env struct { Step int; Result string }
step1 := reader.Asks(func(e Env) int { return e.Step })
getResult := reader.Asks(func(e Env) string { return e.Result })
pipeline := F.Pipe1(
step1,
reader.ChainTo[int, Env, string](getResult),
)
output := pipeline(Env{Step: 1, Result: "success"}) // "success" (step1 was never evaluated)
func Flap ¶
func Flap[R, B, A any](a A) Operator[R, func(A) B, B]
Flap takes a value and returns a function that applies a Reader containing a function to that value. This is useful for partial application in the Reader context.
Example:
type Config struct { Multiplier int }
getMultiplier := reader.Asks(func(c Config) func(int) int {
return N.Mul(c.Multiplier)
})
applyTo5 := reader.Flap[Config](5)
r := applyTo5(getMultiplier)
result := r(Config{Multiplier: 3}) // 15
func Let ¶
func Let[R, S1, S2, T any]( setter func(T) func(S1) S2, f func(S1) T, ) Operator[R, S1, S2]
Let attaches the result of a pure computation to a context [S1] to produce a context [S2]. Unlike Bind, the computation function f does not return a Reader, just a plain value. This is useful for transformations that don't need to access the environment.
Example:
type State struct {
FirstName string
LastName string
FullName string
}
addFullName := reader.Let(
func(full string) func(State) State {
return func(s State) State { s.FullName = full; return s }
},
func(s State) string {
return s.FirstName + " " + s.LastName
},
)
func LetL ¶
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 Reader).
Example:
type State struct {
Config ConfigData
Status string
}
type ConfigData struct {
Host string
Port int
}
configLens := lens.MakeLens(
func(s State) ConfigData { return s.Config },
func(s State, c ConfigData) State { s.Config = c; return s },
)
result := F.Pipe2(
reader.Do[any](State{Config: ConfigData{Host: "localhost"}}),
reader.LetL(configLens, func(cfg ConfigData) ConfigData {
cfg.Port = 8080
return cfg
}),
)
func LetTo ¶
func LetTo[R, S1, S2, T any]( setter func(T) func(S1) S2, b T, ) Operator[R, S1, S2]
LetTo attaches a constant value to a context [S1] to produce a context [S2]. This is useful for adding fixed values to the context without any computation.
Example:
type State struct {
Name string
Version string
}
addVersion := reader.LetTo(
func(v string) func(State) State {
return func(s State) State { s.Version = v; return s }
},
"1.0.0",
)
func LetToL ¶
LetToL is a variant of LetTo that uses a lens to focus on a specific part of the context. This provides a more ergonomic API when working with nested structures, eliminating the need to manually write setter functions.
The lens parameter provides both a getter and setter for a field of type T within the context S. The value b is set directly to the focused field.
Example:
type State struct {
Config ConfigData
Status string
}
type ConfigData struct {
Host string
Port int
}
configLens := lens.MakeLens(
func(s State) ConfigData { return s.Config },
func(s State, c ConfigData) State { s.Config = c; return s },
)
newConfig := ConfigData{Host: "localhost", Port: 8080}
result := F.Pipe2(
reader.Do[any](State{}),
reader.LetToL(configLens, newConfig),
)
func Map ¶
func Map[E, A, B any](f func(A) B) Operator[E, A, B]
Map transforms the result value of a Reader using the provided function. This is the Functor operation that allows you to transform values inside the Reader context.
Map can be used to turn functions `func(A)B` into functions `(fa F[A])F[B]` whose argument and return types use the type constructor `F` to represent some computational context.
Example:
type Config struct { Port int }
getPort := reader.Asks(func(c Config) int { return c.Port })
getPortStr := reader.Map(strconv.Itoa)(getPort)
result := getPortStr(Config{Port: 8080}) // "8080"
func MapTo ¶
func MapTo[E, A, B any](b B) Operator[E, A, B]
MapTo creates an operator that completely ignores any Reader 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 Reader.
IMPORTANT: Readers are pure functions with no side effects. This operator does NOT compose or evaluate the input Reader - it completely ignores it and returns a new Reader that always returns the constant value. The input Reader is neither executed during composition nor when the resulting Reader runs.
Type Parameters:
- E: The environment type
- A: The result type of the input Reader (completely ignored)
- B: The type of the constant value to return
Parameters:
- b: The constant value to return
Returns:
- An Operator that takes a Reader[E, A] and returns Reader[E, B]
Example:
type Config struct { Counter int }
increment := reader.Asks(func(c Config) int { return c.Counter + 1 })
// Create an operator that ignores any Reader and returns "done"
toDone := reader.MapTo[Config, int, string]("done")
pipeline := toDone(increment)
result := pipeline(Config{Counter: 5}) // "done" (increment was never evaluated)
Example - In a functional pipeline:
type Env struct { Step int }
step1 := reader.Asks(func(e Env) int { return e.Step })
pipeline := F.Pipe1(
step1,
reader.MapTo[Env, int, string]("complete"),
)
output := pipeline(Env{Step: 1}) // "complete" (step1 was never evaluated)
type Reader ¶
type Reader[R, A any] = func(R) A
Reader represents a computation that depends on a shared environment of type R and produces a value of type A.
The purpose of the Reader monad is to avoid threading arguments through multiple functions in order to only get them where they are needed. This enables dependency injection and configuration management in a functional style.
Type Parameters:
- R: The environment/context type (read-only, shared across computations)
- A: The result type produced by the computation
A Reader[R, A] is simply a function from R to A: func(R) A
Example:
type Config struct {
DatabaseURL string
APIKey string
}
// A Reader that extracts the database URL from config
getDatabaseURL := func(c Config) string { return c.DatabaseURL }
// A Reader that extracts the API key from config
getAPIKey := func(c Config) string { return c.APIKey }
// Use the readers with a config
config := Config{DatabaseURL: "localhost:5432", APIKey: "secret"}
dbURL := getDatabaseURL(config) // "localhost:5432"
apiKey := getAPIKey(config) // "secret"
func Ask ¶
Ask reads the current context and returns it as the result. This is the fundamental operation for accessing the environment.
Example:
type Config struct { Host string }
r := reader.Ask[Config]()
config := r(Config{Host: "localhost"}) // Returns the config itself
func Asks ¶
Asks projects a value from the global context in a Reader. It's essentially an identity function that makes the intent clearer.
Example:
type Config struct { Port int }
getPort := reader.Asks(func(c Config) int { return c.Port })
port := getPort(Config{Port: 8080}) // Returns 8080
func AsksReader ¶
AsksReader creates a Reader that depends on the environment to produce another Reader, then immediately executes that Reader with the same environment.
This is useful when you need to dynamically choose a Reader based on the environment.
Example:
type Config struct { UseCache bool }
r := reader.AsksReader(func(c Config) reader.Reader[Config, string] {
if c.UseCache {
return reader.Of[Config]("cached")
}
return reader.Of[Config]("fresh")
})
func Curry0 ¶
Curry0 converts a function that takes a context and returns a value into a Reader.
Example:
type Config struct { Value int }
getValue := func(c Config) int { return c.Value }
r := reader.Curry0(getValue)
result := r(Config{Value: 42}) // 42
func Do ¶
Do creates an empty context of type [S] to be used with the Bind operation. This is the starting point for the do-notation style of composing Reader computations.
Example:
type State struct {
Name string
Age int
}
type Config struct {
DefaultName string
DefaultAge int
}
result := function.Pipe3(
reader.Do[Config](State{}),
reader.Bind(
func(name string) func(State) State {
return func(s State) State { s.Name = name; return s }
},
func(s State) reader.Reader[Config, string] {
return reader.Asks(func(c Config) string { return c.DefaultName })
},
),
reader.Bind(
func(age int) func(State) State {
return func(s State) State { s.Age = age; return s }
},
func(s State) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return c.DefaultAge })
},
),
)
func First ¶
First applies a Reader to the first element of a tuple, leaving the second element unchanged. This is useful for working with paired data where only one element needs transformation.
Example:
double := N.Mul(2) r := reader.First[int, int, string](double) result := r(tuple.MakeTuple2(5, "hello")) // (10, "hello")
func Flatten ¶
Flatten removes one level of Reader nesting. Converts Reader[R, Reader[R, A]] to Reader[R, A].
Example:
type Config struct { Value int }
nested := func(c Config) reader.Reader[Config, int] {
return func(c2 Config) int { return c.Value + c2.Value }
}
flat := reader.Flatten(nested)
result := flat(Config{Value: 5}) // 10 (5 + 5)
func MonadAp ¶
MonadAp applies a Reader containing a function to a Reader containing a value. Both Readers share the same environment and are evaluated with it. This is the monadic version that takes both parameters.
Example:
type Config struct { X, Y int }
add := func(x int) func(int) int { return func(y int) int { return x + y } }
getX := func(c Config) func(int) int { return add(c.X) }
getY := func(c Config) int { return c.Y }
result := reader.MonadAp(getX, getY)
sum := result(Config{X: 3, Y: 4}) // 7
func MonadChain ¶
MonadChain sequences two Reader computations where the second depends on the result of the first. Both computations share the same environment. This is the monadic bind operation (flatMap).
Example:
type Config struct { UserId int }
getUser := func(c Config) int { return c.UserId }
getUserName := func(id int) reader.Reader[Config, string] {
return func(c Config) string { return fmt.Sprintf("User%d", id) }
}
r := reader.MonadChain(getUser, getUserName)
name := r(Config{UserId: 42}) // "User42"
func MonadChainTo ¶
MonadChainTo completely ignores the first Reader and returns the second Reader. This is the monadic version that takes both Readers as parameters.
IMPORTANT: Readers are pure functions with no side effects. This function does NOT compose or evaluate the first Reader - it completely ignores it and returns the second Reader directly. The first Reader is neither executed during composition nor when the resulting Reader runs.
Type Parameters:
- A: The result type of the first Reader (completely ignored)
- R: The environment type
- B: The result type of the second Reader
Parameters:
- _: The first Reader (completely ignored, never evaluated)
- b: The second Reader to return
Returns:
- The second Reader unchanged
Example:
type Config struct { Counter int; Message string }
increment := func(c Config) int { return c.Counter + 1 }
getMessage := func(c Config) string { return c.Message }
// Ignore increment and return getMessage
r := reader.MonadChainTo(increment, getMessage)
result := r(Config{Counter: 5, Message: "done"}) // "done" (increment was never evaluated)
func MonadFlap ¶
MonadFlap is the monadic version of Flap. It takes a Reader containing a function and a value, and returns a Reader that applies the function to the value.
Example:
type Config struct { Multiplier int }
getMultiplier := func(c Config) func(int) int {
return N.Mul(c.Multiplier)
}
r := reader.MonadFlap(getMultiplier, 5)
result := r(Config{Multiplier: 3}) // 15
func MonadMap ¶
MonadMap transforms the result value of a Reader using the provided function. This is the monadic version that takes the Reader as the first parameter.
Example:
type Config struct { Port int }
getPort := func(c Config) int { return c.Port }
getPortStr := reader.MonadMap(getPort, strconv.Itoa)
result := getPortStr(Config{Port: 8080}) // "8080"
func MonadMapTo ¶
MonadMapTo creates a new Reader that completely ignores the first Reader and returns a constant value. This is the monadic version that takes both the Reader and the constant value as parameters.
IMPORTANT: Readers are pure functions with no side effects. This function does NOT compose or evaluate the first Reader - it completely ignores it and returns a new Reader that always returns the constant value. The first Reader is neither executed during composition nor when the resulting Reader runs.
Type Parameters:
- E: The environment type
- A: The result type of the first Reader (completely ignored)
- B: The type of the constant value to return
Parameters:
- _: The first Reader (completely ignored, never evaluated)
- b: The constant value to return
Returns:
- A new Reader that ignores the environment and always returns b
Example:
type Config struct { Counter int }
increment := func(c Config) int { return c.Counter + 1 }
// Create a Reader that ignores increment and returns "done"
r := reader.MonadMapTo(increment, "done")
result := r(Config{Counter: 5}) // "done" (increment was never evaluated)
func MonadReduceArray ¶
MonadReduceArray reduces an array of Readers to a single Reader by applying a reduction function. This is the monadic version that takes the array of Readers as the first parameter.
Each Reader is evaluated with the same environment R, and the results are accumulated using the provided reduce function starting from the initial value.
Parameters:
- as: Array of Readers to reduce
- reduce: Binary function that combines accumulated value with each Reader's result
- initial: Starting value for the reduction
Example:
type Config struct { Base int }
readers := []reader.Reader[Config, int]{
reader.Asks(func(c Config) int { return c.Base + 1 }),
reader.Asks(func(c Config) int { return c.Base + 2 }),
reader.Asks(func(c Config) int { return c.Base + 3 }),
}
sum := func(acc, val int) int { return acc + val }
r := reader.MonadReduceArray(readers, sum, 0)
result := r(Config{Base: 10}) // 36 (11 + 12 + 13)
func MonadReduceArrayM ¶
MonadReduceArrayM reduces an array of Readers using a Monoid to combine the results. This is the monadic version that takes the array of Readers as the first parameter.
The Monoid provides both the binary operation (Concat) and the identity element (Empty) for the reduction, making it convenient when working with monoidal types.
Parameters:
- as: Array of Readers to reduce
- m: Monoid that defines how to combine the Reader results
Example:
type Config struct { Factor int }
readers := []reader.Reader[Config, int]{
reader.Asks(func(c Config) int { return c.Factor }),
reader.Asks(func(c Config) int { return c.Factor * 2 }),
reader.Asks(func(c Config) int { return c.Factor * 3 }),
}
intAddMonoid := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0)
r := reader.MonadReduceArrayM(readers, intAddMonoid)
result := r(Config{Factor: 5}) // 30 (5 + 10 + 15)
func MonadTraverseArray ¶
MonadTraverseArray transforms each element of an array using a function that returns a Reader, then collects the results into a single Reader containing an array. This is the monadic version that takes the array as the first parameter.
All Readers share the same environment R and are evaluated with it.
Example:
type Config struct { Prefix string }
numbers := []int{1, 2, 3}
addPrefix := func(n int) reader.Reader[Config, string] {
return reader.Asks(func(c Config) string {
return fmt.Sprintf("%s%d", c.Prefix, n)
})
}
r := reader.MonadTraverseArray(numbers, addPrefix)
result := r(Config{Prefix: "num"}) // ["num1", "num2", "num3"]
func MonadTraverseRecord ¶
func MonadTraverseRecord[K comparable, R, A, B any](ma map[K]A, f Kleisli[R, A, B]) Reader[R, map[K]B]
func MonadTraverseRecordWithIndex ¶
func MonadTraverseRecordWithIndex[K comparable, R, A, B any](ma map[K]A, f func(K, A) Reader[R, B]) Reader[R, map[K]B]
func MonadTraverseReduceArray ¶
func MonadTraverseReduceArray[R, A, B, C any](as []A, trfrm Kleisli[R, A, B], reduce func(C, B) C, initial C) Reader[R, C]
MonadTraverseReduceArray transforms and reduces an array in one operation. This is the monadic version that takes the array as the first parameter.
First, each element is transformed using the provided Kleisli function into a Reader. Then, the Reader results are reduced using the provided reduction function.
This is more efficient than calling TraverseArray followed by a separate reduce operation, as it combines both operations into a single traversal.
Parameters:
- as: Array of elements to transform and reduce
- trfrm: Function that transforms each element into a Reader
- reduce: Binary function that combines accumulated value with each transformed result
- initial: Starting value for the reduction
Example:
type Config struct { Multiplier int }
numbers := []int{1, 2, 3, 4}
multiply := func(n int) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return n * c.Multiplier })
}
sum := func(acc, val int) int { return acc + val }
r := reader.MonadTraverseReduceArray(numbers, multiply, sum, 0)
result := r(Config{Multiplier: 10}) // 100 (10 + 20 + 30 + 40)
func MonadTraverseReduceArrayM ¶
func MonadTraverseReduceArrayM[R, A, B any](as []A, trfrm Kleisli[R, A, B], m monoid.Monoid[B]) Reader[R, B]
MonadTraverseReduceArrayM transforms and reduces an array using a Monoid. This is the monadic version that takes the array as the first parameter.
First, each element is transformed using the provided Kleisli function into a Reader. Then, the Reader results are reduced using the Monoid's binary operation and identity element.
This combines transformation and monoidal reduction in a single efficient operation.
Parameters:
- as: Array of elements to transform and reduce
- trfrm: Function that transforms each element into a Reader
- m: Monoid that defines how to combine the transformed results
Example:
type Config struct { Offset int }
numbers := []int{1, 2, 3}
addOffset := func(n int) reader.Reader[Config, int] {
return reader.Asks(func(c Config) int { return n + c.Offset })
}
intSumMonoid := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0)
r := reader.MonadTraverseReduceArrayM(numbers, addOffset, intSumMonoid)
result := r(Config{Offset: 100}) // 306 (101 + 102 + 103)
func Of ¶
Of lifts a pure value into the Reader context. The resulting Reader ignores its environment and always returns the given value. This is the Pointed/Applicative pure operation.
Example:
type Config struct { Host string }
r := reader.Of[Config]("constant value")
result := r(Config{Host: "any"}) // "constant value"
func Second ¶
Second applies a Reader to the second element of a tuple, leaving the first element unchanged. This is useful for working with paired data where only one element needs transformation.
Example:
double := N.Mul(2)
r := reader.Second[string, int, int](double)
result := r(tuple.MakeTuple2("hello", 5)) // ("hello", 10)
func SequenceArray ¶
SequenceArray converts an array of Readers into a single Reader containing an array. All Readers in the input array share the same environment and are evaluated with it.
This is useful when you have multiple independent Reader computations and want to collect all their results.
Example:
type Config struct { X, Y, Z int }
readers := []reader.Reader[Config, int]{
reader.Asks(func(c Config) int { return c.X }),
reader.Asks(func(c Config) int { return c.Y }),
reader.Asks(func(c Config) int { return c.Z }),
}
r := reader.SequenceArray(readers)
result := r(Config{X: 1, Y: 2, Z: 3}) // [1, 2, 3]
func SequenceRecord ¶
func SequenceRecord[K comparable, R, A any](ma map[K]Reader[R, A]) Reader[R, map[K]A]
func SequenceT1 ¶
SequenceT1 combines 1 Reader into a Reader of a 1-tuple.
Example:
type Config struct { Value int }
r := reader.Asks(func(c Config) int { return c.Value })
result := reader.SequenceT1(r)
tuple := result(Config{Value: 42}) // Tuple1{F1: 42}
func SequenceT2 ¶
SequenceT2 combines 2 Readers into a Reader of a 2-tuple. All Readers share the same environment and are evaluated with it.
Example:
type Config struct { X, Y int }
getX := reader.Asks(func(c Config) int { return c.X })
getY := reader.Asks(func(c Config) int { return c.Y })
result := reader.SequenceT2(getX, getY)
tuple := result(Config{X: 10, Y: 20}) // Tuple2{F1: 10, F2: 20}
func SequenceT3 ¶
func SequenceT3[R, A, B, C any](a Reader[R, A], b Reader[R, B], c Reader[R, C]) Reader[R, T.Tuple3[A, B, C]]
SequenceT3 combines 3 Readers into a Reader of a 3-tuple. All Readers share the same environment and are evaluated with it.
Example:
type Config struct { Host string; Port int; Secure bool }
getHost := reader.Asks(func(c Config) string { return c.Host })
getPort := reader.Asks(func(c Config) int { return c.Port })
getSecure := reader.Asks(func(c Config) bool { return c.Secure })
result := reader.SequenceT3(getHost, getPort, getSecure)
tuple := result(Config{Host: "localhost", Port: 8080, Secure: true})
// Tuple3{F1: "localhost", F2: 8080, F3: true}
func SequenceT4 ¶
func SequenceT4[R, A, B, C, D any](a Reader[R, A], b Reader[R, B], c Reader[R, C], d Reader[R, D]) Reader[R, T.Tuple4[A, B, C, D]]
SequenceT4 combines 4 Readers into a Reader of a 4-tuple. All Readers share the same environment and are evaluated with it.
Example:
type Config struct { A, B, C, D int }
getA := reader.Asks(func(c Config) int { return c.A })
getB := reader.Asks(func(c Config) int { return c.B })
getC := reader.Asks(func(c Config) int { return c.C })
getD := reader.Asks(func(c Config) int { return c.D })
result := reader.SequenceT4(getA, getB, getC, getD)
tuple := result(Config{A: 1, B: 2, C: 3, D: 4})
// Tuple4{F1: 1, F2: 2, F3: 3, F4: 4}