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:
@@ -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": []
|
||||
|
||||
@@ -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],
|
||||
|
||||
132
v2/statereaderioeither/doc.go
Normal file
132
v2/statereaderioeither/doc.go
Normal 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
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
662
v2/statereaderioeither/statereaderioeither_test.go
Normal file
662
v2/statereaderioeither/statereaderioeither_test.go
Normal 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)
|
||||
}
|
||||
@@ -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]]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user