1
0
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:
Dr. Carsten Leue
2025-11-21 13:01:27 +01:00
parent 4909ad5473
commit 12a20e30d1
7 changed files with 788 additions and 1 deletions

View File

@@ -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),
)
}

View File

@@ -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),
)
}

View File

@@ -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,

View File

@@ -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),
)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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())))
})
}