1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-10 22:31:32 +02:00

fix: add support for context sensitive readers

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-07-14 23:52:14 +02:00
parent 5020437b6a
commit 84c3e3ff88
101 changed files with 4440 additions and 13 deletions

View File

@@ -0,0 +1,18 @@
package ioeither
import (
"context"
ET "github.com/ibm/fp-go/either"
IOE "github.com/ibm/fp-go/ioeither"
)
// withContext wraps an existing IOEither and performs a context check for cancellation before delegating
func WithContext[A any](ctx context.Context, ma IOE.IOEither[error, A]) IOE.IOEither[error, A] {
return IOE.MakeIO(func() ET.Either[error, A] {
if err := context.Cause(ctx); err != nil {
return ET.Left[A](err)
}
return ma()
})
}

15
context/reader/array.go Normal file
View File

@@ -0,0 +1,15 @@
package reader
import (
R "github.com/ibm/fp-go/reader/generic"
)
// TraverseArray transforms an array
func TraverseArray[A, B any](f func(A) Reader[B]) func([]A) Reader[[]B] {
return R.TraverseArray[Reader[B], Reader[[]B], []A](f)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[A any](ma []Reader[A]) Reader[[]A] {
return R.SequenceArray[Reader[A], Reader[[]A]](ma)
}

38
context/reader/curry.go Normal file
View File

@@ -0,0 +1,38 @@
package reader
import (
"context"
R "github.com/ibm/fp-go/reader/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Curry0[A any](f func(context.Context) A) Reader[A] {
return R.Curry0[Reader[A]](f)
}
func Curry1[T1, A any](f func(context.Context, T1) A) func(T1) Reader[A] {
return R.Curry1[Reader[A]](f)
}
func Curry2[T1, T2, A any](f func(context.Context, T1, T2) A) func(T1) func(T2) Reader[A] {
return R.Curry2[Reader[A]](f)
}
func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) A) func(T1) func(T2) func(T3) Reader[A] {
return R.Curry3[Reader[A]](f)
}
func Uncurry1[T1, A any](f func(T1) Reader[A]) func(context.Context, T1) A {
return R.Uncurry1(f)
}
func Uncurry2[T1, T2, A any](f func(T1) func(T2) Reader[A]) func(context.Context, T1, T2) A {
return R.Uncurry2(f)
}
func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) func(T3) Reader[A]) func(context.Context, T1, T2, T3) A {
return R.Uncurry3(f)
}

26
context/reader/from.go Normal file
View File

@@ -0,0 +1,26 @@
package reader
import (
"context"
R "github.com/ibm/fp-go/reader/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[A any](f func(context.Context) A) Reader[A] {
return R.From0[Reader[A]](f)
}
func From1[T1, A any](f func(context.Context, T1) A) func(T1) Reader[A] {
return R.From1[Reader[A]](f)
}
func From2[T1, T2, A any](f func(context.Context, T1, T2) A) func(T1, T2) Reader[A] {
return R.From2[Reader[A]](f)
}
func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) A) func(T1, T2, T3) Reader[A] {
return R.From3[Reader[A]](f)
}

43
context/reader/reader.go Normal file
View File

@@ -0,0 +1,43 @@
package reader
import (
"context"
R "github.com/ibm/fp-go/reader/generic"
)
func MonadMap[A, B any](fa Reader[A], f func(A) B) Reader[B] {
return R.MonadMap[Reader[A], Reader[B]](fa, f)
}
func Map[A, B any](f func(A) B) func(Reader[A]) Reader[B] {
return R.Map[Reader[A], Reader[B]](f)
}
func MonadChain[A, B any](ma Reader[A], f func(A) Reader[B]) Reader[B] {
return R.MonadChain(ma, f)
}
func Chain[A, B any](f func(A) Reader[B]) func(Reader[A]) Reader[B] {
return R.Chain[Reader[A]](f)
}
func Of[A any](a A) Reader[A] {
return R.Of[Reader[A]](a)
}
func MonadAp[A, B any](fab Reader[func(A) B], fa Reader[A]) Reader[B] {
return R.MonadAp[Reader[A], Reader[B]](fab, fa)
}
func Ap[A, B any](fa Reader[A]) func(Reader[func(A) B]) Reader[B] {
return R.Ap[Reader[A], Reader[B], Reader[func(A) B]](fa)
}
func Ask() Reader[context.Context] {
return R.Ask[Reader[context.Context]]()
}
func Asks[A any](r Reader[A]) Reader[A] {
return R.Asks(r)
}

View File

@@ -0,0 +1,60 @@
package reader
import (
"context"
"fmt"
"strings"
"testing"
F "github.com/ibm/fp-go/function"
T "github.com/ibm/fp-go/tuple"
"github.com/stretchr/testify/assert"
)
func GoFunction(ctx context.Context, data string) string {
return strings.ToUpper(data)
}
func GoIntFunction(ctx context.Context, data string, number int) string {
return fmt.Sprintf("%s: %d", data, number)
}
func TestReaderFrom(t *testing.T) {
ctx := context.Background()
f := From1(GoFunction)
result := f("input")(ctx)
assert.Equal(t, result, "INPUT")
}
func MyFinalResult(left, right string) string {
return fmt.Sprintf("%s-%s", left, right)
}
func TestReadersFrom(t *testing.T) {
ctx := context.Background()
f1 := From1(GoFunction)
f2 := From2(GoIntFunction)
result1 := f1("input")(ctx)
result2 := f2("input", 10)(ctx)
result3 := MyFinalResult(result1, result2)
h := F.Pipe1(
SequenceT2(f1("input"), f2("input", 10)),
Map(T.Tupled2(MyFinalResult)),
)
composedResult := h(ctx)
assert.Equal(t, result1, "INPUT")
assert.Equal(t, result2, "input: 10")
assert.Equal(t, result3, "INPUT-input: 10")
assert.Equal(t, composedResult, "INPUT-input: 10")
}

View File

