1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00

fix: document statereaderioeither

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-11-18 16:06:56 +01:00
parent 77dde302ef
commit 6d94697128
6 changed files with 1084 additions and 13 deletions

View File

@@ -3,7 +3,8 @@
"allow": [
"Bash(ls -la \"c:\\d\\fp-go\\v2\\internal\\monad\"\" && ls -la \"c:dfp-gov2internalapplicative\"\")",
"Bash(ls -la \"c:\\d\\fp-go\\v2\\internal\\chain\"\" && ls -la \"c:dfp-gov2internalfunctor\"\")",
"Bash(go build:*)"
"Bash(go build:*)",
"Bash(go test:*)"
],
"deny": [],
"ask": []

View File

@@ -1,3 +1,18 @@
// Copyright (c) 2024 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statereaderioeither
import (
@@ -7,6 +22,22 @@ import (
F "github.com/IBM/fp-go/v2/internal/functor"
)
// Do starts a do-notation chain for building computations in a fluent style.
// This is typically used with Bind, Let, and other combinators to compose
// stateful, context-dependent computations that can fail.
//
// Example:
//
// type State struct {
// name string
// age int
// }
// result := function.Pipe2(
// statereaderioeither.Do[AppState, Config, error](State{}),
// statereaderioeither.Bind(...),
// statereaderioeither.Let(...),
// )
//
//go:inline
func Do[ST, R, E, A any](
empty A,
@@ -14,6 +45,23 @@ func Do[ST, R, E, A any](
return Of[ST, R, E](empty)
}
// Bind executes a computation and binds its result to a field in the accumulator state.
// This is used in do-notation to sequence dependent computations.
//
// Example:
//
// result := function.Pipe2(
// statereaderioeither.Do[AppState, Config, error](State{}),
// statereaderioeither.Bind(
// func(name string) func(State) State {
// return func(s State) State { return State{name: name, age: s.age} }
// },
// func(s State) statereaderioeither.StateReaderIOEither[AppState, Config, error, string] {
// return statereaderioeither.Of[AppState, Config, error]("John")
// },
// ),
// )
//
//go:inline
func Bind[ST, R, E, S1, S2, T any](
setter func(T) func(S1) S2,
@@ -27,6 +75,21 @@ func Bind[ST, R, E, S1, S2, T any](
)
}
// Let computes a derived value and binds it to a field in the accumulator state.
// Unlike Bind, this does not execute a monadic computation, just a pure function.
//
// Example:
//
// result := function.Pipe2(
// statereaderioeither.Do[AppState, Config, error](State{age: 25}),
// statereaderioeither.Let(
// func(isAdult bool) func(State) State {
// return func(s State) State { return State{age: s.age, isAdult: isAdult} }
// },
// func(s State) bool { return s.age >= 18 },
// ),
// )
//
//go:inline
func Let[ST, R, E, S1, S2, T any](
key func(T) func(S1) S2,
@@ -39,6 +102,20 @@ func Let[ST, R, E, S1, S2, T any](
)
}
// LetTo binds a constant value to a field in the accumulator state.
//
// Example:
//
// result := function.Pipe2(
// statereaderioeither.Do[AppState, Config, error](State{}),
// statereaderioeither.LetTo(
// func(status string) func(State) State {
// return func(s State) State { return State{...s, status: status} }
// },
// "active",
// ),
// )
//
//go:inline
func LetTo[ST, R, E, S1, S2, T any](
key func(T) func(S1) S2,
@@ -51,6 +128,16 @@ func LetTo[ST, R, E, S1, S2, T any](
)
}
// BindTo wraps a value in a simple constructor, typically used to start a do-notation chain
// after getting an initial value.
//
// Example:
//
// result := function.Pipe2(
// statereaderioeither.Of[AppState, Config, error](42),
// statereaderioeither.BindTo[AppState, Config, error](func(x int) State { return State{value: x} }),
// )
//
//go:inline
func BindTo[ST, R, E, S1, T any](
setter func(T) S1,
@@ -61,6 +148,9 @@ func BindTo[ST, R, E, S1, T any](
)
}
// ApS applies a computation in sequence and binds the result to a field.
// This is the applicative version of Bind.
//
//go:inline
func ApS[ST, R, E, S1, S2, T any](
setter func(T) func(S1) S2,
@@ -74,6 +164,9 @@ func ApS[ST, R, E, S1, S2, T any](
)
}
// ApSL is a lens-based variant of ApS for working with nested structures.
// It uses a lens to focus on a specific field in the state.
//
//go:inline
func ApSL[ST, R, E, S, T any](
lens Lens[S, T],
@@ -82,6 +175,9 @@ func ApSL[ST, R, E, S, T any](
return ApS(lens.Set, fa)
}
// BindL is a lens-based variant of Bind for working with nested structures.
// It uses a lens to focus on a specific field in the state.
//
//go:inline
func BindL[ST, R, E, S, T any](
lens Lens[S, T],
@@ -90,6 +186,9 @@ func BindL[ST, R, E, S, T any](
return Bind(lens.Set, function.Flow2(lens.Get, f))
}
// LetL is a lens-based variant of Let for working with nested structures.
// It uses a lens to focus on a specific field in the state.
//
//go:inline
func LetL[ST, R, E, S, T any](
lens Lens[S, T],
@@ -98,6 +197,9 @@ func LetL[ST, R, E, S, T any](
return Let[ST, R, E](lens.Set, function.Flow2(lens.Get, f))
}
// LetToL is a lens-based variant of LetTo for working with nested structures.
// It uses a lens to focus on a specific field in the state.
//
//go:inline
func LetToL[ST, R, E, S, T any](
lens Lens[S, T],

View File

@@ -0,0 +1,132 @@
// Copyright (c) 2024 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package statereaderioeither provides a functional programming abstraction that combines
// four powerful concepts: State, Reader, IO, and Either monads.
//
// # StateReaderIOEither
//
// StateReaderIOEither[S, R, E, A] represents a computation that:
// - Manages state of type S (State)
// - Depends on some context/environment of type R (Reader)
// - Performs side effects (IO)
// - Can fail with an error of type E or succeed with a value of type A (Either)
//
// The type is defined as:
//
// StateReaderIOEither[S, R, E, A] = Reader[S, ReaderIOEither[R, E, Pair[S, A]]]
//
// This is particularly useful for:
// - Stateful computations with dependency injection
// - Error handling in effectful computations with state
// - Composing operations that need access to shared configuration, manage state, and can fail
//
// # Core Operations
//
// Construction:
// - Of/Right: Create a successful computation with a value
// - Left: Create a failed computation with an error
// - FromState: Lift a State into StateReaderIOEither
// - FromReader: Lift a Reader into StateReaderIOEither
// - FromIO: Lift an IO into StateReaderIOEither
// - FromEither: Lift an Either into StateReaderIOEither
// - FromIOEither: Lift an IOEither into StateReaderIOEither
// - FromReaderEither: Lift a ReaderEither into StateReaderIOEither
// - FromReaderIOEither: Lift a ReaderIOEither into StateReaderIOEither
//
// Transformation:
// - Map: Transform the success value
// - Chain: Sequence dependent computations (monadic bind)
// - Flatten: Flatten nested StateReaderIOEither
//
// Combination:
// - Ap: Apply a function in a context to a value in a context
//
// Context Access:
// - Asks: Get a value derived from the context
// - Local: Run a computation with a modified context
//
// Kleisli Arrows:
// - FromEitherK: Lift an Either-returning function to a Kleisli arrow
// - FromIOK: Lift an IO-returning function to a Kleisli arrow
// - FromIOEitherK: Lift an IOEither-returning function to a Kleisli arrow
// - FromReaderIOEitherK: Lift a ReaderIOEither-returning function to a Kleisli arrow
// - ChainEitherK: Chain with an Either-returning function
// - ChainIOEitherK: Chain with an IOEither-returning function
// - ChainReaderIOEitherK: Chain with a ReaderIOEither-returning function
//
// Do Notation (Monadic Composition):
// - Do: Start a do-notation chain
// - Bind: Bind a value from a computation
// - BindTo: Bind a value to a simple constructor
// - Let: Compute a derived value
// - LetTo: Set a constant value
// - ApS: Apply in sequence (for applicative composition)
// - BindL/ApSL/LetL/LetToL: Lens-based variants for working with nested structures
//
// # Example Usage
//
// type Config struct {
// DatabaseURL string
// MaxRetries int
// }
//
// type AppState struct {
// RequestCount int
// LastError error
// }
//
// // A computation that manages state, depends on config, performs IO, and can fail
// func processRequest(data string) statereaderioeither.StateReaderIOEither[AppState, Config, error, string] {
// return func(state AppState) readerioeither.ReaderIOEither[Config, error, pair.Pair[AppState, string]] {
// return func(cfg Config) ioeither.IOEither[error, pair.Pair[AppState, string]] {
// return func() either.Either[error, pair.Pair[AppState, string]] {
// // Use cfg.DatabaseURL and cfg.MaxRetries
// // Update state.RequestCount
// // Perform IO operations
// // Return either.Right(pair.MakePair(newState, result)) or either.Left(err)
// newState := AppState{RequestCount: state.RequestCount + 1}
// return either.Right(pair.MakePair(newState, "processed: " + data))
// }
// }
// }
// }
//
// // Compose operations using do-notation
// result := function.Pipe3(
// statereaderioeither.Do[AppState, Config, error](State{}),
// statereaderioeither.Bind(
// func(result string) func(State) State { return func(s State) State { return State{result: result} } },
// func(s State) statereaderioeither.StateReaderIOEither[AppState, Config, error, string] {
// return processRequest(s.input)
// },
// ),
// statereaderioeither.Map[AppState, Config, error](func(s State) string { return s.result }),
// )
//
// // Execute with initial state and config
// initialState := AppState{RequestCount: 0}
// config := Config{DatabaseURL: "postgres://localhost", MaxRetries: 3}
// outcome := result(initialState)(config)() // Returns either.Either[error, pair.Pair[AppState, string]]
//
// # Monad Laws
//
// StateReaderIOEither satisfies the monad laws:
// - Left Identity: Of(a) >>= f ≡ f(a)
// - Right Identity: m >>= Of ≡ m
// - Associativity: (m >>= f) >>= g ≡ m >>= (x => f(x) >>= g)
//
// These laws are verified in the testing subpackage.
package statereaderioeither

View File

@@ -23,18 +23,51 @@ import (
"github.com/IBM/fp-go/v2/readerioeither"
)
// Left creates a StateReaderIOEither that represents a failed computation with the given error.
// The error value is immediately available and does not depend on state or context.
//
// Example:
//
// result := statereaderioeither.Left[AppState, Config, string](errors.New("validation failed"))
// // Returns a failed computation that ignores state and context
func Left[S, R, A, E any](e E) StateReaderIOEither[S, R, E, A] {
return function.Constant1[S](readerioeither.Left[R, Pair[S, A]](e))
}
// Right creates a StateReaderIOEither that represents a successful computation with the given value.
// The value is wrapped and the state is passed through unchanged.
//
// Example:
//
// result := statereaderioeither.Right[AppState, Config, error](42)
// // Returns a successful computation containing 42
func Right[S, R, E, A any](a A) StateReaderIOEither[S, R, E, A] {
return statet.Of[StateReaderIOEither[S, R, E, A]](readerioeither.Of[R, E, Pair[S, A]], a)
}
// Of creates a StateReaderIOEither that represents a successful computation with the given value.
// This is the monadic return/pure operation for StateReaderIOEither.
// Equivalent to [Right].
//
// Example:
//
// result := statereaderioeither.Of[AppState, Config, error](42)
// // Returns a successful computation containing 42
func Of[S, R, E, A any](a A) StateReaderIOEither[S, R, E, A] {
return Right[S, R, E](a)
}
// MonadMap transforms the success value of a StateReaderIOEither using the provided function.
// If the computation fails, the error is propagated unchanged.
// The state is threaded through the computation.
// This is the functor map operation.
//
// Example:
//
// result := statereaderioeither.MonadMap(
// statereaderioeither.Of[AppState, Config, error](21),
// func(x int) int { return x * 2 },
// ) // Result contains 42
func MonadMap[S, R, E, A, B any](fa StateReaderIOEither[S, R, E, A], f func(A) B) StateReaderIOEither[S, R, E, B] {
return statet.MonadMap[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]](
readerioeither.MonadMap[R, E, Pair[S, A], Pair[S, B]],
@@ -43,6 +76,13 @@ func MonadMap[S, R, E, A, B any](fa StateReaderIOEither[S, R, E, A], f func(A) B
)
}
// Map is the curried version of [MonadMap].
// Returns a function that transforms a StateReaderIOEither.
//
// Example:
//
// double := statereaderioeither.Map[AppState, Config, error](func(x int) int { return x * 2 })
// result := function.Pipe1(statereaderioeither.Of[AppState, Config, error](21), double)
func Map[S, R, E, A, B any](f func(A) B) Operator[S, R, E, A, B] {
return statet.Map[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]](
readerioeither.Map[R, E, Pair[S, A], Pair[S, B]],
@@ -50,6 +90,18 @@ func Map[S, R, E, A, B any](f func(A) B) Operator[S, R, E, A, B] {
)
}
// MonadChain sequences two computations, passing the result of the first to a function
// that produces the second computation. This is the monadic bind operation.
// The state is threaded through both computations.
//
// Example:
//
// result := statereaderioeither.MonadChain(
// statereaderioeither.Of[AppState, Config, error](5),
// func(x int) statereaderioeither.StateReaderIOEither[AppState, Config, error, string] {
// return statereaderioeither.Of[AppState, Config, error](fmt.Sprintf("value: %d", x))
// },
// )
func MonadChain[S, R, E, A, B any](fa StateReaderIOEither[S, R, E, A], f Kleisli[S, R, E, A, B]) StateReaderIOEither[S, R, E, B] {
return statet.MonadChain(
readerioeither.MonadChain[R, E, Pair[S, A], Pair[S, B]],
@@ -58,6 +110,15 @@ func MonadChain[S, R, E, A, B any](fa StateReaderIOEither[S, R, E, A], f Kleisli
)
}
// Chain is the curried version of [MonadChain].
// Returns a function that sequences computations.
//
// Example:
//
// stringify := statereaderioeither.Chain(func(x int) statereaderioeither.StateReaderIOEither[AppState, Config, error, string] {
// return statereaderioeither.Of[AppState, Config, error](fmt.Sprintf("%d", x))
// })
// result := function.Pipe1(statereaderioeither.Of[AppState, Config, error](42), stringify)
func Chain[S, R, E, A, B any](f Kleisli[S, R, E, A, B]) Operator[S, R, E, A, B] {
return statet.Chain[StateReaderIOEither[S, R, E, A]](
readerioeither.Chain[R, E, Pair[S, A], Pair[S, B]],
@@ -65,6 +126,16 @@ func Chain[S, R, E, A, B any](f Kleisli[S, R, E, A, B]) Operator[S, R, E, A, B]
)
}
// MonadAp applies a function wrapped in a StateReaderIOEither to a value wrapped in a StateReaderIOEither.
// If either the function or the value fails, the error is propagated.
// The state is threaded through both computations sequentially.
// This is the applicative apply operation.
//
// Example:
//
// fab := statereaderioeither.Of[AppState, Config, error](func(x int) int { return x * 2 })
// fa := statereaderioeither.Of[AppState, Config, error](21)
// result := statereaderioeither.MonadAp(fab, fa) // Result contains 42
func MonadAp[B, S, R, E, A any](fab StateReaderIOEither[S, R, E, func(A) B], fa StateReaderIOEither[S, R, E, A]) StateReaderIOEither[S, R, E, B] {
return statet.MonadAp[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]](
readerioeither.MonadMap[R, E, Pair[S, A], Pair[S, B]],
@@ -74,6 +145,8 @@ func MonadAp[B, S, R, E, A any](fab StateReaderIOEither[S, R, E, func(A) B], fa
)
}
// Ap is the curried version of [MonadAp].
// Returns a function that applies a wrapped function to the given wrapped value.
func Ap[B, S, R, E, A any](fa StateReaderIOEither[S, R, E, A]) Operator[S, R, E, func(A) B, B] {
return statet.Ap[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B], StateReaderIOEither[S, R, E, func(A) B]](
readerioeither.Map[R, E, Pair[S, A], Pair[S, B]],
@@ -82,6 +155,13 @@ func Ap[B, S, R, E, A any](fa StateReaderIOEither[S, R, E, A]) Operator[S, R, E,
)
}
// FromReaderIOEither lifts a ReaderIOEither into a StateReaderIOEither.
// The state is passed through unchanged.
//
// Example:
//
// rioe := readerioeither.Of[Config, error](42)
// result := statereaderioeither.FromReaderIOEither[AppState](rioe)
func FromReaderIOEither[S, R, E, A any](fa ReaderIOEither[R, E, A]) StateReaderIOEither[S, R, E, A] {
return statet.FromF[StateReaderIOEither[S, R, E, A]](
readerioeither.MonadMap[R, E, A],
@@ -89,38 +169,74 @@ func FromReaderIOEither[S, R, E, A any](fa ReaderIOEither[R, E, A]) StateReaderI
)
}
// FromReaderEither lifts a ReaderEither into a StateReaderIOEither.
// The state is passed through unchanged.
func FromReaderEither[S, R, E, A any](fa ReaderEither[R, E, A]) StateReaderIOEither[S, R, E, A] {
return FromReaderIOEither[S](readerioeither.FromReaderEither(fa))
}
// FromIOEither lifts an IOEither into a StateReaderIOEither.
// The state is passed through unchanged and the context is ignored.
func FromIOEither[S, R, E, A any](fa IOEither[E, A]) StateReaderIOEither[S, R, E, A] {
return FromReaderIOEither[S](readerioeither.FromIOEither[R](fa))
}
// FromState lifts a State computation into a StateReaderIOEither.
// The computation cannot fail (uses the error type parameter).
func FromState[R, E, S, A any](sa State[S, A]) StateReaderIOEither[S, R, E, A] {
return statet.FromState[StateReaderIOEither[S, R, E, A]](readerioeither.Of[R, E, Pair[S, A]], sa)
}
// FromIO lifts an IO computation into a StateReaderIOEither.
// The state is passed through unchanged and the context is ignored.
func FromIO[S, R, E, A any](fa IO[A]) StateReaderIOEither[S, R, E, A] {
return FromReaderIOEither[S](readerioeither.FromIO[R, E](fa))
}
// FromReader lifts a Reader into a StateReaderIOEither.
// The state is passed through unchanged.
func FromReader[S, E, R, A any](fa Reader[R, A]) StateReaderIOEither[S, R, E, A] {
return FromReaderIOEither[S](readerioeither.FromReader[E](fa))
}
// FromEither lifts an Either into a StateReaderIOEither.
// The state is passed through unchanged and the context is ignored.
//
// Example:
//
// result := statereaderioeither.FromEither[AppState, Config](either.Right[error](42))
func FromEither[S, R, E, A any](ma Either[E, A]) StateReaderIOEither[S, R, E, A] {
return either.MonadFold(ma, Left[S, R, A, E], Right[S, R, E, A])
}
// Combinators
// Local runs a computation with a modified context.
// The function f transforms the context before passing it to the computation.
//
// Example:
//
// // Modify config before running computation
// withTimeout := statereaderioeither.Local[AppState, error, int](
// func(cfg Config) Config { return Config{...cfg, Timeout: 60} }
// )
// result := withTimeout(computation)
func Local[S, E, A, B, R1, R2 any](f func(R2) R1) func(StateReaderIOEither[S, R1, E, A]) StateReaderIOEither[S, R2, E, A] {
return func(ma StateReaderIOEither[S, R1, E, A]) StateReaderIOEither[S, R2, E, A] {
return function.Flow2(ma, readerioeither.Local[E, Pair[S, A]](f))
}
}
// Asks creates a computation that derives a value from the context.
// The function receives the context and returns a StateReaderIOEither.
//
// Example:
//
// getTimeout := statereaderioeither.Asks[AppState, Config, error, int](
// func(cfg Config) statereaderioeither.StateReaderIOEither[AppState, Config, error, int] {
// return statereaderioeither.Of[AppState, Config, error](cfg.Timeout)
// },
// )
func Asks[
S, R, E, A any,
](f func(R) StateReaderIOEither[S, R, E, A]) StateReaderIOEither[S, R, E, A] {
@@ -131,6 +247,15 @@ func Asks[
}
}
// FromEitherK lifts an Either-returning function into a Kleisli arrow for StateReaderIOEither.
//
// Example:
//
// validate := func(x int) either.Either[error, int] {
// if x > 0 { return either.Right[error](x) }
// return either.Left[int](errors.New("negative"))
// }
// kleisli := statereaderioeither.FromEitherK[AppState, Config](validate)
func FromEitherK[S, R, E, A, B any](f either.Kleisli[E, A, B]) Kleisli[S, R, E, A, B] {
return function.Flow2(
f,
@@ -138,6 +263,7 @@ func FromEitherK[S, R, E, A, B any](f either.Kleisli[E, A, B]) Kleisli[S, R, E,
)
}
// FromIOK lifts an IO-returning function into a Kleisli arrow for StateReaderIOEither.
func FromIOK[S, R, E, A, B any](f func(A) IO[B]) Kleisli[S, R, E, A, B] {
return function.Flow2(
f,
@@ -145,6 +271,7 @@ func FromIOK[S, R, E, A, B any](f func(A) IO[B]) Kleisli[S, R, E, A, B] {
)
}
// FromIOEitherK lifts an IOEither-returning function into a Kleisli arrow for StateReaderIOEither.
func FromIOEitherK[
S, R, E, A, B any,
](f ioeither.Kleisli[E, A, B]) Kleisli[S, R, E, A, B] {
@@ -154,6 +281,7 @@ func FromIOEitherK[
)
}
// FromReaderIOEitherK lifts a ReaderIOEither-returning function into a Kleisli arrow for StateReaderIOEither.
func FromReaderIOEitherK[S, R, E, A, B any](f readerioeither.Kleisli[R, E, A, B]) Kleisli[S, R, E, A, B] {
return function.Flow2(
f,
@@ -161,26 +289,32 @@ func FromReaderIOEitherK[S, R, E, A, B any](f readerioeither.Kleisli[R, E, A, B]
)
}
// MonadChainReaderIOEitherK chains a StateReaderIOEither with a ReaderIOEither-returning function.
func MonadChainReaderIOEitherK[S, R, E, A, B any](ma StateReaderIOEither[S, R, E, A], f readerioeither.Kleisli[R, E, A, B]) StateReaderIOEither[S, R, E, B] {
return MonadChain(ma, FromReaderIOEitherK[S](f))
}
// ChainReaderIOEitherK is the curried version of [MonadChainReaderIOEitherK].
func ChainReaderIOEitherK[S, R, E, A, B any](f readerioeither.Kleisli[R, E, A, B]) Operator[S, R, E, A, B] {
return Chain(FromReaderIOEitherK[S](f))
}
// MonadChainIOEitherK chains a StateReaderIOEither with an IOEither-returning function.
func MonadChainIOEitherK[S, R, E, A, B any](ma StateReaderIOEither[S, R, E, A], f ioeither.Kleisli[E, A, B]) StateReaderIOEither[S, R, E, B] {
return MonadChain(ma, FromIOEitherK[S, R](f))
}
// ChainIOEitherK is the curried version of [MonadChainIOEitherK].
func ChainIOEitherK[S, R, E, A, B any](f ioeither.Kleisli[E, A, B]) Operator[S, R, E, A, B] {
return Chain(FromIOEitherK[S, R](f))
}
// MonadChainEitherK chains a StateReaderIOEither with an Either-returning function.
func MonadChainEitherK[S, R, E, A, B any](ma StateReaderIOEither[S, R, E, A], f either.Kleisli[E, A, B]) StateReaderIOEither[S, R, E, B] {
return MonadChain(ma, FromEitherK[S, R](f))
}
// ChainEitherK is the curried version of [MonadChainEitherK].
func ChainEitherK[S, R, E, A, B any](f either.Kleisli[E, A, B]) Operator[S, R, E, A, B] {
return Chain(FromEitherK[S, R](f))
}

View File

@@ -0,0 +1,662 @@
// Copyright (c) 2024 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statereaderioeither
import (
"errors"
"fmt"
"testing"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
N "github.com/IBM/fp-go/v2/number"
P "github.com/IBM/fp-go/v2/pair"
RE "github.com/IBM/fp-go/v2/readereither"
RIOE "github.com/IBM/fp-go/v2/readerioeither"
"github.com/stretchr/testify/assert"
)
type testState struct {
counter int
}
type testContext struct {
multiplier int
}
func TestOf(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := Of[testState, testContext, error](42)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Fold(
func(err error) bool {
t.Fatalf("Expected Right but got Left: %v", err)
return false
},
func(p P.Pair[testState, int]) bool {
assert.Equal(t, 42, P.Tail(p))
assert.Equal(t, 0, P.Head(p).counter) // State unchanged
return true
},
)(res)
}
func TestRight(t *testing.T) {
state := testState{counter: 5}
ctx := testContext{multiplier: 3}
result := Right[testState, testContext, error](100)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 100, P.Tail(p))
assert.Equal(t, 5, P.Head(p).counter)
return p
})(res)
}
func TestLeft(t *testing.T) {
state := testState{counter: 10}
ctx := testContext{multiplier: 2}
testErr := errors.New("test error")
result := Left[testState, testContext, int](testErr)
res := result(state)(ctx)()
assert.True(t, E.IsLeft(res))
}
func TestMonadMap(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := MonadMap(
Of[testState, testContext, error](21),
N.Mul(2),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestMap(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := F.Pipe1(
Of[testState, testContext, error](21),
Map[testState, testContext, error](N.Mul(2)),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestMonadChain(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := MonadChain(
Of[testState, testContext, error](5),
func(x int) StateReaderIOEither[testState, testContext, error, string] {
return Of[testState, testContext, error](fmt.Sprintf("value: %d", x))
},
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "value: 5", P.Tail(p))
return p
})(res)
}
func TestChain(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
result := F.Pipe1(
Of[testState, testContext, error](5),
Chain(func(x int) StateReaderIOEither[testState, testContext, error, string] {
return Of[testState, testContext, error](fmt.Sprintf("value: %d", x))
}),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "value: 5", P.Tail(p))
return p
})(res)
}
func TestMonadAp(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
fab := Of[testState, testContext, error](N.Mul(2))
fa := Of[testState, testContext, error](21)
result := MonadAp(fab, fa)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestAp(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
fa := Of[testState, testContext, error](21)
result := F.Pipe1(
Of[testState, testContext, error](N.Mul(2)),
Ap[int](fa),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
return p
})(res)
}
func TestFromReaderIOEither(t *testing.T) {
state := testState{counter: 5}
ctx := testContext{multiplier: 2}
rioe := RIOE.Of[testContext, error](42)
result := FromReaderIOEither[testState](rioe)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 42, P.Tail(p))
assert.Equal(t, 5, P.Head(p).counter) // State unchanged
return p
})(res)
}
func TestFromReaderEither(t *testing.T) {
state := testState{counter: 7}
ctx := testContext{multiplier: 3}
re := RE.Of[testContext, error](100)
result := FromReaderEither[testState](re)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 100, P.Tail(p))
assert.Equal(t, 7, P.Head(p).counter)
return p
})(res)
}
func TestFromIOEither(t *testing.T) {
state := testState{counter: 3}
ctx := testContext{multiplier: 4}
ioe := IOE.Right[error](55)
result := FromIOEither[testState, testContext](ioe)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 55, P.Tail(p))
assert.Equal(t, 3, P.Head(p).counter)
return p
})(res)
}
func TestFromState(t *testing.T) {
initialState := testState{counter: 10}
ctx := testContext{multiplier: 2}
// State computation that increments counter and returns it
stateComp := func(s testState) P.Pair[testState, int] {
newState := testState{counter: s.counter + 1}
return P.MakePair(newState, newState.counter)
}
result := FromState[testContext, error](stateComp)
res := result(initialState)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 11, P.Tail(p)) // Incremented value
assert.Equal(t, 11, P.Head(p).counter) // State updated
return p
})(res)
}
func TestFromIO(t *testing.T) {
state := testState{counter: 8}
ctx := testContext{multiplier: 2}
ioVal := func() int { return 99 }
result := FromIO[testState, testContext, error](ioVal)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 99, P.Tail(p))
assert.Equal(t, 8, P.Head(p).counter)
return p
})(res)
}
func TestFromReader(t *testing.T) {
state := testState{counter: 6}
ctx := testContext{multiplier: 5}
reader := func(c testContext) int { return c.multiplier * 10 }
result := FromReader[testState, error](reader)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 50, P.Tail(p))
assert.Equal(t, 6, P.Head(p).counter)
return p
})(res)
}
func TestFromEither(t *testing.T) {
state := testState{counter: 12}
ctx := testContext{multiplier: 3}
// Test Right case
resultRight := FromEither[testState, testContext](E.Right[error](42))
resRight := resultRight(state)(ctx)()
assert.True(t, E.IsRight(resRight))
// Test Left case
resultLeft := FromEither[testState, testContext](E.Left[int](errors.New("error")))
resLeft := resultLeft(state)(ctx)()
assert.True(t, E.IsLeft(resLeft))
}
func TestLocal(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
// Create a computation that uses the context
comp := Asks[testState, testContext, error, int](func(c testContext) StateReaderIOEither[testState, testContext, error, int] {
return Of[testState, testContext, error](c.multiplier * 10)
})
// Modify context before running computation
result := Local[testState, error, int, int, testContext, testContext](
func(c testContext) testContext {
return testContext{multiplier: c.multiplier * 2}
},
)(comp)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 40, P.Tail(p)) // (2 * 2) * 10
return p
})(res)
}
func TestAsks(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 7}
result := Asks[testState, testContext, error, int](func(c testContext) StateReaderIOEither[testState, testContext, error, int] {
return Of[testState, testContext, error](c.multiplier * 5)
})
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 35, P.Tail(p))
return p
})(res)
}
func TestFromEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
validate := func(x int) E.Either[error, int] {
if x > 0 {
return E.Right[error](x * 2)
}
return E.Left[int](errors.New("negative"))
}
kleisli := FromEitherK[testState, testContext](validate)
// Test with valid input
resultValid := kleisli(5)
resValid := resultValid(state)(ctx)()
assert.True(t, E.IsRight(resValid))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 10, P.Tail(p))
return p
})(resValid)
// Test with invalid input
resultInvalid := kleisli(-5)
resInvalid := resultInvalid(state)(ctx)()
assert.True(t, E.IsLeft(resInvalid))
}
func TestFromIOK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
ioFunc := func(x int) io.IO[int] {
return func() int { return x * 3 }
}
kleisli := FromIOK[testState, testContext, error](ioFunc)
result := kleisli(7)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 21, P.Tail(p))
return p
})(res)
}
func TestFromIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
ioeFunc := func(x int) IOE.IOEither[error, int] {
if x > 0 {
return IOE.Right[error](x * 4)
}
return IOE.Left[int](errors.New("invalid"))
}
kleisli := FromIOEitherK[testState, testContext](ioeFunc)
result := kleisli(3)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 12, P.Tail(p))
return p
})(res)
}
func TestFromReaderIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
rioeFunc := func(x int) RIOE.ReaderIOEither[testContext, error, int] {
return func(c testContext) IOE.IOEither[error, int] {
return IOE.Right[error](x * c.multiplier)
}
}
kleisli := FromReaderIOEitherK[testState](rioeFunc)
result := kleisli(5)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 10, P.Tail(p))
return p
})(res)
}
func TestChainEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
validate := func(x int) E.Either[error, string] {
if x > 0 {
return E.Right[error](fmt.Sprintf("valid: %d", x))
}
return E.Left[string](errors.New("invalid"))
}
result := F.Pipe1(
Of[testState, testContext, error](42),
ChainEitherK[testState, testContext](validate),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "valid: 42", P.Tail(p))
return p
})(res)
}
func TestChainIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
ioeFunc := func(x int) IOE.IOEither[error, string] {
return IOE.Right[error](fmt.Sprintf("result: %d", x))
}
result := F.Pipe1(
Of[testState, testContext, error](100),
ChainIOEitherK[testState, testContext](ioeFunc),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "result: 100", P.Tail(p))
return p
})(res)
}
func TestChainReaderIOEitherK(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 3}
rioeFunc := func(x int) RIOE.ReaderIOEither[testContext, error, int] {
return func(c testContext) IOE.IOEither[error, int] {
return IOE.Right[error](x * c.multiplier)
}
}
result := F.Pipe1(
Of[testState, testContext, error](5),
ChainReaderIOEitherK[testState](rioeFunc),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 15, P.Tail(p))
return p
})(res)
}
func TestDo(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
type Result struct {
value int
}
result := Do[testState, testContext, error](Result{})
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, Result]) P.Pair[testState, Result] {
assert.Equal(t, 0, P.Tail(p).value)
return p
})(res)
}
func TestBindTo(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
type Result struct {
value int
}
result := F.Pipe1(
Of[testState, testContext, error](42),
BindTo[testState, testContext, error](func(v int) Result {
return Result{value: v}
}),
)
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, Result]) P.Pair[testState, Result] {
assert.Equal(t, 42, P.Tail(p).value)
return p
})(res)
}
func TestStatefulComputation(t *testing.T) {
initialState := testState{counter: 0}
ctx := testContext{multiplier: 10}
// Create a computation that modifies state
incrementAndGet := func(s testState) P.Pair[testState, int] {
newState := testState{counter: s.counter + 1}
return P.MakePair(newState, newState.counter)
}
// Chain multiple stateful operations
result := F.Pipe2(
FromState[testContext, error](incrementAndGet),
Chain(func(v1 int) StateReaderIOEither[testState, testContext, error, int] {
return FromState[testContext, error](incrementAndGet)
}),
Chain(func(v2 int) StateReaderIOEither[testState, testContext, error, int] {
return FromState[testContext, error](incrementAndGet)
}),
)
res := result(initialState)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, int]) P.Pair[testState, int] {
assert.Equal(t, 3, P.Tail(p)) // Last incremented value
assert.Equal(t, 3, P.Head(p).counter) // State updated three times
return p
})(res)
}
func TestErrorPropagation(t *testing.T) {
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
testErr := errors.New("test error")
// Chain operations where the second one fails
result := F.Pipe1(
Of[testState, testContext, error](42),
Chain(func(x int) StateReaderIOEither[testState, testContext, error, int] {
return Left[testState, testContext, int](testErr)
}),
)
res := result(state)(ctx)()
assert.True(t, E.IsLeft(res))
}
func TestPointed(t *testing.T) {
p := Pointed[testState, testContext, error, int]()
assert.NotNil(t, p)
result := p.Of(42)
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
}
func TestFunctor(t *testing.T) {
f := Functor[testState, testContext, error, int, string]()
assert.NotNil(t, f)
mapper := f.Map(func(x int) string { return fmt.Sprintf("%d", x) })
result := mapper(Of[testState, testContext, error](42))
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "42", P.Tail(p))
return p
})(res)
}
func TestApplicative(t *testing.T) {
a := Applicative[testState, testContext, error, int, string]()
assert.NotNil(t, a)
fab := Of[testState, testContext, error](func(x int) string { return fmt.Sprintf("%d", x) })
fa := Of[testState, testContext, error](42)
result := a.Ap(fa)(fab)
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "42", P.Tail(p))
return p
})(res)
}
func TestMonad(t *testing.T) {
m := Monad[testState, testContext, error, int, string]()
assert.NotNil(t, m)
fa := m.Of(42)
result := m.Chain(func(x int) StateReaderIOEither[testState, testContext, error, string] {
return Of[testState, testContext, error](fmt.Sprintf("%d", x))
})(fa)
state := testState{counter: 0}
ctx := testContext{multiplier: 2}
res := result(state)(ctx)()
assert.True(t, E.IsRight(res))
E.Map[error](func(p P.Pair[testState, string]) P.Pair[testState, string] {
assert.Equal(t, "42", P.Tail(p))
return p
})(res)
}

