From 6d94697128e48c251ea69ff70873432fda2abe27 Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Tue, 18 Nov 2025 16:06:56 +0100 Subject: [PATCH] fix: document statereaderioeither Signed-off-by: Dr. Carsten Leue --- v2/.claude/settings.local.json | 3 +- v2/statereaderioeither/bind.go | 102 +++ v2/statereaderioeither/doc.go | 132 ++++ v2/statereaderioeither/state.go | 134 ++++ .../statereaderioeither_test.go | 662 ++++++++++++++++++ v2/statereaderioeither/type.go | 64 +- 6 files changed, 1084 insertions(+), 13 deletions(-) create mode 100644 v2/statereaderioeither/doc.go create mode 100644 v2/statereaderioeither/statereaderioeither_test.go diff --git a/v2/.claude/settings.local.json b/v2/.claude/settings.local.json index bd479a4..c4d5cd1 100644 --- a/v2/.claude/settings.local.json +++ b/v2/.claude/settings.local.json @@ -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": [] diff --git a/v2/statereaderioeither/bind.go b/v2/statereaderioeither/bind.go index 125f4b4..1b484f5 100644 --- a/v2/statereaderioeither/bind.go +++ b/v2/statereaderioeither/bind.go @@ -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], diff --git a/v2/statereaderioeither/doc.go b/v2/statereaderioeither/doc.go new file mode 100644 index 0000000..6a8a2e9 --- /dev/null +++ b/v2/statereaderioeither/doc.go @@ -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 diff --git a/v2/statereaderioeither/state.go b/v2/statereaderioeither/state.go index e2e4fca..2b29b60 100644 --- a/v2/statereaderioeither/state.go +++ b/v2/statereaderioeither/state.go @@ -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)) } diff --git a/v2/statereaderioeither/statereaderioeither_test.go b/v2/statereaderioeither/statereaderioeither_test.go new file mode 100644 index 0000000..e9f321f --- /dev/null +++ b/v2/statereaderioeither/statereaderioeither_test.go @@ -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) +} diff --git a/v2/statereaderioeither/type.go b/v2/statereaderioeither/type.go index dd08b73..0423da5 100644 --- a/v2/statereaderioeither/type.go +++ b/v2/statereaderioeither/type.go @@ -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]] )