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:
18
context/ioeither/ioeither.go
Normal file
18
context/ioeither/ioeither.go
Normal 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
15
context/reader/array.go
Normal 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
38
context/reader/curry.go
Normal 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
26
context/reader/from.go
Normal 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
43
context/reader/reader.go
Normal 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)
|
||||
}
|
60
context/reader/reader_test.go
Normal file
60
context/reader/reader_test.go
Normal 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")
|
||||
|
||||
}
|
42
context/reader/sequence.go
Normal file
42
context/reader/sequence.go
Normal 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
11
context/reader/type.go
Normal 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]
|
15
context/readereither/array.go
Normal file
15
context/readereither/array.go
Normal 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)
|
||||
}
|
17
context/readereither/cancel.go
Normal file
17
context/readereither/cancel.go
Normal 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)
|
||||
}
|
||||
}
|
38
context/readereither/curry.go
Normal file
38
context/readereither/curry.go
Normal 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)
|
||||
}
|
26
context/readereither/exec/exec.go
Normal file
26
context/readereither/exec/exec.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
26
context/readereither/from.go
Normal file
26
context/readereither/from.go
Normal 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)
|
||||
}
|
106
context/readereither/reader.go
Normal file
106
context/readereither/reader.go
Normal 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)
|
||||
}
|
42
context/readereither/sequence.go
Normal file
42
context/readereither/sequence.go
Normal 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)
|
||||
}
|
11
context/readereither/type.go
Normal file
11
context/readereither/type.go
Normal 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
16
context/readerio/array.go
Normal 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
27
context/readerio/from.go
Normal 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)
|
||||
}
|
44
context/readerio/reader.go
Normal file
44
context/readerio/reader.go
Normal 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)
|
||||
}
|
65
context/readerio/reader_test.go
Normal file
65
context/readerio/reader_test.go
Normal 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")
|
||||
|
||||
}
|
42
context/readerio/sequence.go
Normal file
42
context/readerio/sequence.go
Normal 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
11
context/readerio/type.go
Normal 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]
|
18
context/readerioeither/cancel.go
Normal file
18
context/readerioeither/cancel.go
Normal 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))
|
||||
}
|
||||
}
|
30
context/readerioeither/eitherize.go
Normal file
30
context/readerioeither/eitherize.go
Normal 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)
|
||||
}
|
14
context/readerioeither/eq.go
Normal file
14
context/readerioeither/eq.go
Normal 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)
|
||||
}
|
24
context/readerioeither/exec/exec.go
Normal file
24
context/readerioeither/exec/exec.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
43
context/readerioeither/file/file.go
Normal file
43
context/readerioeither/file/file.go
Normal 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)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
30
context/readerioeither/from.go
Normal file
30
context/readerioeither/from.go
Normal 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)
|
||||
}
|
78
context/readerioeither/http/request.go
Normal file
78
context/readerioeither/http/request.go
Normal 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]),
|
||||
)
|
||||
}
|
31
context/readerioeither/http/request_test.go
Normal file
31
context/readerioeither/http/request_test.go
Normal 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)
|
||||
}
|
219
context/readerioeither/reader.go
Normal file
219
context/readerioeither/reader.go
Normal 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)
|
||||
}
|
149
context/readerioeither/reader_test.go
Normal file
149
context/readerioeither/reader_test.go
Normal 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)
|
||||
}
|
15
context/readerioeither/resource.go
Normal file
15
context/readerioeither/resource.go
Normal 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),
|
||||
)
|
||||
}
|
42
context/readerioeither/sequence.go
Normal file
42
context/readerioeither/sequence.go
Normal 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)
|
||||
}
|
26
context/readerioeither/traverse.go
Normal file
26
context/readerioeither/traverse.go
Normal 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)
|
||||
}
|
11
context/readerioeither/type.go
Normal file
11
context/readerioeither/type.go
Normal 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]
|
Reference in New Issue
Block a user