reader

package
v2.0.4 Latest Latest
Warning

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

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

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:

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

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

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

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

func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1) func(T2) func(T3) Reader[R, A]

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

func From0[F ~func(C) R, C, R any](f F) func() Reader[C, R]

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

func From1[F ~func(C, T0) R, T0, C, R any](f F) func(T0) Reader[C, R]

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

func From2[F ~func(C, T0, T1) R, T0, T1, C, R any](f F) func(T0, T1) Reader[C, R]

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

func From3[F ~func(C, T0, T1, T2) R, T0, T1, T2, C, R any](f F) func(T0, T1, T2) Reader[C, R]

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

func Read[A, E any](e E) func(Reader[E, A]) A

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

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

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

func Uncurry0[R, A any](f Reader[R, A]) func(R) A

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

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

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"

func Uncurry3

func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) Reader[R, A]) func(R, T1, T2, T3) A

Uncurry3 converts a curried function with 3 parameters returning a Reader back into a regular function with context as first parameter.

func Uncurry4

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

Uncurry4 converts a curried function with 4 parameters returning a Reader back into a regular function with context as first parameter.

Types

type Kleisli

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

func Compose

func Compose[C, R, B any](ab Reader[R, B]) Kleisli[R, Reader[B, C], C]

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

func Local[A, R2, R1 any](f func(R2) R1) Kleisli[R2, Reader[R1, A], A]

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

func Promap[E, A, D, B any](f func(D) E, g func(A) B) Kleisli[D, Reader[E, A], B]

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

func ReduceArray[R, A, B any](reduce func(B, A) B, initial B) Kleisli[R, []Reader[R, A], B]

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

func ReduceArrayM[R, A any](m monoid.Monoid[A]) Kleisli[R, []Reader[R, A], A]

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

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

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

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

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

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

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

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

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

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

ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently (using Applicative rather than Monad).

This 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

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

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

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

Example:

type State struct {
    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

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

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

The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a 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

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

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

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

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

The lens parameter provides both a getter and setter for a field of type T within the context S. The function f receives the current value of the focused field and returns a new value (without wrapping in a 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

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

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

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

Example:

type State struct {
    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

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

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

func Asks[R, A any](f Reader[R, A]) Reader[R, A]

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

func AsksReader[R, A any](f Kleisli[R, R, A]) Reader[R, A]

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 Bracket

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

func Curry0[R, A any](f func(R) A) Reader[R, A]

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

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

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

func First[A, B, C any](pab Reader[A, B]) Reader[T.Tuple2[A, C], T.Tuple2[B, C]]

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

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

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

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

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

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

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

func MonadChainTo[A, R, B any](_ Reader[R, A], b Reader[R, B]) Reader[R, B]

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

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

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

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

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

func MonadMapTo[E, A, B any](_ Reader[E, A], b B) Reader[E, B]

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

func MonadReduceArray[R, A, B any](as []Reader[R, A], reduce func(B, A) B, initial B) Reader[R, B]

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

func MonadReduceArrayM[R, A any](as []Reader[R, A], m monoid.Monoid[A]) Reader[R, A]

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

func MonadTraverseArray[R, A, B any](ma []A, f Kleisli[R, A, B]) Reader[R, []B]

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

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

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

func Second[A, B, C any](pbc Reader[B, C]) Reader[T.Tuple2[A, B], T.Tuple2[A, C]]

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

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

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

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

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

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

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}

Directories

Path Synopsis
Package generic provides generic array operations for custom Reader types.
Package generic provides generic array operations for custom Reader types.

Jump to

Keyboard shortcuts

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