mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-25 22:21:49 +02:00
fix: implement BindReaderK
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -22,10 +22,12 @@ import (
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func FromOption[A, HKTEA, E any](fromEither func(ET.Either[E, A]) HKTEA, onNone func() E) func(ma O.Option[A]) HKTEA {
|
||||
return F.Flow2(ET.FromOption[A](onNone), fromEither)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromPredicate[E, A, HKTEA any](fromEither func(ET.Either[E, A]) HKTEA, pred func(A) bool, onFalse func(A) E) func(A) HKTEA {
|
||||
return F.Flow2(ET.FromPredicate(pred, onFalse), fromEither)
|
||||
}
|
||||
@@ -45,6 +47,7 @@ func MonadFromOption[E, A, HKTEA any](
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromOptionK[A, E, B, HKTEB any](
|
||||
fromEither func(ET.Either[E, B]) HKTEB,
|
||||
onNone func() E) func(f func(A) O.Option[B]) func(A) HKTEB {
|
||||
@@ -52,6 +55,7 @@ func FromOptionK[A, E, B, HKTEB any](
|
||||
return F.Bind2nd(F.Flow2[func(A) O.Option[B], func(O.Option[B]) HKTEB, A, O.Option[B], HKTEB], FromOption(fromEither, onNone))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
mchain func(HKTEA, func(A) HKTEB) HKTEB,
|
||||
fromEither func(ET.Either[E, B]) HKTEB,
|
||||
@@ -60,6 +64,7 @@ func MonadChainEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
return mchain(ma, F.Flow2(f, fromEither))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
mchain func(func(A) HKTEB) func(HKTEA) HKTEB,
|
||||
fromEither func(ET.Either[E, B]) HKTEB,
|
||||
@@ -67,6 +72,7 @@ func ChainEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
return mchain(F.Flow2(f, fromEither))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainOptionK[A, E, B, HKTEA, HKTEB any](
|
||||
mchain func(HKTEA, func(A) HKTEB) HKTEB,
|
||||
fromEither func(ET.Either[E, B]) HKTEB,
|
||||
@@ -75,6 +81,7 @@ func ChainOptionK[A, E, B, HKTEA, HKTEB any](
|
||||
return F.Flow2(FromOptionK[A](fromEither, onNone), F.Bind1st(F.Bind2nd[HKTEA, func(A) HKTEB, HKTEB], mchain))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
mchain func(HKTEA, func(A) HKTEA) HKTEA,
|
||||
mmap func(HKTEB, func(B) A) HKTEA,
|
||||
@@ -84,6 +91,7 @@ func MonadChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
return C.MonadChainFirst(mchain, mmap, ma, F.Flow2(f, fromEither))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
mchain func(func(A) HKTEA) func(HKTEA) HKTEA,
|
||||
mmap func(func(B) A) func(HKTEB) HKTEA,
|
||||
@@ -91,3 +99,23 @@ func ChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
f func(A) ET.Either[E, B]) func(HKTEA) HKTEA {
|
||||
return C.ChainFirst(mchain, mmap, F.Flow2(f, fromEither))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindEitherK[
|
||||
E, S1, S2, T,
|
||||
HKTET,
|
||||
HKTES1,
|
||||
HKTES2 any](
|
||||
mchain func(func(S1) HKTES2) func(HKTES1) HKTES2,
|
||||
mmap func(func(T) S2) func(HKTET) HKTES2,
|
||||
fromEither func(ET.Either[E, T]) HKTET,
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) ET.Either[E, T],
|
||||
) func(HKTES1) HKTES2 {
|
||||
return C.Bind(
|
||||
mchain,
|
||||
mmap,
|
||||
setter,
|
||||
F.Flow2(f, fromEither),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package fromreader
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
C "github.com/IBM/fp-go/v2/internal/chain"
|
||||
G "github.com/IBM/fp-go/v2/reader/generic"
|
||||
)
|
||||
|
||||
@@ -69,3 +70,24 @@ func ChainFirstReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
|
||||
) func(HKTRA) HKTRA {
|
||||
return mchain(FromReaderK(fromReader, f))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindReaderK[
|
||||
GT ~func(R) T,
|
||||
R, S1, S2, T,
|
||||
HKTET,
|
||||
HKTES1,
|
||||
HKTES2 any](
|
||||
mchain func(func(S1) HKTES2) func(HKTES1) HKTES2,
|
||||
mmap func(func(T) S2) func(HKTET) HKTES2,
|
||||
fromReader func(GT) HKTET,
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) GT,
|
||||
) func(HKTES1) HKTES2 {
|
||||
return C.Bind(
|
||||
mchain,
|
||||
mmap,
|
||||
setter,
|
||||
FromReaderK(fromReader, f),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package readereither
|
||||
|
||||
import (
|
||||
ET "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
L "github.com/IBM/fp-go/v2/optics/lens"
|
||||
G "github.com/IBM/fp-go/v2/readereither/generic"
|
||||
@@ -35,6 +36,8 @@ import (
|
||||
// ConfigService ConfigService
|
||||
// }
|
||||
// result := readereither.Do[Env, error](State{})
|
||||
//
|
||||
//go:inline
|
||||
func Do[R, E, S any](
|
||||
empty S,
|
||||
) ReaderEither[R, E, S] {
|
||||
@@ -83,6 +86,8 @@ func Do[R, E, S any](
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func Bind[R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) ReaderEither[R, E, T],
|
||||
@@ -90,7 +95,59 @@ func Bind[R, E, S1, S2, T any](
|
||||
return G.Bind[ReaderEither[R, E, S1], ReaderEither[R, E, S2]](setter, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindReaderK[R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) Reader[R, T],
|
||||
) func(ReaderEither[R, E, S1]) ReaderEither[R, E, S2] {
|
||||
return G.BindReaderK[ReaderEither[R, E, S1], ReaderEither[R, E, S2]](setter, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindEitherK[R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) Either[E, T],
|
||||
) func(ReaderEither[R, E, S1]) ReaderEither[R, E, S2] {
|
||||
return G.BindEitherK[ReaderEither[R, E, S1], ReaderEither[R, E, S2]](setter, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindToReader[
|
||||
R, E, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(Reader[R, T]) ReaderEither[R, E, S1] {
|
||||
return G.BindToReader[ReaderEither[R, E, S1], Reader[R, T]](setter)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindToEither[
|
||||
R, E, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(ET.Either[E, T]) ReaderEither[R, E, S1] {
|
||||
return G.BindToEither[ReaderEither[R, E, S1]](setter)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ApReaderS[
|
||||
R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Reader[R, T],
|
||||
) Operator[R, E, S1, S2] {
|
||||
return G.ApReaderS[ReaderEither[R, E, S1], ReaderEither[R, E, S2]](setter, fa)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ApEitherS[
|
||||
R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa ET.Either[E, T],
|
||||
) Operator[R, E, S1, S2] {
|
||||
return G.ApEitherS[ReaderEither[R, E, S1], ReaderEither[R, E, S2]](setter, fa)
|
||||
}
|
||||
|
||||
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
|
||||
//
|
||||
//go:inline
|
||||
func Let[R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) T,
|
||||
|
||||
@@ -17,8 +17,11 @@ package generic
|
||||
|
||||
import (
|
||||
ET "github.com/IBM/fp-go/v2/either"
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
A "github.com/IBM/fp-go/v2/internal/apply"
|
||||
C "github.com/IBM/fp-go/v2/internal/chain"
|
||||
FE "github.com/IBM/fp-go/v2/internal/fromeither"
|
||||
FR "github.com/IBM/fp-go/v2/internal/fromreader"
|
||||
F "github.com/IBM/fp-go/v2/internal/functor"
|
||||
)
|
||||
|
||||
@@ -36,6 +39,8 @@ import (
|
||||
// UserService UserService
|
||||
// }
|
||||
// result := generic.Do[ReaderEither[Env, error, State], Env, error, State](State{})
|
||||
//
|
||||
//go:inline
|
||||
func Do[GS ~func(R) ET.Either[E, S], R, E, S any](
|
||||
empty S,
|
||||
) GS {
|
||||
@@ -84,7 +89,12 @@ func Do[GS ~func(R) ET.Either[E, S], R, E, S any](
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
func Bind[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], GT ~func(R) ET.Either[E, T], R, E, S1, S2, T any](
|
||||
//
|
||||
//go:inline
|
||||
func Bind[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
GS2 ~func(R) ET.Either[E, S2],
|
||||
GT ~func(R) ET.Either[E, T], R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) GT,
|
||||
) func(GS1) GS2 {
|
||||
@@ -96,6 +106,41 @@ func Bind[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], GT ~func
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindReaderK[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
GS2 ~func(R) ET.Either[E, S2],
|
||||
GRT ~func(R) T,
|
||||
R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) GRT,
|
||||
) func(GS1) GS2 {
|
||||
return FR.BindReaderK(
|
||||
Chain[GS1, GS2, E, R, S1, S2],
|
||||
Map[func(R) ET.Either[E, T], GS2, E, R, T, S2],
|
||||
FromReader[GRT, func(R) ET.Either[E, T]],
|
||||
setter,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindEitherK[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
GS2 ~func(R) ET.Either[E, S2],
|
||||
R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) ET.Either[E, T],
|
||||
) func(GS1) GS2 {
|
||||
return FE.BindEitherK(
|
||||
Chain[GS1, GS2, E, R, S1, S2],
|
||||
Map[func(R) ET.Either[E, T], GS2, E, R, T, S2],
|
||||
FromEither[func(R) ET.Either[E, T]],
|
||||
setter,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
|
||||
func Let[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], R, E, S1, S2, T any](
|
||||
key func(T) func(S1) S2,
|
||||
@@ -130,6 +175,31 @@ func BindTo[GS1 ~func(R) ET.Either[E, S1], GT ~func(R) ET.Either[E, T], R, E, S1
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindToReader[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
GT ~func(R) T,
|
||||
R, E, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(GT) GS1 {
|
||||
return function.Flow2(
|
||||
FromReader[GT, func(R) ET.Either[E, T]],
|
||||
BindTo[GS1, func(R) ET.Either[E, T]](setter),
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindToEither[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
R, E, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(ET.Either[E, T]) GS1 {
|
||||
return function.Flow2(
|
||||
FromEither[func(R) ET.Either[E, T]],
|
||||
BindTo[GS1, func(R) ET.Either[E, T]](setter),
|
||||
)
|
||||
}
|
||||
|
||||
// ApS attaches a value to a context [S1] to produce a context [S2] by considering
|
||||
// the context and the value concurrently (using Applicative rather than Monad).
|
||||
// This allows independent computations to be combined without one depending on the result of the other.
|
||||
@@ -182,3 +252,32 @@ func ApS[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], GT ~func(
|
||||
fa,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ApReaderS[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
GS2 ~func(R) ET.Either[E, S2],
|
||||
GT ~func(R) T,
|
||||
R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa GT,
|
||||
) func(GS1) GS2 {
|
||||
return ApS[GS1, GS2](
|
||||
setter,
|
||||
FromReader[GT, func(R) ET.Either[E, T]](fa),
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ApEitherS[
|
||||
GS1 ~func(R) ET.Either[E, S1],
|
||||
GS2 ~func(R) ET.Either[E, S2],
|
||||
R, E, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa ET.Either[E, T],
|
||||
) func(GS1) GS2 {
|
||||
return ApS[GS1, GS2](
|
||||
setter,
|
||||
FromEither[func(R) ET.Either[E, T]](fa),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,23 @@ func Chain[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A,
|
||||
return F.Bind2nd(MonadChain[GEA, GEB, L, E, A, B], f)
|
||||
}
|
||||
|
||||
func MonadChainReaderK[
|
||||
GEA ~func(E) ET.Either[L, A],
|
||||
GEB ~func(E) ET.Either[L, B],
|
||||
GB ~func(E) B,
|
||||
L, E, A, B any](ma GEA, f func(A) GB) GEB {
|
||||
|
||||
return MonadChain(ma, F.Flow2(f, FromReader[GB, GEB, L, E, B]))
|
||||
}
|
||||
|
||||
func ChainReaderK[
|
||||
GEA ~func(E) ET.Either[L, A],
|
||||
GEB ~func(E) ET.Either[L, B],
|
||||
GB ~func(E) B,
|
||||
L, E, A, B any](f func(A) GB) func(GEA) GEB {
|
||||
return Chain[GEA, GEB, L, E, A, B](F.Flow2(f, FromReader[GB, GEB, L, E, B]))
|
||||
}
|
||||
|
||||
func Of[GEA ~func(E) ET.Either[L, A], L, E, A any](a A) GEA {
|
||||
return readert.MonadOf[GEA](ET.Of[L, A], a)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ package readerresult
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
L "github.com/IBM/fp-go/v2/optics/lens"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
G "github.com/IBM/fp-go/v2/readereither/generic"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
// Do creates an empty context of type [S] to be used with the [Bind] operation.
|
||||
@@ -321,3 +323,277 @@ func LetToL[R, S, T any](
|
||||
) Operator[R, S, S] {
|
||||
return LetTo[R](lens.Set, b)
|
||||
}
|
||||
|
||||
// BindReaderK lifts a Reader Kleisli arrow into a ReaderResult context and binds it to the state.
|
||||
// This allows you to integrate pure Reader computations (that don't have error handling)
|
||||
// into a ReaderResult computation chain.
|
||||
//
|
||||
// The function f takes the current state S1 and returns a Reader[R, T] computation.
|
||||
// The result T is then attached to the state using the setter to produce state S2.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Env struct {
|
||||
// ConfigPath string
|
||||
// }
|
||||
// type State struct {
|
||||
// Config string
|
||||
// }
|
||||
//
|
||||
// // A pure Reader computation that reads from environment
|
||||
// getConfigPath := func(s State) reader.Reader[Env, string] {
|
||||
// return func(env Env) string {
|
||||
// return env.ConfigPath
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readerresult.Do[Env](State{}),
|
||||
// readerresult.BindReaderK(
|
||||
// func(path string) func(State) State {
|
||||
// return func(s State) State { s.Config = path; return s }
|
||||
// },
|
||||
// getConfigPath,
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func BindReaderK[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f reader.Kleisli[R, S1, T],
|
||||
) Operator[R, S1, S2] {
|
||||
return G.BindReaderK[ReaderResult[R, S1], ReaderResult[R, S2]](setter, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindEitherK[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f result.Kleisli[S1, T],
|
||||
) Operator[R, S1, S2] {
|
||||
return G.BindEitherK[ReaderResult[R, S1], ReaderResult[R, S2]](setter, f)
|
||||
}
|
||||
|
||||
// BindResultK lifts a Result Kleisli arrow into a ReaderResult context and binds it to the state.
|
||||
// This allows you to integrate Result computations (that may fail with an error but don't need
|
||||
// environment access) into a ReaderResult computation chain.
|
||||
//
|
||||
// The function f takes the current state S1 and returns a Result[T] computation.
|
||||
// If the Result is successful, the value T is attached to the state using the setter to produce state S2.
|
||||
// If the Result is an error, the entire computation short-circuits with that error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type State struct {
|
||||
// Value int
|
||||
// ParsedValue int
|
||||
// }
|
||||
//
|
||||
// // A Result computation that may fail
|
||||
// parseValue := func(s State) result.Result[int] {
|
||||
// if s.Value < 0 {
|
||||
// return result.Error[int](errors.New("negative value"))
|
||||
// }
|
||||
// return result.Of(s.Value * 2)
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readerresult.Do[any](State{Value: 5}),
|
||||
// readerresult.BindResultK(
|
||||
// func(parsed int) func(State) State {
|
||||
// return func(s State) State { s.ParsedValue = parsed; return s }
|
||||
// },
|
||||
// parseValue,
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func BindResultK[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f result.Kleisli[S1, T],
|
||||
) Operator[R, S1, S2] {
|
||||
return G.BindEitherK[ReaderResult[R, S1], ReaderResult[R, S2]](setter, f)
|
||||
}
|
||||
|
||||
// BindToReader initializes a new state S1 from a Reader[R, T] computation.
|
||||
// This is used to start a ReaderResult computation chain from a pure Reader value.
|
||||
//
|
||||
// The setter function takes the result T from the Reader and initializes the state S1.
|
||||
// This is useful when you want to begin a do-notation chain with a Reader computation
|
||||
// that doesn't involve error handling.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Env struct {
|
||||
// ConfigPath string
|
||||
// }
|
||||
// type State struct {
|
||||
// Config string
|
||||
// }
|
||||
//
|
||||
// // A Reader that just reads from the environment
|
||||
// getConfigPath := func(env Env) string {
|
||||
// return env.ConfigPath
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe1(
|
||||
// reader.Of[Env](getConfigPath),
|
||||
// readerresult.BindToReader(func(path string) State {
|
||||
// return State{Config: path}
|
||||
// }),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func BindToReader[
|
||||
R, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(Reader[R, T]) ReaderResult[R, S1] {
|
||||
return G.BindToReader[ReaderResult[R, S1], Reader[R, T]](setter)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func BindToEither[
|
||||
R, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(Result[T]) ReaderResult[R, S1] {
|
||||
return G.BindToEither[ReaderResult[R, S1]](setter)
|
||||
}
|
||||
|
||||
// BindToResult initializes a new state S1 from a Result[T] value.
|
||||
// This is used to start a ReaderResult computation chain from a Result that may contain an error.
|
||||
//
|
||||
// The setter function takes the successful result T and initializes the state S1.
|
||||
// If the Result is an error, the entire computation will carry that error forward.
|
||||
// This is useful when you want to begin a do-notation chain with a Result computation
|
||||
// that doesn't need environment access.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type State struct {
|
||||
// Value int
|
||||
// }
|
||||
//
|
||||
// // A Result that might contain an error
|
||||
// parseResult := result.TryCatch(func() int {
|
||||
// // some parsing logic that might fail
|
||||
// return 42
|
||||
// })
|
||||
//
|
||||
// computation := F.Pipe1(
|
||||
// parseResult,
|
||||
// readerresult.BindToResult[any](func(value int) State {
|
||||
// return State{Value: value}
|
||||
// }),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func BindToResult[
|
||||
R, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(Result[T]) ReaderResult[R, S1] {
|
||||
return G.BindToEither[ReaderResult[R, S1]](setter)
|
||||
}
|
||||
|
||||
// ApReaderS attaches a value from a pure Reader computation to a context [S1] to produce a context [S2]
|
||||
// using Applicative semantics (independent, non-sequential composition).
|
||||
//
|
||||
// Unlike BindReaderK which uses monadic bind (sequential), ApReaderS uses applicative apply,
|
||||
// meaning the Reader computation fa is independent of the current state and can conceptually
|
||||
// execute in parallel.
|
||||
//
|
||||
// This is useful when you want to combine a Reader computation with your ReaderResult state
|
||||
// without creating a dependency between them.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Env struct {
|
||||
// DefaultPort int
|
||||
// DefaultHost string
|
||||
// }
|
||||
// type State struct {
|
||||
// Port int
|
||||
// Host string
|
||||
// }
|
||||
//
|
||||
// getDefaultPort := func(env Env) int { return env.DefaultPort }
|
||||
// getDefaultHost := func(env Env) string { return env.DefaultHost }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readerresult.Do[Env](State{}),
|
||||
// readerresult.ApReaderS(
|
||||
// func(port int) func(State) State {
|
||||
// return func(s State) State { s.Port = port; return s }
|
||||
// },
|
||||
// getDefaultPort,
|
||||
// ),
|
||||
// readerresult.ApReaderS(
|
||||
// func(host string) func(State) State {
|
||||
// return func(s State) State { s.Host = host; return s }
|
||||
// },
|
||||
// getDefaultHost,
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func ApReaderS[
|
||||
R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Reader[R, T],
|
||||
) Operator[R, S1, S2] {
|
||||
return G.ApReaderS[ReaderResult[R, S1], ReaderResult[R, S2]](setter, fa)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ApEitherS[
|
||||
R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Result[T],
|
||||
) Operator[R, S1, S2] {
|
||||
return G.ApEitherS[ReaderResult[R, S1], ReaderResult[R, S2]](setter, fa)
|
||||
}
|
||||
|
||||
// ApResultS attaches a value from a Result to a context [S1] to produce a context [S2]
|
||||
// using Applicative semantics (independent, non-sequential composition).
|
||||
//
|
||||
// Unlike BindResultK which uses monadic bind (sequential), ApResultS uses applicative apply,
|
||||
// meaning the Result computation fa is independent of the current state and can conceptually
|
||||
// execute in parallel.
|
||||
//
|
||||
// If the Result fa contains an error, the entire computation short-circuits with that error.
|
||||
// This is useful when you want to combine a Result value with your ReaderResult state
|
||||
// without creating a dependency between them.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type State struct {
|
||||
// Value1 int
|
||||
// Value2 int
|
||||
// }
|
||||
//
|
||||
// // Independent Result computations
|
||||
// parseValue1 := result.TryCatch(func() int { return 42 })
|
||||
// parseValue2 := result.TryCatch(func() int { return 100 })
|
||||
//
|
||||
// computation := F.Pipe2(
|
||||
// readerresult.Do[any](State{}),
|
||||
// readerresult.ApResultS(
|
||||
// func(v int) func(State) State {
|
||||
// return func(s State) State { s.Value1 = v; return s }
|
||||
// },
|
||||
// parseValue1,
|
||||
// ),
|
||||
// readerresult.ApResultS(
|
||||
// func(v int) func(State) State {
|
||||
// return func(s State) State { s.Value2 = v; return s }
|
||||
// },
|
||||
// parseValue2,
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func ApResultS[
|
||||
R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Result[T],
|
||||
) Operator[R, S1, S2] {
|
||||
return G.ApEitherS[ReaderResult[R, S1], ReaderResult[R, S2]](setter, fa)
|
||||
}
|
||||
|
||||
@@ -56,3 +56,291 @@ func TestApS(t *testing.T) {
|
||||
|
||||
assert.Equal(t, res(context.Background()), result.Of("John Doe"))
|
||||
}
|
||||
|
||||
func TestBindReaderK(t *testing.T) {
|
||||
type Env struct {
|
||||
ConfigPath string
|
||||
}
|
||||
type State struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
// A pure Reader computation
|
||||
getConfigPath := func(s State) func(Env) string {
|
||||
return func(env Env) string {
|
||||
return env.ConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
res := F.Pipe2(
|
||||
Do[Env](State{}),
|
||||
BindReaderK(
|
||||
func(path string) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Config = path
|
||||
return s
|
||||
}
|
||||
},
|
||||
getConfigPath,
|
||||
),
|
||||
Map[Env](func(s State) string { return s.Config }),
|
||||
)
|
||||
|
||||
env := Env{ConfigPath: "/etc/config"}
|
||||
assert.Equal(t, result.Of("/etc/config"), res(env))
|
||||
}
|
||||
|
||||
func TestBindResultK(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
ParsedValue int
|
||||
}
|
||||
|
||||
// A Result computation that may fail
|
||||
parseValue := func(s State) result.Result[int] {
|
||||
if s.Value < 0 {
|
||||
return result.Left[int](assert.AnError)
|
||||
}
|
||||
return result.Of(s.Value * 2)
|
||||
}
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
res := F.Pipe2(
|
||||
Do[context.Context](State{Value: 5}),
|
||||
BindResultK[context.Context](
|
||||
func(parsed int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.ParsedValue = parsed
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue,
|
||||
),
|
||||
Map[context.Context](func(s State) int { return s.ParsedValue }),
|
||||
)
|
||||
|
||||
assert.Equal(t, result.Of(10), res(context.Background()))
|
||||
})
|
||||
|
||||
t.Run("error case", func(t *testing.T) {
|
||||
res := F.Pipe2(
|
||||
Do[context.Context](State{Value: -5}),
|
||||
BindResultK[context.Context](
|
||||
func(parsed int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.ParsedValue = parsed
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue,
|
||||
),
|
||||
Map[context.Context](func(s State) int { return s.ParsedValue }),
|
||||
)
|
||||
|
||||
assert.True(t, result.IsLeft(res(context.Background())))
|
||||
})
|
||||
}
|
||||
|
||||
func TestBindToReader(t *testing.T) {
|
||||
type Env struct {
|
||||
ConfigPath string
|
||||
}
|
||||
type State struct {
|
||||
Config string
|
||||
}
|
||||
|
||||
// A Reader that just reads from the environment
|
||||
getConfigPath := func(env Env) string {
|
||||
return env.ConfigPath
|
||||
}
|
||||
|
||||
res := F.Pipe2(
|
||||
getConfigPath,
|
||||
BindToReader[Env](func(path string) State {
|
||||
return State{Config: path}
|
||||
}),
|
||||
Map[Env](func(s State) string { return s.Config }),
|
||||
)
|
||||
|
||||
env := Env{ConfigPath: "/etc/config"}
|
||||
assert.Equal(t, result.Of("/etc/config"), res(env))
|
||||
}
|
||||
|
||||
func TestBindToResult(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
parseResult := result.Of(42)
|
||||
|
||||
computation := F.Pipe2(
|
||||
parseResult,
|
||||
BindToResult[context.Context](func(value int) State {
|
||||
return State{Value: value}
|
||||
}),
|
||||
Map[context.Context](func(s State) int { return s.Value }),
|
||||
)
|
||||
|
||||
assert.Equal(t, result.Of(42), computation(context.Background()))
|
||||
})
|
||||
|
||||
t.Run("error case", func(t *testing.T) {
|
||||
parseResult := result.Left[int](assert.AnError)
|
||||
|
||||
computation := F.Pipe2(
|
||||
parseResult,
|
||||
BindToResult[context.Context](func(value int) State {
|
||||
return State{Value: value}
|
||||
}),
|
||||
Map[context.Context](func(s State) int { return s.Value }),
|
||||
)
|
||||
|
||||
assert.True(t, result.IsLeft(computation(context.Background())))
|
||||
})
|
||||
}
|
||||
|
||||
func TestApReaderS(t *testing.T) {
|
||||
type Env struct {
|
||||
DefaultPort int
|
||||
DefaultHost string
|
||||
}
|
||||
type State struct {
|
||||
Port int
|
||||
Host string
|
||||
}
|
||||
|
||||
getDefaultPort := func(env Env) int { return env.DefaultPort }
|
||||
getDefaultHost := func(env Env) string { return env.DefaultHost }
|
||||
|
||||
res := F.Pipe3(
|
||||
Do[Env](State{}),
|
||||
ApReaderS(
|
||||
func(port int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Port = port
|
||||
return s
|
||||
}
|
||||
},
|
||||
getDefaultPort,
|
||||
),
|
||||
ApReaderS(
|
||||
func(host string) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Host = host
|
||||
return s
|
||||
}
|
||||
},
|
||||
getDefaultHost,
|
||||
),
|
||||
Map[Env](func(s State) State { return s }),
|
||||
)
|
||||
|
||||
env := Env{DefaultPort: 8080, DefaultHost: "localhost"}
|
||||
r := res(env)
|
||||
assert.True(t, result.IsRight(r))
|
||||
state := result.GetOrElse(func(error) State { return State{} })(r)
|
||||
assert.Equal(t, 8080, state.Port)
|
||||
assert.Equal(t, "localhost", state.Host)
|
||||
}
|
||||
|
||||
func TestApResultS(t *testing.T) {
|
||||
type State struct {
|
||||
Value1 int
|
||||
Value2 int
|
||||
}
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
parseValue1 := result.Of(42)
|
||||
parseValue2 := result.Of(100)
|
||||
|
||||
computation := F.Pipe3(
|
||||
Do[context.Context](State{}),
|
||||
ApResultS[context.Context](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Value1 = v
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue1,
|
||||
),
|
||||
ApResultS[context.Context](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Value2 = v
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue2,
|
||||
),
|
||||
Map[context.Context](func(s State) State { return s }),
|
||||
)
|
||||
|
||||
r := computation(context.Background())
|
||||
assert.True(t, result.IsRight(r))
|
||||
state := result.GetOrElse(func(error) State { return State{} })(r)
|
||||
assert.Equal(t, 42, state.Value1)
|
||||
assert.Equal(t, 100, state.Value2)
|
||||
})
|
||||
|
||||
t.Run("error in first value", func(t *testing.T) {
|
||||
parseValue1 := result.Left[int](assert.AnError)
|
||||
parseValue2 := result.Of(100)
|
||||
|
||||
computation := F.Pipe3(
|
||||
Do[context.Context](State{}),
|
||||
ApResultS[context.Context](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Value1 = v
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue1,
|
||||
),
|
||||
ApResultS[context.Context](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Value2 = v
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue2,
|
||||
),
|
||||
Map[context.Context](func(s State) State { return s }),
|
||||
)
|
||||
|
||||
assert.True(t, result.IsLeft(computation(context.Background())))
|
||||
})
|
||||
|
||||
t.Run("error in second value", func(t *testing.T) {
|
||||
parseValue1 := result.Of(42)
|
||||
parseValue2 := result.Left[int](assert.AnError)
|
||||
|
||||
computation := F.Pipe3(
|
||||
Do[context.Context](State{}),
|
||||
ApResultS[context.Context](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Value1 = v
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue1,
|
||||
),
|
||||
ApResultS[context.Context](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State {
|
||||
s.Value2 = v
|
||||
return s
|
||||
}
|
||||
},
|
||||
parseValue2,
|
||||
),
|
||||
Map[context.Context](func(s State) State { return s }),
|
||||
)
|
||||
|
||||
assert.True(t, result.IsLeft(computation(context.Background())))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user