readeriooption

package
v2.2.6 Latest Latest
Warning

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

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

Documentation

Overview

Package readeriooption provides a monad transformer that combines the Reader, IO, and Option monads.

Overview

ReaderIOOption[R, A] represents a computation that:

  • Depends on a shared environment of type R (Reader monad)
  • Performs side effects (IO monad)
  • May fail to produce a value of type A (Option monad)

This is particularly useful for computations that need:

  • Dependency injection or configuration access
  • Side effects like I/O operations
  • Optional results without using error types

The ReaderIOOption monad is defined as: Reader[R, IOOption[A]]

Fantasy Land Specification

This package implements the following Fantasy Land algebras:

Core Operations

Creating ReaderIOOption values:

  • Of/Some: Wraps a value in a successful ReaderIOOption
  • None: Creates a ReaderIOOption representing no value
  • FromOption: Lifts an Option into ReaderIOOption
  • FromReader: Lifts a Reader into ReaderIOOption
  • Ask/Asks: Accesses the environment

Transforming values:

  • Map: Transforms the value inside a ReaderIOOption
  • Chain: Sequences ReaderIOOption computations
  • Ap: Applies a function wrapped in ReaderIOOption
  • Alt: Provides alternative computation on failure

Extracting values:

  • Fold: Extracts value by providing handlers for both cases
  • GetOrElse: Returns value or default
  • Read: Executes the computation with an environment

Basic Example

type Config struct {
    DatabaseURL string
    Timeout     int
}

// A computation that may or may not find a user
func findUser(id int) readeriooption.ReaderIOOption[Config, User] {
    return readeriooption.Asks(func(cfg Config) iooption.IOOption[User] {
        return func() option.Option[User] {
            // Use cfg.DatabaseURL to query database
            // Return Some(user) if found, None() if not found
            user, found := queryDB(cfg.DatabaseURL, id)
            if found {
                return option.Some(user)
            }
            return option.None[User]()
        }
    })
}

// Chain multiple operations
result := F.Pipe2(
    findUser(123),
    readeriooption.Chain(func(user User) readeriooption.ReaderIOOption[Config, Profile] {
        return loadProfile(user.ProfileID)
    }),
    readeriooption.Map(func(profile Profile) string {
        return profile.DisplayName
    }),
)

// Execute with config
config := Config{DatabaseURL: "localhost:5432", Timeout: 30}
displayName := result(config)() // Returns Option[string]

Do-Notation Style

The package supports do-notation style composition for building complex computations:

type State struct {
    User    User
    Profile Profile
    Posts   []Post
}

result := F.Pipe3(
    readeriooption.Do[Config](State{}),
    readeriooption.Bind(
        func(user User) func(State) State {
            return func(s State) State { s.User = user; return s }
        },
        func(s State) readeriooption.ReaderIOOption[Config, User] {
            return findUser(123)
        },
    ),
    readeriooption.Bind(
        func(profile Profile) func(State) State {
            return func(s State) State { s.Profile = profile; return s }
        },
        func(s State) readeriooption.ReaderIOOption[Config, Profile] {
            return loadProfile(s.User.ProfileID)
        },
    ),
    readeriooption.Bind(
        func(posts []Post) func(State) State {
            return func(s State) State { s.Posts = posts; return s }
        },
        func(s State) readeriooption.ReaderIOOption[Config, []Post] {
            return loadPosts(s.User.ID)
        },
    ),
)

Alternative Computations

Use Alt to provide fallback behavior when computations fail:

// Try cache first, fall back to database
result := F.Pipe1(
    findUserInCache(123),
    readeriooption.Alt(func() readeriooption.ReaderIOOption[Config, User] {
        return findUserInDB(123)
    }),
)

Array Operations

Transform arrays where each element may fail:

userIDs := []int{1, 2, 3, 4, 5}
users := F.Pipe1(
    readeriooption.Of[Config](userIDs),
    readeriooption.Chain(readeriooption.TraverseArray[Config](findUser)),
)
// Returns Some([]User) if all users found, None otherwise

Monoid Operations

Combine multiple ReaderIOOption computations:

import N "github.com/IBM/fp-go/v2/number"

// Applicative monoid - all must succeed
intAdd := N.MonoidSum[int]()
roMonoid := readeriooption.ApplicativeMonoid[Config](intAdd)
combined := roMonoid.Concat(
    readeriooption.Of[Config](5),
    readeriooption.Of[Config](3),
)
// Returns Some(8)