View File

@@ -29,17 +29,57 @@ import (
)
type (
Endomorphism[A any] = endomorphism.Endomorphism[A]
Lens[S, A any] = lens.Lens[S, A]
State[S, A any] = state.State[S, A]
Pair[L, R any] = pair.Pair[L, R]
Reader[R, A any] = reader.Reader[R, A]
Either[E, A any] = either.Either[E, A]
IO[A any] = io.IO[A]
IOEither[E, A any] = ioeither.IOEither[E, A]
ReaderIOEither[R, E, A any] = readerioeither.ReaderIOEither[R, E, A]
ReaderEither[R, E, A any] = readereither.ReaderEither[R, E, A]
// Endomorphism represents a function from A to A.
Endomorphism[A any] = endomorphism.Endomorphism[A]
// Lens is an optic that focuses on a field of type A within a structure of type S.
Lens[S, A any] = lens.Lens[S, A]
// State represents a stateful computation that takes an initial state S and returns
// a pair of the new state S and a value A.
State[S, A any] = state.State[S, A]
// Pair represents a tuple of two values.
Pair[L, R any] = pair.Pair[L, R]
// Reader represents a computation that depends on an environment/context of type R
// and produces a value of type A.
Reader[R, A any] = reader.Reader[R, A]
// Either represents a value that can be either a Left (error) or Right (success).
Either[E, A any] = either.Either[E, A]
// IO represents a computation that performs side effects and produces a value of type A.
IO[A any] = io.IO[A]
// IOEither represents a computation that performs side effects and can fail with an error E
// or succeed with a value A.
IOEither[E, A any] = ioeither.IOEither[E, A]
// ReaderIOEither represents a computation that depends on an environment R,
// performs side effects, and can fail with an error E or succeed with a value A.
ReaderIOEither[R, E, A any] = readerioeither.ReaderIOEither[R, E, A]
// ReaderEither represents a computation that depends on an environment R and can fail
// with an error E or succeed with a value A (without side effects).
ReaderEither[R, E, A any] = readereither.ReaderEither[R, E, A]
// StateReaderIOEither represents a stateful computation that:
// - Takes an initial state S
// - Depends on an environment/context R
// - Performs side effects (IO)
// - Can fail with an error E or succeed with a value A
// - Returns a pair of the new state S and the result
//
// This is the main type of this package, combining State, Reader, IO, and Either monads.
StateReaderIOEither[S, R, E, A any] = Reader[S, ReaderIOEither[R, E, Pair[S, A]]]
Kleisli[S, R, E, A, B any] = Reader[A, StateReaderIOEither[S, R, E, B]]
Operator[S, R, E, A, B any] = Reader[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]]
// Kleisli represents a Kleisli arrow - a function that takes a value A and returns
// a StateReaderIOEither computation producing B.
// This is used for monadic composition via Chain.
Kleisli[S, R, E, A, B any] = Reader[A, StateReaderIOEither[S, R, E, B]]
// Operator represents a function that transforms one StateReaderIOEither into another.
// This is commonly used for building composable operations via Map, Chain, etc.
Operator[S, R, E, A, B any] = Reader[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]]
)