@@ -0,0 +1,42 @@
package reader
import (
R "github.com/ibm/fp-go/reader/generic"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[A any](a Reader[A]) Reader[T.Tuple1[A]] {
return R.SequenceT1[
Reader[A],
Reader[T.Tuple1[A]],
](a)
}
func SequenceT2[A, B any](a Reader[A], b Reader[B]) Reader[T.Tuple2[A, B]] {
return R.SequenceT2[
Reader[A],
Reader[B],
Reader[T.Tuple2[A, B]],
](a, b)
}
func SequenceT3[A, B, C any](a Reader[A], b Reader[B], c Reader[C]) Reader[T.Tuple3[A, B, C]] {
return R.SequenceT3[
Reader[A],
Reader[B],
Reader[C],
Reader[T.Tuple3[A, B, C]],
](a, b, c)
}
func SequenceT4[A, B, C, D any](a Reader[A], b Reader[B], c Reader[C], d Reader[D]) Reader[T.Tuple4[A, B, C, D]] {
return R.SequenceT4[
Reader[A],
Reader[B],
Reader[C],
Reader[D],
Reader[T.Tuple4[A, B, C, D]],
](a, b, c, d)
}

11
context/reader/type.go Normal file
View File

@@ -0,0 +1,11 @@
// Package reader implements a specialization of the Reader monad assuming a golang context as the context of the monad
package reader
import (
"context"
R "github.com/ibm/fp-go/reader"
)
// Reader is a specialization of the Reader monad assuming a golang context as the context of the monad
type Reader[A any] R.Reader[context.Context, A]

View File

@@ -0,0 +1,15 @@
package readereither
import (
RE "github.com/ibm/fp-go/readereither/generic"
)
// TraverseArray transforms an array
func TraverseArray[A, B any](f func(A) ReaderEither[B]) func([]A) ReaderEither[[]B] {
return RE.TraverseArray[ReaderEither[B], ReaderEither[[]B], []A](f)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[A any](ma []ReaderEither[A]) ReaderEither[[]A] {
return RE.SequenceArray[ReaderEither[A], ReaderEither[[]A]](ma)
}

View File

@@ -0,0 +1,17 @@
package readereither
import (
"context"
E "github.com/ibm/fp-go/either"
)
// withContext wraps an existing ReaderEither and performs a context check for cancellation before deletating
func WithContext[A any](ma ReaderEither[A]) ReaderEither[A] {
return func(ctx context.Context) E.Either[error, A] {
if err := context.Cause(ctx); err != nil {
return E.Left[A](err)
}
return ma(ctx)
}
}

View File

@@ -0,0 +1,38 @@
package readereither
import (
"context"
RE "github.com/ibm/fp-go/readereither/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Curry0[A any](f func(context.Context) (A, error)) ReaderEither[A] {
return RE.Curry0[ReaderEither[A]](f)
}
func Curry1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderEither[A] {
return RE.Curry1[ReaderEither[A]](f)
}
func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) func(T2) ReaderEither[A] {
return RE.Curry2[ReaderEither[A]](f)
}
func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderEither[A] {
return RE.Curry3[ReaderEither[A]](f)
}
func Uncurry1[T1, A any](f func(T1) ReaderEither[A]) func(context.Context, T1) (A, error) {
return RE.Uncurry1(f)
}
func Uncurry2[T1, T2, A any](f func(T1) func(T2) ReaderEither[A]) func(context.Context, T1, T2) (A, error) {
return RE.Uncurry2(f)
}
func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderEither[A]) func(context.Context, T1, T2, T3) (A, error) {
return RE.Uncurry3(f)
}

View File

@@ -0,0 +1,26 @@
package exec
import (
"context"
RE "github.com/ibm/fp-go/context/readereither"
E "github.com/ibm/fp-go/either"
"github.com/ibm/fp-go/exec"
F "github.com/ibm/fp-go/function"
GE "github.com/ibm/fp-go/internal/exec"
)
var (
// Command executes a command
// use this version if the command does not produce any side effect, i.e. if the output is uniquely determined by by the input
// typically you'd rather use the ReaderIOEither version of the command
Command = F.Curry3(command)
)
func command(name string, args []string, in []byte) RE.ReaderEither[exec.CommandOutput] {
return func(ctx context.Context) E.Either[error, exec.CommandOutput] {
return E.TryCatchError(func() (exec.CommandOutput, error) {
return GE.Exec(ctx, name, args, in)
})
}
}

View File

@@ -0,0 +1,26 @@
package readereither
import (
"context"
RE "github.com/ibm/fp-go/readereither/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[A any](f func(context.Context) (A, error)) ReaderEither[A] {
return RE.From0[ReaderEither[A]](f)
}
func From1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderEither[A] {
return RE.From1[ReaderEither[A]](f)
}
func From2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderEither[A] {
return RE.From2[ReaderEither[A]](f)
}
func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderEither[A] {
return RE.From3[ReaderEither[A]](f)
}

View File