// Alternative monoid - provides fallback
altMonoid := readeriooption.AlternativeMonoid[Config](intAdd)
withFallback := altMonoid.Concat(
    readeriooption.None[Config, int](),
    readeriooption.Of[Config](10),
)
// Returns Some(10)

Profunctor Operations

Transform both input and output:

type GlobalConfig struct {
    DB DBConfig
}

type DBConfig struct {
    Host string
}

// Adapt environment and transform result
adapted := F.Pipe1(
    queryDB, // ReaderIOOption[DBConfig, User]
    readeriooption.Promap(
        func(g GlobalConfig) DBConfig { return g.DB },
        func(u User) string { return u.Name },
    ),
)
// Now: ReaderIOOption[GlobalConfig, string]

Tail Recursion

For recursive computations, use TailRec to avoid stack overflow:

func factorial(n int) readeriooption.ReaderIOOption[Config, int] {
    return readeriooption.TailRec(func(acc int) readeriooption.ReaderIOOption[Config, tailrec.Trampoline[int, int]] {
        if n <= 1 {
            return readeriooption.Of[Config](tailrec.Done[int](acc))
        }
        return readeriooption.Of[Config](tailrec.Continue[int](acc * n))
    })(1)
}

Relationship to Other Monads

ReaderIOOption is related to other monads in the fp-go library:

  • reader: ReaderIOOption adds IO and Option capabilities
  • readerio: ReaderIOOption adds Option capability
  • readeroption: ReaderIOOption adds IO capability
  • iooption: ReaderIOOption adds Reader capability
  • option: ReaderIOOption adds Reader and IO capabilities

Type Safety

The type system ensures:

  • Environment dependencies are explicit in the type signature
  • Side effects are tracked through the IO layer
  • Optional results are handled explicitly
  • Composition maintains type safety

Performance Considerations

ReaderIOOption computations are lazy and only execute when:

  1. An environment is provided (Reader layer)
  2. The IO action is invoked (IO layer)

This allows for efficient composition without premature execution.

package readeriooption provides a monad transformer that combines the Reader and Option monads.

Fantasy Land Specification

This is a monad transformer combining:

Implemented Fantasy Land algebras:

ReaderIOOption[R, A] represents a computation that:

  • Depends on a shared environment of type R (Reader monad)
  • May fail to produce a value of type A (Option monad)

This is useful for computations that need access to configuration, context, or dependencies while also being able to represent the absence of a value without using errors.

The ReaderIOOption monad is defined as: Reader[R, Option[A]]

Key operations:

  • Of: Wraps a value in a ReaderIOOption
  • None: Creates a ReaderIOOption representing no value
  • Map: Transforms the value inside a ReaderIOOption
  • Chain: Sequences ReaderIOOption computations
  • Ask/Asks: Accesses the environment

Example:

type Config struct {
    DatabaseURL string
    Timeout     int
}

// A computation that may or may not find a user
func findUser(id int) readeriooption.ReaderIOOption[Config, User] {
    return readeriooption.Asks(func(cfg Config) option.Option[User] {
        // Use cfg.DatabaseURL to query database
        // Return Some(user) if found, None() if not found
    })
}

// Chain multiple operations
result := F.Pipe2(
    findUser(123),
    readeriooption.Chain(func(user User) readeriooption.ReaderIOOption[Config, Profile] {
        return loadProfile(user.ProfileID)
    }),
    readeriooption.Map(func(profile Profile) string {
        return profile.DisplayName
    }),
)

// Execute with config
config := Config{DatabaseURL: "localhost:5432", Timeout: 30}
displayName := result(config) // Returns Option[string]

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AlternativeMonoid

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

AlternativeMonoid creates a Monoid for ReaderIOOption that combines both Alternative and Applicative behavior. It uses the provided monoid for the success values and falls back to alternative computations on failure.

The empty element is Of(m.Empty()), and concat tries the first computation, falling back to the second if it fails (returns None), then combines successful values using the underlying monoid.

This is particularly useful when you want to:

  • Try multiple computations and accumulate their results
  • Provide fallback behavior when computations fail
  • Combine results from computations that may or may not succeed

The behavior differs from ApplicativeMonoid in that it provides fallback semantics:

  • If the first computation succeeds, use its value
  • If the first fails but the second succeeds, use the second's value
  • If both succeed, combine their values using the underlying monoid
  • If both fail, the result is None

