diff --git a/v2/internal/fromeither/either.go b/v2/internal/fromeither/either.go index 163eeeb..b769166 100644 --- a/v2/internal/fromeither/either.go +++ b/v2/internal/fromeither/either.go @@ -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), + ) +} diff --git a/v2/internal/fromreader/reader.go b/v2/internal/fromreader/reader.go index 4ef6967..e27f737 100644 --- a/v2/internal/fromreader/reader.go +++ b/v2/internal/fromreader/reader.go @@ -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), + ) +} diff --git a/v2/readereither/bind.go b/v2/readereither/bind.go index 8669398..6441be8 100644 --- a/v2/readereither/bind.go +++ b/v2/readereither/bind.go @@ -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, diff --git a/v2/readereither/generic/bind.go b/v2/readereither/generic/bind.go index ad9a707..57a8859 100644 --- a/v2/readereither/generic/bind.go +++ b/v2/readereither/generic/bind.go @@ -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), + ) +} diff --git a/v2/readereither/generic/reader.go b/v2/readereither/generic/reader.go index 384b1e6..863b0f4 100644 --- a/v2/readereither/generic/reader.go +++ b/v2/readereither/generic/reader.go @@ -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) } diff --git a/v2/readerresult/bind.go b/v2/readerresult/bind.go index fd96c48..62c25f0 100644 --- a/v2/readerresult/bind.go +++ b/v2/readerresult/bind.go @@ -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) +} diff --git a/v2/readerresult/bind_test.go b/v2/readerresult/bind_test.go index 68d3636..448964b 100644 --- a/v2/readerresult/bind_test.go +++ b/v2/readerresult/bind_test.go @@ -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()))) + }) +}