@@ -0,0 +1,106 @@
package readereither
import (
"context"
R "github.com/ibm/fp-go/context/reader"
ET "github.com/ibm/fp-go/either"
O "github.com/ibm/fp-go/option"
RE "github.com/ibm/fp-go/readereither/generic"
)
func MakeReaderEither[A any](f func(context.Context) ET.Either[error, A]) ReaderEither[A] {
return RE.MakeReaderEither[ReaderEither[A]](f)
}
func FromEither[A any](e ET.Either[error, A]) ReaderEither[A] {
return RE.FromEither[ReaderEither[A]](e)
}
func RightReader[A any](r R.Reader[A]) ReaderEither[A] {
return RE.RightReader[R.Reader[A], ReaderEither[A]](r)
}
func LeftReader[A any](l R.Reader[error]) ReaderEither[A] {
return RE.LeftReader[R.Reader[error], ReaderEither[A]](l)
}
func Left[A any](l error) ReaderEither[A] {
return RE.Left[ReaderEither[A]](l)
}
func Right[A any](r A) ReaderEither[A] {
return RE.Right[ReaderEither[A]](r)
}
func FromReader[A any](r R.Reader[A]) ReaderEither[A] {
return RE.FromReader[R.Reader[A], ReaderEither[A]](r)
}
func MonadMap[A, B any](fa ReaderEither[A], f func(A) B) ReaderEither[B] {
return RE.MonadMap[ReaderEither[A], ReaderEither[B]](fa, f)
}
func Map[A, B any](f func(A) B) func(ReaderEither[A]) ReaderEither[B] {
return RE.Map[ReaderEither[A], ReaderEither[B]](f)
}
func MonadChain[A, B any](ma ReaderEither[A], f func(A) ReaderEither[B]) ReaderEither[B] {
return RE.MonadChain(ma, f)
}
func Chain[A, B any](f func(A) ReaderEither[B]) func(ReaderEither[A]) ReaderEither[B] {
return RE.Chain[ReaderEither[A]](f)
}
func Of[A any](a A) ReaderEither[A] {
return RE.Of[ReaderEither[A]](a)
}
func MonadAp[A, B any](fab ReaderEither[func(A) B], fa ReaderEither[A]) ReaderEither[B] {
return RE.MonadAp[ReaderEither[A], ReaderEither[B]](fab, fa)
}
func Ap[A, B any](fa ReaderEither[A]) func(ReaderEither[func(A) B]) ReaderEither[B] {
return RE.Ap[ReaderEither[A], ReaderEither[B], ReaderEither[func(A) B]](fa)
}
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) func(A) ReaderEither[A] {
return RE.FromPredicate[ReaderEither[A]](pred, onFalse)
}
func Fold[A, B any](onLeft func(error) R.Reader[B], onRight func(A) R.Reader[B]) func(ReaderEither[A]) R.Reader[B] {
return RE.Fold[ReaderEither[A]](onLeft, onRight)
}
func GetOrElse[A any](onLeft func(error) R.Reader[A]) func(ReaderEither[A]) R.Reader[A] {
return RE.GetOrElse[ReaderEither[A]](onLeft)
}
func OrElse[A any](onLeft func(error) ReaderEither[A]) func(ReaderEither[A]) ReaderEither[A] {
return RE.OrElse[ReaderEither[A]](onLeft)
}
func OrLeft[A any](onLeft func(error) R.Reader[error]) func(ReaderEither[A]) ReaderEither[A] {
return RE.OrLeft[ReaderEither[A], ReaderEither[A]](onLeft)
}
func Ask() ReaderEither[context.Context] {
return RE.Ask[ReaderEither[context.Context]]()
}
func Asks[A any](r R.Reader[A]) ReaderEither[A] {
return RE.Asks[R.Reader[A], ReaderEither[A]](r)
}
func MonadChainEitherK[A, B any](ma ReaderEither[A], f func(A) ET.Either[error, B]) ReaderEither[B] {
return RE.MonadChainEitherK[ReaderEither[A], ReaderEither[B]](ma, f)
}
func ChainEitherK[A, B any](f func(A) ET.Either[error, B]) func(ma ReaderEither[A]) ReaderEither[B] {
return RE.ChainEitherK[ReaderEither[A], ReaderEither[B]](f)
}
func ChainOptionK[A, B any](onNone func() error) func(func(A) O.Option[B]) func(ReaderEither[A]) ReaderEither[B] {
return RE.ChainOptionK[ReaderEither[A], ReaderEither[B]](onNone)
}

View File

@@ -0,0 +1,42 @@
package readereither
import (
RE "github.com/ibm/fp-go/readereither/generic"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[A any](a ReaderEither[A]) ReaderEither[T.Tuple1[A]] {
return RE.SequenceT1[
ReaderEither[A],
ReaderEither[T.Tuple1[A]],
](a)
}
func SequenceT2[A, B any](a ReaderEither[A], b ReaderEither[B]) ReaderEither[T.Tuple2[A, B]] {
return RE.SequenceT2[
ReaderEither[A],
ReaderEither[B],
ReaderEither[T.Tuple2[A, B]],
](a, b)
}
func SequenceT3[A, B, C any](a ReaderEither[A], b ReaderEither[B], c ReaderEither[C]) ReaderEither[T.Tuple3[A, B, C]] {
return RE.SequenceT3[
ReaderEither[A],
ReaderEither[B],
ReaderEither[C],
ReaderEither[T.Tuple3[A, B, C]],
](a, b, c)
}
func SequenceT4[A, B, C, D any](a ReaderEither[A], b ReaderEither[B], c ReaderEither[C], d ReaderEither[D]) ReaderEither[T.Tuple4[A, B, C, D]] {
return RE.SequenceT4[
ReaderEither[A],
ReaderEither[B],
ReaderEither[C],
ReaderEither[D],
ReaderEither[T.Tuple4[A, B, C, D]],
](a, b, c, d)
}

View File

@@ -0,0 +1,11 @@
// Package readereither implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error
package readereither
import (
"context"
RE "github.com/ibm/fp-go/readereither"
)
// ReaderEither is a specialization of the Reader monad for the typical golang scenario
type ReaderEither[A any] RE.ReaderEither[context.Context, error, A]

16
context/readerio/array.go Normal file
View File

@@ -0,0 +1,16 @@
package readerio
import (
IO "github.com/ibm/fp-go/io"
R "github.com/ibm/fp-go/readerio/generic"
)
// TraverseArray transforms an array
func TraverseArray[A, B any](f func(A) ReaderIO[B]) func([]A) ReaderIO[[]B] {
return R.TraverseArray[ReaderIO[B], ReaderIO[[]B], IO.IO[B], IO.IO[[]B], []A](f)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[A any](ma []ReaderIO[A]) ReaderIO[[]A] {
return R.SequenceArray[ReaderIO[A], ReaderIO[[]A]](ma)
}

27
context/readerio/from.go Normal file
View File

@@ -0,0 +1,27 @@
package readerio
import (
"context"
IO "github.com/ibm/fp-go/io"
R "github.com/ibm/fp-go/readerio/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[A any](f func(context.Context) IO.IO[A]) ReaderIO[A] {
return R.From0[ReaderIO[A]](f)
}
func From1[T1, A any](f func(context.Context, T1) IO.IO[A]) func(T1) ReaderIO[A] {
return R.From1[ReaderIO[A]](f)
}
func From2[T1, T2, A any](f func(context.Context, T1, T2) IO.IO[A]) func(T1, T2) ReaderIO[A] {
return R.From2[ReaderIO[A]](f)
}
func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) IO.IO[A]) func(T1, T2, T3) ReaderIO[A] {
return R.From3[ReaderIO[A]](f)
}