The resulting monoid satisfies the monoid laws:

  • Left identity: Concat(Empty(), x) = x
  • Right identity: Concat(x, Empty()) = x
  • Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z))

Parameters:

  • m: The underlying monoid for combining success values of type A

Returns:

  • A Monoid[ReaderIOOption[R, A]] that combines ReaderIOOption computations with fallback

Example:

import (
    N "github.com/IBM/fp-go/v2/number"
    RO "github.com/IBM/fp-go/v2/readeroption"
)

// Create a monoid for integer addition with alternative behavior
intAdd := N.MonoidSum[int]()
roMonoid := RO.AlternativeMonoid[Config](intAdd)

// Combine successful computations
ro1 := RO.Of[Config](5)
ro2 := RO.Of[Config](3)
combined := roMonoid.Concat(ro1, ro2)
// combined(cfg) returns option.Some(8)

// Fallback when first fails
ro3 := RO.None[Config, int]()
ro4 := RO.Of[Config](10)
withFallback := roMonoid.Concat(ro3, ro4)
// withFallback(cfg) returns option.Some(10)

// Use first success when available
withFirst := roMonoid.Concat(ro1, ro3)
// withFirst(cfg) returns option.Some(5)

// Accumulate multiple values with some failures
result := roMonoid.Concat(
    roMonoid.Concat(ro3, ro1),  // None + 5 = 5
    ro2,                         // 5 + 3 = 8
)
// result(cfg) returns option.Some(8)

func ApplicativeMonoid

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

ApplicativeMonoid creates a Monoid for ReaderIOOption based on Applicative functor composition. The empty element is Of(m.Empty()), and concat combines two computations using the underlying monoid. Both computations must succeed (return Some) for the result to succeed.

This is useful for accumulating results from multiple independent computations that all need to succeed. If any computation returns None, the entire result is None.

The resulting monoid satisfies the monoid laws:

  • Left identity: Concat(Empty(), x) = x
  • Right identity: Concat(x, Empty()) = x
  • Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z))

Parameters:

  • m: The underlying monoid for combining success values of type A

Returns:

  • A Monoid[ReaderIOOption[R, A]] that combines ReaderIOOption computations

Example:

import (
    N "github.com/IBM/fp-go/v2/number"
    RO "github.com/IBM/fp-go/v2/readeroption"
)

// Create a monoid for integer addition
intAdd := N.MonoidSum[int]()
roMonoid := RO.ApplicativeMonoid[Config](intAdd)

// Combine successful computations
ro1 := RO.Of[Config](5)
ro2 := RO.Of[Config](3)
combined := roMonoid.Concat(ro1, ro2)
// combined(cfg) returns option.Some(8)

// If either fails, the whole computation fails
ro3 := RO.None[Config, int]()
failed := roMonoid.Concat(ro1, ro3)
// failed(cfg) returns option.None[int]()

// Empty element is the identity
withEmpty := roMonoid.Concat(ro1, roMonoid.Empty())
// withEmpty(cfg) returns option.Some(5)

func Fold

func Fold[R, A, B any](onNone Reader[R, B], onRight reader.Kleisli[R, A, B]) reader.Operator[R, Option[A], B]

Fold extracts the value from a ReaderIOOption by providing handlers for both cases. The onNone handler is called if the computation returns None. The onRight handler is called if the computation returns Some(a).

Example:

result := readeroption.Fold(
    func() reader.Reader[Config, string] { return reader.Of[Config]("not found") },
    func(user User) reader.Reader[Config, string] { return reader.Of[Config](user.Name) },
)(findUser(123))

func GetOrElse

func GetOrElse[R, A any](onNone Reader[R, A]) reader.Operator[R, Option[A], A]

GetOrElse returns the value from a ReaderIOOption, or a default value if it's None.

Example:

result := readeroption.GetOrElse(
    func() reader.Reader[Config, User] { return reader.Of[Config](defaultUser) },
)(findUser(123))

func Local

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

Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s `contramap`).

This allows you to transform the environment before passing it to a computation.

Example:

type GlobalConfig struct { DB DBConfig }
type DBConfig struct { Host string }

// A computation that needs DBConfig
query := func(cfg DBConfig) option.Option[User] { ... }

// Transform GlobalConfig to DBConfig
result := readeroption.Local(func(g GlobalConfig) DBConfig { return g.DB })(
    readeroption.Asks(query),
)

func Read

