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": [
|
"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\\monad\"\" && ls -la \"c:dfp-gov2internalapplicative\"\")",
|
||||||
"Bash(ls -la \"c:\\d\\fp-go\\v2\\internal\\chain\"\" && ls -la \"c:dfp-gov2internalfunctor\"\")",
|
"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": [],
|
"deny": [],
|
||||||
"ask": []
|
"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
|
package statereaderioeither
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -7,6 +22,22 @@ import (
|
|||||||
F "github.com/IBM/fp-go/v2/internal/functor"
|
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
|
//go:inline
|
||||||
func Do[ST, R, E, A any](
|
func Do[ST, R, E, A any](
|
||||||
empty A,
|
empty A,
|
||||||
@@ -14,6 +45,23 @@ func Do[ST, R, E, A any](
|
|||||||
return Of[ST, R, E](empty)
|
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
|
//go:inline
|
||||||
func Bind[ST, R, E, S1, S2, T any](
|
func Bind[ST, R, E, S1, S2, T any](
|
||||||
setter func(T) func(S1) S2,
|
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
|
//go:inline
|
||||||
func Let[ST, R, E, S1, S2, T any](
|
func Let[ST, R, E, S1, S2, T any](
|
||||||
key func(T) func(S1) S2,
|
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
|
//go:inline
|
||||||
func LetTo[ST, R, E, S1, S2, T any](
|
func LetTo[ST, R, E, S1, S2, T any](
|
||||||
key func(T) func(S1) S2,
|
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
|
//go:inline
|
||||||
func BindTo[ST, R, E, S1, T any](
|
func BindTo[ST, R, E, S1, T any](
|
||||||
setter func(T) S1,
|
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
|
//go:inline
|
||||||
func ApS[ST, R, E, S1, S2, T any](
|
func ApS[ST, R, E, S1, S2, T any](
|
||||||
setter func(T) func(S1) S2,
|
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
|
//go:inline
|
||||||
func ApSL[ST, R, E, S, T any](
|
func ApSL[ST, R, E, S, T any](
|
||||||
lens Lens[S, T],
|
lens Lens[S, T],
|
||||||
@@ -82,6 +175,9 @@ func ApSL[ST, R, E, S, T any](
|
|||||||
return ApS(lens.Set, fa)
|
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
|
//go:inline
|
||||||
func BindL[ST, R, E, S, T any](
|
func BindL[ST, R, E, S, T any](
|
||||||
lens Lens[S, T],
|
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))
|
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
|
//go:inline
|
||||||
func LetL[ST, R, E, S, T any](
|
func LetL[ST, R, E, S, T any](
|
||||||
lens Lens[S, T],
|
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))
|
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
|
//go:inline
|
||||||
func LetToL[ST, R, E, S, T any](
|
func LetToL[ST, R, E, S, T any](
|
||||||
lens Lens[S, T],
|
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"
|
"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] {
|
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))
|
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] {
|
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)
|
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] {
|
func Of[S, R, E, A any](a A) StateReaderIOEither[S, R, E, A] {
|
||||||
return Right[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] {
|
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]](
|
return statet.MonadMap[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]](
|
||||||
readerioeither.MonadMap[R, E, Pair[S, A], Pair[S, 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] {
|
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]](
|
return statet.Map[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]](
|
||||||
readerioeither.Map[R, E, Pair[S, A], Pair[S, 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] {
|
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(
|
return statet.MonadChain(
|
||||||
readerioeither.MonadChain[R, E, Pair[S, A], Pair[S, B]],
|
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] {
|
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]](
|
return statet.Chain[StateReaderIOEither[S, R, E, A]](
|
||||||
readerioeither.Chain[R, E, Pair[S, A], Pair[S, B]],
|
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] {
|
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]](
|
return statet.MonadAp[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]](
|
||||||
readerioeither.MonadMap[R, E, Pair[S, A], Pair[S, 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] {
|
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]](
|
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]],
|
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] {
|
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]](
|
return statet.FromF[StateReaderIOEither[S, R, E, A]](
|
||||||
readerioeither.MonadMap[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] {
|
func FromReaderEither[S, R, E, A any](fa ReaderEither[R, E, A]) StateReaderIOEither[S, R, E, A] {
|
||||||
return FromReaderIOEither[S](readerioeither.FromReaderEither(fa))
|
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] {
|
func FromIOEither[S, R, E, A any](fa IOEither[E, A]) StateReaderIOEither[S, R, E, A] {
|
||||||
return FromReaderIOEither[S](readerioeither.FromIOEither[R](fa))
|
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] {
|
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)
|
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] {
|
func FromIO[S, R, E, A any](fa IO[A]) StateReaderIOEither[S, R, E, A] {
|
||||||
return FromReaderIOEither[S](readerioeither.FromIO[R, E](fa))
|
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] {
|
func FromReader[S, E, R, A any](fa Reader[R, A]) StateReaderIOEither[S, R, E, A] {
|
||||||
return FromReaderIOEither[S](readerioeither.FromReader[E](fa))
|
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] {
|
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])
|
return either.MonadFold(ma, Left[S, R, A, E], Right[S, R, E, A])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combinators
|
// 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] {
|
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 func(ma StateReaderIOEither[S, R1, E, A]) StateReaderIOEither[S, R2, E, A] {
|
||||||
return function.Flow2(ma, readerioeither.Local[E, Pair[S, A]](f))
|
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[
|
func Asks[
|
||||||
S, R, E, A any,
|
S, R, E, A any,
|
||||||
](f func(R) StateReaderIOEither[S, R, E, A]) StateReaderIOEither[S, R, E, A] {
|
](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] {
|
func FromEitherK[S, R, E, A, B any](f either.Kleisli[E, A, B]) Kleisli[S, R, E, A, B] {
|
||||||
return function.Flow2(
|
return function.Flow2(
|
||||||
f,
|
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] {
|
func FromIOK[S, R, E, A, B any](f func(A) IO[B]) Kleisli[S, R, E, A, B] {
|
||||||
return function.Flow2(
|
return function.Flow2(
|
||||||
f,
|
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[
|
func FromIOEitherK[
|
||||||
S, R, E, A, B any,
|
S, R, E, A, B any,
|
||||||
](f ioeither.Kleisli[E, A, B]) Kleisli[S, R, E, A, B] {
|
](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] {
|
func FromReaderIOEitherK[S, R, E, A, B any](f readerioeither.Kleisli[R, E, A, B]) Kleisli[S, R, E, A, B] {
|
||||||
return function.Flow2(
|
return function.Flow2(
|
||||||
f,
|
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] {
|
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))
|
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] {
|
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))
|
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] {
|
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))
|
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] {
|
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))
|
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] {
|
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))
|
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] {
|
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))
|
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 (
|
type (
|
||||||
|
// Endomorphism represents a function from A to A.
|
||||||
Endomorphism[A any] = endomorphism.Endomorphism[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]
|
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]
|
State[S, A any] = state.State[S, A]
|
||||||
|
|
||||||
|
// Pair represents a tuple of two values.
|
||||||
Pair[L, R any] = pair.Pair[L, R]
|
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]
|
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]
|
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]
|
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]
|
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]
|
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]
|
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]]]
|
StateReaderIOEither[S, R, E, A any] = Reader[S, ReaderIOEither[R, E, Pair[S, A]]]
|
||||||
|
|
||||||
|
// 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]]
|
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]]
|
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