View File

@@ -0,0 +1,44 @@
package readerio
import (
"context"
R "github.com/ibm/fp-go/readerio/generic"
)
func MonadMap[A, B any](fa ReaderIO[A], f func(A) B) ReaderIO[B] {
return R.MonadMap[ReaderIO[A], ReaderIO[B]](fa, f)
}
func Map[A, B any](f func(A) B) func(ReaderIO[A]) ReaderIO[B] {
return R.Map[ReaderIO[A], ReaderIO[B]](f)
}
func MonadChain[A, B any](ma ReaderIO[A], f func(A) ReaderIO[B]) ReaderIO[B] {
return R.MonadChain(ma, f)
}
func Chain[A, B any](f func(A) ReaderIO[B]) func(ReaderIO[A]) ReaderIO[B] {
return R.Chain[ReaderIO[A]](f)
}
func Of[A any](a A) ReaderIO[A] {
return R.Of[ReaderIO[A]](a)
}
func MonadAp[A, B any](fab ReaderIO[func(A) B], fa ReaderIO[A]) ReaderIO[B] {
return R.MonadAp[ReaderIO[A], ReaderIO[B]](fab, fa)
}
func Ap[A, B any](fa ReaderIO[A]) func(ReaderIO[func(A) B]) ReaderIO[B] {
return R.Ap[ReaderIO[A], ReaderIO[B], ReaderIO[func(A) B]](fa)
}
func Ask() ReaderIO[context.Context] {
return R.Ask[ReaderIO[context.Context]]()
}
// Defer creates an IO by creating a brand new IO via a generator function, each time
func Defer[A any](gen func() ReaderIO[A]) ReaderIO[A] {
return R.Defer[ReaderIO[A]](gen)
}

View File

@@ -0,0 +1,65 @@
package readerio
import (
"context"
"fmt"
"strings"
"testing"
F "github.com/ibm/fp-go/function"
IO "github.com/ibm/fp-go/io"
T "github.com/ibm/fp-go/tuple"
"github.com/stretchr/testify/assert"
)
func GoFunction(ctx context.Context, data string) IO.IO[string] {
return func() string {
return strings.ToUpper(data)
}
}
func GoIntFunction(ctx context.Context, data string, number int) IO.IO[string] {
return func() string {
return fmt.Sprintf("%s: %d", data, number)
}
}
func TestReaderFrom(t *testing.T) {
ctx := context.Background()
f := From1(GoFunction)
result := f("input")(ctx)
assert.Equal(t, result(), "INPUT")
}
func MyFinalResult(left, right string) string {
return fmt.Sprintf("%s-%s", left, right)
}
func TestReadersFrom(t *testing.T) {
ctx := context.Background()
f1 := From1(GoFunction)
f2 := From2(GoIntFunction)
result1 := f1("input")(ctx)
result2 := f2("input", 10)(ctx)
result3 := MyFinalResult(result1(), result2())
h := F.Pipe1(
SequenceT2(f1("input"), f2("input", 10)),
Map(T.Tupled2(MyFinalResult)),
)
composedResult := h(ctx)
assert.Equal(t, result1(), "INPUT")
assert.Equal(t, result2(), "input: 10")
assert.Equal(t, result3, "INPUT-input: 10")
assert.Equal(t, composedResult(), "INPUT-input: 10")
}

View File