func Read[A, R any](e R) func(ReaderIOOption[R, A]) IOOption[A]

Read applies a context to a reader to obtain its value. This executes the ReaderIOOption computation with the given environment.

Example:

ro := readeroption.Of[Config](42)
result := readeroption.Read[int](myConfig)(ro) // Returns option.Some(42)

func TraverseArrayWithIndex

func TraverseArrayWithIndex[E, A, B any](f func(int, A) ReaderIOOption[E, B]) func([]A) ReaderIOOption[E, []B]

TraverseArrayWithIndex is like TraverseArray but the function also receives the index of each element.

Example:

type DB struct { ... }

processWithIndex := func(idx int, value string) readeroption.ReaderIOOption[DB, Result] {
    // Use idx in processing
    return readeroption.Asks(func(db DB) option.Option[Result] { ... })
}

values := []string{"a", "b", "c"}
result := readeroption.TraverseArrayWithIndex[DB](processWithIndex)(values)

Types

type Either

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

Either represents a value of one of two possible types (a disjoint union). An instance of Either is either Left (representing an error) or Right (representing a success).

type IOOption

type IOOption[A any] = iooption.IOOption[A]

IOOption represents an IO computation that may produce a value of type A. It combines IO effects with the Option monad for optional values.

type Kleisli

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

Kleisli represents a function that takes a value A and returns a ReaderIOOption[R, B]. This is the type of functions used with Chain/Bind operations, enabling monadic composition.

func Contramap

func Contramap[A, R1, R2 any](f func(R2) R1) Kleisli[R2, ReaderIOOption[R1, A], A]

Contramap changes the value of the local environment during the execution of a ReaderIOOption. This is the contravariant functor operation that transforms the input environment.

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

Contramap is useful for adapting a ReaderIOOption to work with a different environment type by providing a function that converts the new environment to the expected one.

Type Parameters:

  • A: The value type (unchanged)
  • R2: The new input environment type
  • R1: The original environment type expected by the ReaderIOOption

Parameters:

  • f: Function to transform the environment from R2 to R1

Returns:

  • A Kleisli arrow that takes a ReaderIOOption[R1, A] and returns a ReaderIOOption[R2, A]

func FromPredicate

func FromPredicate[R, A any](pred Predicate[A]) Kleisli[R, A, A]

FromPredicate creates a Kleisli arrow that filters a value based on a predicate. If the predicate returns true, the value is wrapped in Some; otherwise, None is returned.

Example:

isPositive := readeroption.FromPredicate[Config](N.MoreThan(0))
result := F.Pipe1(
    readeroption.Of[Config](42),
    readeroption.Chain(isPositive),
)

func Promap

func Promap[R, A, D, B any](f func(D) R, g func(A) B) Kleisli[D, ReaderIOOption[R, A], B]

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

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

This operation allows you to:

  • Adapt the environment type before passing it to the ReaderIOOption (via f)
  • Transform the Some value after the computation completes (via g)

The None case remains unchanged through the transformation.

Type Parameters:

  • R: The original environment type expected by the ReaderIOOption
  • A: The original value type produced by the ReaderIOOption
  • D: The new input environment type
  • B: The new output value type

Parameters:

  • f: Function to transform the input environment from D to R (contravariant)
  • g: Function to transform the output Some value from A to B (covariant)

Returns:

  • A Kleisli arrow that takes a ReaderIOOption[R, A] and returns a ReaderIOOption[D, B]

func TailRec

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

func TraverseArray

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

TraverseArray transforms an array by applying a function that returns a ReaderIOOption to each element. If any element results in None, the entire result is None. Otherwise, returns Some containing an array of all the unwrapped values.

This is useful for performing a sequence of operations that may fail on each element of an array, where you want all operations to succeed or the entire computation to fail.

Example:

type DB struct { ... }

findUser := func(id int) readeroption.ReaderIOOption[DB, User] { ... }

userIDs := []int{1, 2, 3}
result := F.Pipe1(
    readeroption.Of[DB](userIDs),
    readeroption.Chain(readeroption.TraverseArray[DB](findUser)),
)
// result will be Some([]User) if all users are found, None otherwise

type Lazy

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

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

type Operator

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

Operator represents a function that transforms one ReaderIOOption into another. It takes a ReaderIOOption[R, A] and produces a ReaderIOOption[R, B]. This is commonly used for lifting functions into the ReaderIOOption context.

func Alt