@@ -0,0 +1,42 @@
package readerio
import (
R "github.com/ibm/fp-go/readerio/generic"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[A any](a ReaderIO[A]) ReaderIO[T.Tuple1[A]] {
return R.SequenceT1[
ReaderIO[A],
ReaderIO[T.Tuple1[A]],
](a)
}
func SequenceT2[A, B any](a ReaderIO[A], b ReaderIO[B]) ReaderIO[T.Tuple2[A, B]] {
return R.SequenceT2[
ReaderIO[A],
ReaderIO[B],
ReaderIO[T.Tuple2[A, B]],
](a, b)
}
func SequenceT3[A, B, C any](a ReaderIO[A], b ReaderIO[B], c ReaderIO[C]) ReaderIO[T.Tuple3[A, B, C]] {
return R.SequenceT3[
ReaderIO[A],
ReaderIO[B],
ReaderIO[C],
ReaderIO[T.Tuple3[A, B, C]],
](a, b, c)
}
func SequenceT4[A, B, C, D any](a ReaderIO[A], b ReaderIO[B], c ReaderIO[C], d ReaderIO[D]) ReaderIO[T.Tuple4[A, B, C, D]] {
return R.SequenceT4[
ReaderIO[A],
ReaderIO[B],
ReaderIO[C],
ReaderIO[D],
ReaderIO[T.Tuple4[A, B, C, D]],
](a, b, c, d)
}

11
context/readerio/type.go Normal file
View File

@@ -0,0 +1,11 @@
// Package readerio implements a specialization of the ReaderIO monad assuming a golang context as the context of the monad
package readerio
import (
"context"
R "github.com/ibm/fp-go/readerio"
)
// ReaderIO is a specialization of the ReaderIO monad assuming a golang context as the context of the monad
type ReaderIO[A any] R.ReaderIO[context.Context, A]

View File

@@ -0,0 +1,18 @@
package readerioeither
import (
"context"
CIOE "github.com/ibm/fp-go/context/ioeither"
IOE "github.com/ibm/fp-go/ioeither"
)
// withContext wraps an existing ReaderIOEither and performs a context check for cancellation before delegating
func WithContext[A any](ma ReaderIOEither[A]) ReaderIOEither[A] {
return func(ctx context.Context) IOE.IOEither[error, A] {
if err := context.Cause(ctx); err != nil {
return IOE.Left[A](err)
}
return CIOE.WithContext(ctx, ma(ctx))
}
}

View File

@@ -0,0 +1,30 @@
package readerioeither
import (
"context"
RE "github.com/ibm/fp-go/readerioeither/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Eitherize0[A any](f func(context.Context) (A, error)) ReaderIOEither[A] {
return RE.Eitherize0[ReaderIOEither[A]](f)
}
func Eitherize1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderIOEither[A] {
return RE.Eitherize1[ReaderIOEither[A]](f)
}
func Eitherize2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderIOEither[A] {
return RE.Eitherize2[ReaderIOEither[A]](f)
}
func Eitherize3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderIOEither[A] {
return RE.Eitherize3[ReaderIOEither[A]](f)
}
func Eitherize4[T1, T2, T3, T4, A any](f func(context.Context, T1, T2, T3, T4) (A, error)) func(T1, T2, T3, T4) ReaderIOEither[A] {
return RE.Eitherize4[ReaderIOEither[A]](f)
}

View File

@@ -0,0 +1,14 @@
package readerioeither
import (
"context"
ET "github.com/ibm/fp-go/either"
EQ "github.com/ibm/fp-go/eq"
G "github.com/ibm/fp-go/readerioeither/generic"
)
// Eq implements the equals predicate for values contained in the IOEither monad
func Eq[A any](eq EQ.Eq[ET.Either[error, A]]) func(context.Context) EQ.Eq[ReaderIOEither[A]] {
return G.Eq[ReaderIOEither[A]](eq)
}

View File

@@ -0,0 +1,24 @@
package exec
import (
"context"
RIOE "github.com/ibm/fp-go/context/readerioeither"
"github.com/ibm/fp-go/exec"
F "github.com/ibm/fp-go/function"
GE "github.com/ibm/fp-go/internal/exec"
IOE "github.com/ibm/fp-go/ioeither"
)
var (
// Command executes a cancelable command
Command = F.Curry3(command)
)
func command(name string, args []string, in []byte) RIOE.ReaderIOEither[exec.CommandOutput] {
return func(ctx context.Context) IOE.IOEither[error, exec.CommandOutput] {
return IOE.TryCatchError(func() (exec.CommandOutput, error) {
return GE.Exec(ctx, name, args, in)
})
}
}

View File

@@ -0,0 +1,43 @@
package file
import (
"context"
"io"
"os"
RIOE "github.com/ibm/fp-go/context/readerioeither"
ET "github.com/ibm/fp-go/either"
F "github.com/ibm/fp-go/function"
"github.com/ibm/fp-go/internal/file"
IOE "github.com/ibm/fp-go/ioeither"
)
var (
openIOE = IOE.Eitherize1(os.Open)
// Open opens a file for reading within the given context
Open = F.Flow3(
openIOE,
RIOE.FromIOEither[*os.File],
RIOE.WithContext[*os.File],
)
)
// Close closes an object
func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] {
return RIOE.FromIOEither(func() ET.Either[error, any] {
return ET.TryCatchError(func() (any, error) {
return c, c.Close()
})
})
}
// ReadFile reads a file in the scope of a context
func ReadFile(path string) RIOE.ReaderIOEither[[]byte] {
return RIOE.WithResource[*os.File, []byte](Open(path), Close[*os.File])(func(r *os.File) RIOE.ReaderIOEither[[]byte] {
return func(ctx context.Context) IOE.IOEither[error, []byte] {
return IOE.MakeIO(func() ET.Either[error, []byte] {
return file.ReadAll(ctx, r)
})
}
})
}

View File

@@ -0,0 +1,30 @@
package readerioeither
import (
"context"
RE "github.com/ibm/fp-go/readerioeither/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[A any](f func(context.Context) func() (A, error)) ReaderIOEither[A] {
return RE.From0[ReaderIOEither[A]](f)
}
func From1[T1, A any](f func(context.Context, T1) func() (A, error)) func(T1) ReaderIOEither[A] {
return RE.From1[ReaderIOEither[A]](f)
}
func From2[T1, T2, A any](f func(context.Context, T1, T2) func() (A, error)) func(T1, T2) ReaderIOEither[A] {
return RE.From2[ReaderIOEither[A]](f)
}
func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) func() (A, error)) func(T1, T2, T3) ReaderIOEither[A] {
return RE.From3[ReaderIOEither[A]](f)
}
func From4[T1, T2, T3, T4, A any](f func(context.Context, T1, T2, T3, T4) func() (A, error)) func(T1, T2, T3, T4) ReaderIOEither[A] {
return RE.From4[ReaderIOEither[A]](f)
}

View File