func Alt[R, A any](second Lazy[ReaderIOOption[R, A]]) Operator[R, A, A]

Alt returns a function that provides an alternative ReaderIOOption if the first one returns None. This is the curried version of MonadAlt, useful for composition with F.Pipe.

Example:

result := F.Pipe1(
    findUserInCache(123),
    readeroption.Alt(findUserInDB(123)),
)

func Ap

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

Ap returns a function that applies a function wrapped in a ReaderIOOption to a value. This is the curried version of MonadAp.

func ApS

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

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

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

Example:

type State struct {
    User   User
    Config Config
}
type Env struct {
    UserService   UserService
    ConfigService ConfigService
}

// These operations are independent and can be combined with ApS
getUser := readereither.Asks(func(env Env) either.Either[error, User] {
    return env.UserService.GetUser()
})
getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
    return env.ConfigService.GetConfig()
})

result := F.Pipe2(
    readereither.Do[Env, error](State{}),
    readereither.ApS(
        func(user User) func(State) State {
            return func(s State) State { s.User = user; return s }
        },
        getUser,
    ),
    readereither.ApS(
        func(cfg Config) func(State) State {
            return func(s State) State { s.Config = cfg; return s }
        },
        getConfig,
    ),
)

func ApSL

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

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

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

Example:

type State struct {
    User   User
    Config Config
}
type Env struct {
    UserService   UserService
    ConfigService ConfigService
}

configLens := lens.MakeLens(
    func(s State) Config { return s.Config },
    func(s State, c Config) State { s.Config = c; return s },
)

getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
    return env.ConfigService.GetConfig()
})
result := F.Pipe2(
    readereither.Of[Env, error](State{}),
    readereither.ApSL(configLens, getConfig),
)

func Bind

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

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

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

Example:

type State struct {
    User   User
    Config Config
}
type Env struct {
    UserService   UserService
    ConfigService ConfigService
}

result := F.Pipe2(
    readereither.Do[Env, error](State{}),
    readereither.Bind(
        func(user User) func(State) State {
            return func(s State) State { s.User = user; return s }
        },
        func(s State) readereither.ReaderIOOption[Env, error, User] {
            return readereither.Asks(func(env Env) either.Either[error, User] {
                return env.UserService.GetUser()
            })
        },
    ),
    readereither.Bind(
        func(cfg Config) func(State) State {
            return func(s State) State { s.Config = cfg; return s }
        },
        func(s State) readereither.ReaderIOOption[Env, error, Config] {
            // This can access s.User from the previous step
            return readereither.Asks(func(env Env) either.Either[error, Config] {
                return env.ConfigService.GetConfigForUser(s.User.ID)
            })
        },
    ),
)

func 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 ReaderIOOption computation that produces an updated value.

Example:

type State struct {
    User   User
    Config Config
}
type Env struct {
    UserService   UserService
    ConfigService ConfigService
}

userLens := lens.MakeLens(
    func(s State) User { return s.User },
    func(s State, u User) State { s.User = u; return s },
)

result := F.Pipe2(
    readereither.Do[Env, error](State{}),
    readereither.BindL(userLens, func(user User) readereither.ReaderIOOption[Env, error, User] {
        return readereither.Asks(func(env Env) either.Either[error, User] {
            return env.UserService.GetUser()
        })
    }),
)

func BindTo

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

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

func Chain

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

Chain returns a function that sequences ReaderIOOption computations. This is the curried version of MonadChain, useful for composition with F.Pipe.

Example:

result := F.Pipe1(
    findUser(123),
    readeroption.Chain(loadProfile),
)

func ChainOptionK

func ChainOptionK[R, A, B any](f O.Kleisli[A, B]) Operator[R, A, B]

ChainOptionK returns a function that chains a ReaderIOOption with a function returning an Option. This is the curried version of MonadChainOptionK.

Example:

parseAge := func(s string) option.Option[int] { ... }
result := F.Pipe1(
    readeroption.Of[Config]("25"),
    readeroption.ChainOptionK[Config](parseAge),
)

func Flap

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

Flap returns a function that applies a value to a function wrapped in a ReaderIOOption. This is the curried version of MonadFlap.

func Let

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

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

func LetL

func LetL[R, S, T any](
	lens L.Lens[S, T],
	f 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 ReaderIOOption).

Example:

type State struct {
    User   User
    Config Config
}

configLens := lens.MakeLens(
    func(s State) Config { return s.Config },
    func(s State, c Config) State { s.Config = c; return s },
)

result := F.Pipe2(
    readereither.Do[any, error](State{Config: Config{Host: "localhost"}}),
    readereither.LetL(configLens, func(cfg Config) Config {
        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 the a value to a context [S1] to produce a context [S2]

func LetToL

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

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

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

Example:

type State struct {
    User   User
    Config Config
}

configLens := lens.MakeLens(
    func(s State) Config { return s.Config },
    func(s State, c Config) State { s.Config = c; return s },
)

newConfig := Config{Host: "localhost", Port: 8080}
result := F.Pipe2(
    readereither.Do[any, error](State{}),
    readereither.LetToL(configLens, newConfig),
)

func Map

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

Map returns a function that applies a transformation to the value inside a ReaderIOOption. This is the curried version of MonadMap, useful for composition with F.Pipe.

Example:

doubled := F.Pipe1(
    readeroption.Of[Config](42),
    readeroption.Map[Config](N.Mul(2)),
)

type Option

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

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

type Predicate

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

Predicate represents a function that tests a value of type A and returns a boolean. It's commonly used for filtering and conditional operations.

type Reader

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

Reader represents a computation that depends on an environment R and produces a value A.

type ReaderIO

type ReaderIO[R, A any] = readerio.ReaderIO[R, A]

ReaderIO represents a computation that depends on an environment R and performs IO to produce a value A. It combines the Reader monad (for dependency injection) with IO effects.

func MonadFold

func MonadFold[R, A, B any](fa ReaderIOOption[R, A], onNone ReaderIO[R, B], onRight readerio.Kleisli[R, A, B]) ReaderIO[R, B]

MonadFold extracts the value from a ReaderIOOption by providing handlers for both cases. This is the non-curried version of Fold. The onNone handler is called if the computation returns None. The onRight handler is called if the computation returns Some(a).

Example:

result := readeroption.MonadFold(
    findUser(123),
    reader.Of[Config]("not found"),
    func(user User) reader.Reader[Config, string] { return reader.Of[Config](user.Name) },
)

type ReaderIOOption

type ReaderIOOption[R, A any] = Reader[R, IOOption[A]]

ReaderIOOption represents a computation that depends on an environment R and may produce a value A. It combines the Reader monad (for dependency injection) with IO effects and the Option monad (for optional values). This is the main type of this package, defined as Reader[R, IOOption[A]].

func Ask

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

Ask retrieves the current environment as a ReaderIOOption. This always succeeds and returns Some(environment).

Example:

getConfig := readeroption.Ask[Config]()
result := getConfig(myConfig) // Returns option.Some(myConfig)

func Asks

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

Asks creates a ReaderIOOption that applies a function to the environment. This always succeeds and returns Some(f(environment)).

Example:

getTimeout := readeroption.Asks(func(cfg Config) int { return cfg.Timeout })
result := getTimeout(myConfig) // Returns option.Some(myConfig.Timeout)

func Do

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

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

Example:

type State struct {
    User   User
    Config Config
}
type Env struct {
    UserService   UserService
    ConfigService ConfigService
}
result := readereither.Do[Env, error](State{})

func Flatten

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

Flatten removes one level of nesting from a ReaderIOOption. Converts ReaderIOOption[R, ReaderIOOption[R, A]] to ReaderIOOption[R, A].

Example:

nested := readeroption.Of[Config](readeroption.Of[Config](42))
flattened := readeroption.Flatten(nested)

func FromOption

func FromOption[R, A any](t Option[A]) ReaderIOOption[R, A]

FromOption lifts an Option[A] into a ReaderIOOption[R, A]. The resulting computation ignores the environment and returns the given option.

func FromReader

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

FromReader lifts a Reader[R, A] into a ReaderIOOption[R, A]. The resulting computation always succeeds (returns Some).

func MonadAlt

func MonadAlt[R, A any](first ReaderIOOption[R, A], second Lazy[ReaderIOOption[R, A]]) ReaderIOOption[R, A]

MonadAlt provides an alternative ReaderIOOption if the first one returns None. If fa returns Some(a), that value is returned; otherwise, the alternative computation is executed. This is useful for providing fallback behavior.

Example:

primary := findUserInCache(123)
fallback := findUserInDB(123)
result := readeroption.MonadAlt(primary, fallback)

func MonadAp

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

MonadAp applies a function wrapped in a ReaderIOOption to a value wrapped in a ReaderIOOption. Both computations are executed with the same environment. If either computation returns None, the result is None.

func MonadChain

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

MonadChain sequences two ReaderIOOption computations, where the second depends on the result of the first. If the first computation returns None, the second is not executed.

Example:

findUser := func(id int) readeroption.ReaderIOOption[DB, User] { ... }
loadProfile := func(user User) readeroption.ReaderIOOption[DB, Profile] { ... }
result := readeroption.MonadChain(findUser(123), loadProfile)

func MonadChainOptionK

func MonadChainOptionK[R, A, B any](ma ReaderIOOption[R, A], f O.Kleisli[A, B]) ReaderIOOption[R, B]

MonadChainOptionK chains a ReaderIOOption with a function that returns an Option. This is useful for integrating functions that return Option directly.

Example:

parseAge := func(s string) option.Option[int] { ... }
result := readeroption.MonadChainOptionK(
    readeroption.Of[Config]("25"),
    parseAge,
)

func MonadFlap

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

MonadFlap applies a value to a function wrapped in a ReaderIOOption. This is the reverse of MonadAp.

func MonadMap

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

MonadMap applies a function to the value inside a ReaderIOOption. If the ReaderIOOption contains None, the function is not applied.

Example:

ro := readeroption.Of[Config](42)
doubled := readeroption.MonadMap(ro, N.Mul(2))

func None

func None[R, A any]() ReaderIOOption[R, A]

None creates a ReaderIOOption representing a failed computation. The resulting computation ignores the environment and returns None.

Example:

ro := readeroption.None[Config, int]()
result := ro(config) // Returns option.None[int]()

func Of

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

Of wraps a value in a ReaderIOOption, representing a successful computation. The resulting computation ignores the environment and returns Some(a).

Example:

ro := readeroption.Of[Config](42)
result := ro(config) // Returns option.Some(42)

func SequenceT1

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

SequenceT1 converts a single ReaderIOOption into a ReaderIOOption of a 1-tuple. This is mainly useful for consistency with the other SequenceT functions.

Example:

type Config struct { ... }

user := readeroption.Of[Config](User{Name: "Alice"})
result := readeroption.SequenceT1(user)
// result(config) returns option.Some(tuple.MakeTuple1(User{Name: "Alice"}))

func SequenceT2

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

SequenceT2 combines two ReaderIOOption values into a ReaderIOOption of a 2-tuple. If either input is None, the result is None.

Example:

type Config struct { ... }

user := readeroption.Of[Config](User{Name: "Alice"})
count := readeroption.Of[Config](42)

result := readeroption.SequenceT2(user, count)
// result(config) returns option.Some(tuple.MakeTuple2(User{Name: "Alice"}, 42))

noneUser := readeroption.None[Config, User]()
result2 := readeroption.SequenceT2(noneUser, count)
// result2(config) returns option.None[tuple.Tuple2[User, int]]()

func SequenceT3

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

SequenceT3 combines three ReaderIOOption values into a ReaderIOOption of a 3-tuple. If any input is None, the result is None.

Example:

type Config struct { ... }

user := readeroption.Of[Config](User{Name: "Alice"})
count := readeroption.Of[Config](42)
active := readeroption.Of[Config](true)

result := readeroption.SequenceT3(user, count, active)
// result(config) returns option.Some(tuple.MakeTuple3(User{Name: "Alice"}, 42, true))

func SequenceT4

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

SequenceT4 combines four ReaderIOOption values into a ReaderIOOption of a 4-tuple. If any input is None, the result is None.

Example:

type Config struct { ... }

user := readeroption.Of[Config](User{Name: "Alice"})
count := readeroption.Of[Config](42)
active := readeroption.Of[Config](true)
score := readeroption.Of[Config](95.5)

result := readeroption.SequenceT4(user, count, active, score)
// result(config) returns option.Some(tuple.MakeTuple4(User{Name: "Alice"}, 42, true, 95.5))

func Some

func Some[R, A any](r A) ReaderIOOption[R, A]

Some wraps a value in a ReaderIOOption, representing a successful computation. This is equivalent to Of but more explicit about the Option semantics.

func SomeReader

func SomeReader[R, A any](r Reader[R, A]) ReaderIOOption[R, A]

SomeReader lifts a Reader[R, A] into a ReaderIOOption[R, A]. The resulting computation always succeeds (returns Some).

Jump to

Keyboard shortcuts

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