@@ -0,0 +1,78 @@
package http
import (
"context"
"io"
"net/http"
B "github.com/ibm/fp-go/bytes"
RIOE "github.com/ibm/fp-go/context/readerioeither"
F "github.com/ibm/fp-go/function"
IOE "github.com/ibm/fp-go/ioeither"
IOEF "github.com/ibm/fp-go/ioeither/file"
J "github.com/ibm/fp-go/json"
)
type (
// Requester is a reader that constructs a request
Requester = RIOE.ReaderIOEither[*http.Request]
Client interface {
// Do can send an HTTP request considering a context
Do(Requester) RIOE.ReaderIOEither[*http.Response]
}
client struct {
delegate *http.Client
doIOE func(*http.Request) IOE.IOEither[error, *http.Response]
}
)
var (
NewRequest = RIOE.Eitherize3(http.NewRequestWithContext)
)
func (client client) Do(req Requester) RIOE.ReaderIOEither[*http.Response] {
return F.Pipe1(
req,
RIOE.ChainIOEitherK(client.doIOE),
)
}
// MakeClient creates an HTTP client proxy
func MakeClient(httpClient *http.Client) Client {
return client{delegate: httpClient, doIOE: IOE.Eitherize1(httpClient.Do)}
}
// ReadAll sends a request and reads the response as a byte array
func ReadAll(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] {
return func(req Requester) RIOE.ReaderIOEither[[]byte] {
doReq := client.Do(req)
return func(ctx context.Context) IOE.IOEither[error, []byte] {
return IOEF.ReadAll(F.Pipe2(
ctx,
doReq,
IOE.Map[error](func(resp *http.Response) io.ReadCloser {
return resp.Body
}),
),
)
}
}
}
// ReadText sends a request, reads the response and represents the response as a text string
func ReadText(client Client) func(Requester) RIOE.ReaderIOEither[string] {
return F.Flow2(
ReadAll(client),
RIOE.Map(B.ToString),
)
}
// ReadJson sends a request, reads the response and parses the response as JSON
func ReadJson[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] {
return F.Flow2(
ReadAll(client),
RIOE.ChainEitherK(J.Unmarshal[A]),
)
}

View File

@@ -0,0 +1,31 @@
package http
import (
"context"
"fmt"
"testing"
H "net/http"
)
type PostItem struct {
UserId uint `json:"userId"`
Id uint `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
func TestSendSingleRequest(t *testing.T) {
client := MakeClient(H.DefaultClient)
req1 := NewRequest("GET", "https://jsonplaceholder.typicode.com/posts/1", nil)
readItem := ReadJson[PostItem](client)
resp1 := readItem(req1)
resE := resp1(context.Background())()
fmt.Println(resE)
}

View File

@@ -0,0 +1,219 @@
package readerioeither
import (
"context"
"time"
R "github.com/ibm/fp-go/context/reader"
RIO "github.com/ibm/fp-go/context/readerio"
ET "github.com/ibm/fp-go/either"
ER "github.com/ibm/fp-go/errors"
F "github.com/ibm/fp-go/function"
IO "github.com/ibm/fp-go/io"
IOE "github.com/ibm/fp-go/ioeither"
O "github.com/ibm/fp-go/option"
RIE "github.com/ibm/fp-go/readerioeither/generic"
)
func FromEither[A any](e ET.Either[error, A]) ReaderIOEither[A] {
return RIE.FromEither[ReaderIOEither[A]](e)
}
func RightReader[A any](r R.Reader[A]) ReaderIOEither[A] {
return RIE.RightReader[R.Reader[A], ReaderIOEither[A]](r)
}
func LeftReader[A any](l R.Reader[error]) ReaderIOEither[A] {
return RIE.LeftReader[R.Reader[error], ReaderIOEither[A]](l)
}
func Left[A any](l error) ReaderIOEither[A] {
return RIE.Left[ReaderIOEither[A]](l)
}
func Right[A any](r A) ReaderIOEither[A] {
return RIE.Right[ReaderIOEither[A]](r)
}
func FromReader[A any](r R.Reader[A]) ReaderIOEither[A] {
return RIE.FromReader[R.Reader[A], ReaderIOEither[A]](r)
}
func MonadMap[A, B any](fa ReaderIOEither[A], f func(A) B) ReaderIOEither[B] {
return RIE.MonadMap[ReaderIOEither[A], ReaderIOEither[B]](fa, f)
}
func Map[A, B any](f func(A) B) func(ReaderIOEither[A]) ReaderIOEither[B] {
return RIE.Map[ReaderIOEither[A], ReaderIOEither[B]](f)
}
func MonadChain[A, B any](ma ReaderIOEither[A], f func(A) ReaderIOEither[B]) ReaderIOEither[B] {
return RIE.MonadChain(ma, f)
}
func Chain[A, B any](f func(A) ReaderIOEither[B]) func(ReaderIOEither[A]) ReaderIOEither[B] {
return RIE.Chain[ReaderIOEither[A]](f)
}
func Of[A any](a A) ReaderIOEither[A] {
return RIE.Of[ReaderIOEither[A]](a)
}
// withCancelCauseFunc wraps an IOEither such that in case of an error the cancel function is invoked
func withCancelCauseFunc[A any](cancel context.CancelCauseFunc, ma IOE.IOEither[error, A]) IOE.IOEither[error, A] {
return F.Pipe3(
ma,
IOE.Swap[error, A],
IOE.ChainFirstIOK[A, error, any](func(err error) IO.IO[any] {
return IO.MakeIO(func() any {
cancel(err)
return nil
})
}),
IOE.Swap[A, error],
)
}
// MonadAp implements the `Ap` function for a reader with context. It creates a sub-context that will
// be canceled if any of the input operations errors out or
func MonadAp[A, B any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] {
// context sensitive input
cfab := WithContext(fab)
cfa := WithContext(fa)
return func(ctx context.Context) IOE.IOEither[error, B] {
// quick check for cancellation
if err := context.Cause(ctx); err != nil {
return IOE.Left[B](err)
}
return func() ET.Either[error, B] {
// quick check for cancellation
if err := context.Cause(ctx); err != nil {
return ET.Left[B](err)
}
// create sub-contexts for fa and fab, so they can cancel one other
ctxSub, cancelSub := context.WithCancelCause(ctx)
defer cancelSub(nil) // cancel has to be called in all paths
fabIOE := withCancelCauseFunc(cancelSub, cfab(ctxSub))
faIOE := withCancelCauseFunc(cancelSub, cfa(ctxSub))
return IOE.MonadAp(fabIOE, faIOE)()
}
}
}
func Ap[A, B any](fa ReaderIOEither[A]) func(ReaderIOEither[func(A) B]) ReaderIOEither[B] {
return F.Bind2nd(MonadAp[A, B], fa)
}
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) func(A) ReaderIOEither[A] {
return RIE.FromPredicate[ReaderIOEither[A]](pred, onFalse)
}
func Fold[A, B any](onLeft func(error) RIO.ReaderIO[B], onRight func(A) RIO.ReaderIO[B]) func(ReaderIOEither[A]) RIO.ReaderIO[B] {
return RIE.Fold[RIO.ReaderIO[B], ReaderIOEither[A]](onLeft, onRight)
}
func GetOrElse[A any](onLeft func(error) RIO.ReaderIO[A]) func(ReaderIOEither[A]) RIO.ReaderIO[A] {
return RIE.GetOrElse[RIO.ReaderIO[A], ReaderIOEither[A]](onLeft)
}
func OrElse[A any](onLeft func(error) ReaderIOEither[A]) func(ReaderIOEither[A]) ReaderIOEither[A] {
return RIE.OrElse[ReaderIOEither[A]](onLeft)
}
func OrLeft[A any](onLeft func(error) RIO.ReaderIO[error]) func(ReaderIOEither[A]) ReaderIOEither[A] {
return RIE.OrLeft[ReaderIOEither[A], RIO.ReaderIO[error], ReaderIOEither[A]](onLeft)
}
func Ask() ReaderIOEither[context.Context] {
return RIE.Ask[ReaderIOEither[context.Context]]()
}
func Asks[A any](r R.Reader[A]) ReaderIOEither[A] {
return RIE.Asks[R.Reader[A], ReaderIOEither[A]](r)
}
func MonadChainEitherK[A, B any](ma ReaderIOEither[A], f func(A) ET.Either[error, B]) ReaderIOEither[B] {
return RIE.MonadChainEitherK[ReaderIOEither[A], ReaderIOEither[B]](ma, f)
}
func ChainEitherK[A, B any](f func(A) ET.Either[error, B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] {
return RIE.ChainEitherK[ReaderIOEither[A], ReaderIOEither[B]](f)
}
func ChainOptionK[A, B any](onNone func() error) func(func(A) O.Option[B]) func(ReaderIOEither[A]) ReaderIOEither[B] {
return RIE.ChainOptionK[ReaderIOEither[A], ReaderIOEither[B]](onNone)
}
func FromIOEither[A any](t IOE.IOEither[error, A]) ReaderIOEither[A] {
return RIE.FromIOEither[ReaderIOEither[A]](t)
}
func FromIO[A any](t IO.IO[A]) ReaderIOEither[A] {
return RIE.FromIO[ReaderIOEither[A]](t)
}
// Never returns a 'ReaderIOEither' that never returns, except if its context gets canceled
func Never[A any]() ReaderIOEither[A] {
return func(ctx context.Context) IOE.IOEither[error, A] {
return IOE.MakeIO(func() ET.Either[error, A] {
<-ctx.Done()
return ET.Left[A](context.Cause(ctx))
})
}
}
func MonadChainIOK[A, B any](ma ReaderIOEither[A], f func(A) IO.IO[B]) ReaderIOEither[B] {
return RIE.MonadChainIOK[ReaderIOEither[A], ReaderIOEither[B]](ma, f)
}
func ChainIOK[A, B any](f func(A) IO.IO[B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] {
return RIE.ChainIOK[ReaderIOEither[A], ReaderIOEither[B]](f)
}
func ChainIOEitherK[A, B any](f func(A) IOE.IOEither[error, B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] {
return RIE.ChainIOEitherK[ReaderIOEither[A], ReaderIOEither[B]](f)
}
// Delay creates an operation that passes in the value after some delay
func Delay[A any](delay time.Duration) func(ma ReaderIOEither[A]) ReaderIOEither[A] {
return func(ma ReaderIOEither[A]) ReaderIOEither[A] {
return func(ctx context.Context) IOE.IOEither[error, A] {
return IOE.MakeIO(func() ET.Either[error, A] {
// manage the timeout
timeoutCtx, cancelTimeout := context.WithTimeout(ctx, delay)
defer cancelTimeout()
// whatever comes first
select {
case <-timeoutCtx.Done():
return ma(ctx)()
case <-ctx.Done():
return ET.Left[A](context.Cause(ctx))
}
})
}
}
}
// Timer will return the current time after an initial delay
func Timer(delay time.Duration) ReaderIOEither[time.Time] {
return F.Pipe2(
IO.Now,
FromIO[time.Time],
Delay[time.Time](delay),
)
}
// Defer creates an IO by creating a brand new IO via a generator function, each time
func Defer[A any](gen func() ReaderIOEither[A]) ReaderIOEither[A] {
return RIE.Defer[ReaderIOEither[A]](gen)
}
// TryCatch wraps a reader returning a tuple as an error into ReaderIOEither
func TryCatch[A any](f func(context.Context) func() (A, error)) ReaderIOEither[A] {
return RIE.TryCatch[ReaderIOEither[A]](f, ER.IdentityError)
}

View File

@@ -0,0 +1,149 @@
package readerioeither
import (
"context"
"fmt"
"testing"
"time"
E "github.com/ibm/fp-go/either"
F "github.com/ibm/fp-go/function"
"github.com/ibm/fp-go/internal/utils"
"github.com/stretchr/testify/assert"
)
func TestInnerContextCancelSemantics(t *testing.T) {
// start with a simple context
outer := context.Background()
parent, parentCancel := context.WithCancel(outer)
defer parentCancel()
inner, innerCancel := context.WithCancel(parent)
defer innerCancel()
assert.NoError(t, parent.Err())
assert.NoError(t, inner.Err())
innerCancel()
assert.NoError(t, parent.Err())
assert.Error(t, inner.Err())
}
func TestOuterContextCancelSemantics(t *testing.T) {
// start with a simple context
outer := context.Background()
parent, outerCancel := context.WithCancel(outer)
defer outerCancel()
inner, innerCancel := context.WithCancel(parent)
defer innerCancel()
assert.NoError(t, parent.Err())
assert.NoError(t, inner.Err())
outerCancel()
assert.Error(t, parent.Err())
assert.Error(t, inner.Err())
}
func TestOuterAndInnerContextCancelSemantics(t *testing.T) {
// start with a simple context
outer := context.Background()
parent, outerCancel := context.WithCancel(outer)
defer outerCancel()
inner, innerCancel := context.WithCancel(parent)
defer innerCancel()
assert.NoError(t, parent.Err())
assert.NoError(t, inner.Err())
outerCancel()
innerCancel()
assert.Error(t, parent.Err())
assert.Error(t, inner.Err())
outerCancel()
innerCancel()
assert.Error(t, parent.Err())
assert.Error(t, inner.Err())
}
func TestCancelCauseSemantics(t *testing.T) {
// start with a simple context
outer := context.Background()
parent, outerCancel := context.WithCancelCause(outer)
defer outerCancel(nil)
inner := context.WithValue(parent, "key", "value")
assert.NoError(t, parent.Err())
assert.NoError(t, inner.Err())
err := fmt.Errorf("test error")
outerCancel(err)
assert.Error(t, parent.Err())
assert.Error(t, inner.Err())
assert.Equal(t, err, context.Cause(parent))
assert.Equal(t, err, context.Cause(inner))
}
func TestTimer(t *testing.T) {
delta := 3 * time.Second
timer := Timer(delta)
ctx := context.Background()
t0 := time.Now()
res := timer(ctx)()
t1 := time.Now()
assert.WithinDuration(t, t0.Add(delta), t1, time.Second)
assert.True(t, E.IsRight(res))
}
func TestCanceledApply(t *testing.T) {
// our error
err := fmt.Errorf("TestCanceledApply")
// the actual apply value errors out after some time
errValue := F.Pipe1(
Left[string](err),
Delay[string](time.Second),
)
// function never resolves
fct := Never[func(string) string]()
// apply the values, we expect an error after 1s
applied := F.Pipe1(
fct,
Ap[string, string](errValue),
)
res := applied(context.Background())()
assert.Equal(t, E.Left[string](err), res)
}
func TestRegularApply(t *testing.T) {
value := Of("Carsten")
fct := Of(utils.Upper)
applied := F.Pipe1(
fct,
Ap[string, string](value),
)
res := applied(context.Background())()
assert.Equal(t, E.Of[error]("CARSTEN"), res)
}

View File

@@ -0,0 +1,15 @@
package readerioeither
import (
F "github.com/ibm/fp-go/function"
RIE "github.com/ibm/fp-go/readerioeither/generic"
)
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource
func WithResource[R, A any](onCreate ReaderIOEither[R], onRelease func(R) ReaderIOEither[any]) func(func(R) ReaderIOEither[A]) ReaderIOEither[A] {
// wraps the callback functions with a context check
return F.Flow2(
F.Bind2nd(F.Flow2[func(R) ReaderIOEither[A], func(ReaderIOEither[A]) ReaderIOEither[A], R, ReaderIOEither[A], ReaderIOEither[A]], WithContext[A]),
RIE.WithResource[ReaderIOEither[A]](WithContext(onCreate), onRelease),
)
}

View File

@@ -0,0 +1,42 @@
package readerioeither
import (
RE "github.com/ibm/fp-go/readerioeither/generic"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[A any](a ReaderIOEither[A]) ReaderIOEither[T.Tuple1[A]] {
return RE.SequenceT1[
ReaderIOEither[A],
ReaderIOEither[T.Tuple1[A]],
](a)
}
func SequenceT2[A, B any](a ReaderIOEither[A], b ReaderIOEither[B]) ReaderIOEither[T.Tuple2[A, B]] {
return RE.SequenceT2[
ReaderIOEither[A],
ReaderIOEither[B],
ReaderIOEither[T.Tuple2[A, B]],
](a, b)
}
func SequenceT3[A, B, C any](a ReaderIOEither[A], b ReaderIOEither[B], c ReaderIOEither[C]) ReaderIOEither[T.Tuple3[A, B, C]] {
return RE.SequenceT3[
ReaderIOEither[A],
ReaderIOEither[B],
ReaderIOEither[C],
ReaderIOEither[T.Tuple3[A, B, C]],
](a, b, c)
}
func SequenceT4[A, B, C, D any](a ReaderIOEither[A], b ReaderIOEither[B], c ReaderIOEither[C], d ReaderIOEither[D]) ReaderIOEither[T.Tuple4[A, B, C, D]] {
return RE.SequenceT4[
ReaderIOEither[A],
ReaderIOEither[B],
ReaderIOEither[C],
ReaderIOEither[D],
ReaderIOEither[T.Tuple4[A, B, C, D]],
](a, b, c, d)
}

View File

@@ -0,0 +1,26 @@
package readerioeither
import (
IOE "github.com/ibm/fp-go/ioeither"
RE "github.com/ibm/fp-go/readerioeither/generic"
)
// TraverseArray transforms an array
func TraverseArray[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return RE.TraverseArray[ReaderIOEither[B], ReaderIOEither[[]B], IOE.IOEither[error, B], IOE.IOEither[error, []B], []A](f)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] {
return RE.SequenceArray[ReaderIOEither[A], ReaderIOEither[[]A]](ma)
}
// TraverseRecord transforms a record
func TraverseRecord[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return RE.TraverseRecord[ReaderIOEither[B], ReaderIOEither[map[K]B], IOE.IOEither[error, B], IOE.IOEither[error, map[K]B], map[K]A](f)
}
// SequenceRecord converts a homogeneous sequence of either into an either of sequence
func SequenceRecord[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return RE.SequenceRecord[ReaderIOEither[A], ReaderIOEither[map[K]A]](ma)
}

View File

@@ -0,0 +1,11 @@
// Package readerioeither implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error
package readerioeither
import (
"context"
RE "github.com/ibm/fp-go/readerioeither"
)
// ReaderIOEither is a specialization of the Reader monad for the typical golang scenario
type ReaderIOEither[A any] RE.ReaderIOEither[context.Context, error, A]