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

fix: add ioeither

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-07-14 17:30:58 +02:00
parent e350f70659
commit 5020437b6a
80 changed files with 5436 additions and 2110 deletions

15
io/apply.go Normal file
View File

@@ -0,0 +1,15 @@
package io
import (
G "github.com/ibm/fp-go/io/generic"
M "github.com/ibm/fp-go/monoid"
S "github.com/ibm/fp-go/semigroup"
)
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[IO[A]] {
return G.ApplySemigroup[IO[A]](s)
}
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[IO[A]] {
return G.ApplicativeMonoid[IO[A]](m)
}

11
io/eq.go Normal file
View File

@@ -0,0 +1,11 @@
package io
import (
EQ "github.com/ibm/fp-go/eq"
G "github.com/ibm/fp-go/io/generic"
)
// Eq implements the equals predicate for values contained in the IO monad
func Eq[A any](e EQ.Eq[A]) EQ.Eq[IO[A]] {
return G.Eq[IO[A]](e)
}

81
io/generic/ap.go Normal file
View File

@@ -0,0 +1,81 @@
package generic
import (
G "github.com/ibm/fp-go/internal/apply"
)
const (
// useParallel is the feature flag to control if we use the parallel or the sequential implementation of ap
useParallel = true
)
// monadApSeq implements the applicative on a single thread by first executing mab and the ma
func monadApSeq[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB {
return MakeIO[GB](func() B {
return mab()(ma())
})
}
// monadApPar implements the applicative on two threads, the main thread executes mab and the actuall
// apply operation and the second thred computes ma. Communication between the threads happens via a channel
func monadApPar[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB {
return MakeIO[GB](func() B {
c := make(chan A)
go func() {
c <- ma()
close(c)
}()
return mab()(<-c)
})
}
// MonadAp implements the `ap` operation. Depending on a feature flag this will be sequential or parallel, the preferred implementation
// is parallel
func MonadAp[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB {
if useParallel {
return monadApPar[GA, GB](mab, ma)
}
return monadApSeq[GA, GB](mab, ma)
}
// MonadApFirst combines two effectful actions, keeping only the result of the first.
func MonadApFirst[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](first GA, second GB) GA {
return G.MonadApFirst(
MonadAp[GB, GA, GBA, B, A],
MonadMap[GA, GBA, A, func(B) A],
first,
second,
)
}
// ApFirst combines two effectful actions, keeping only the result of the first.
func ApFirst[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](second GB) func(GA) GA {
return G.ApFirst(
MonadAp[GB, GA, GBA, B, A],
MonadMap[GA, GBA, A, func(B) A],
second,
)
}
// MonadApSecond combines two effectful actions, keeping only the result of the second.
func MonadApSecond[GA ~func() A, GB ~func() B, GBB ~func() func(B) B, A, B any](first GA, second GB) GB {
return G.MonadApSecond(
MonadAp[GB, GB, GBB, B, B],
MonadMap[GA, GBB, A, func(B) B],
first,
second,
)
}
// ApSecond combines two effectful actions, keeping only the result of the second.
func ApSecond[GA ~func() A, GB ~func() B, GBB ~func() func(B) B, A, B any](second GB) func(GA) GB {
return G.ApSecond(
MonadAp[GB, GB, GBB, B, B],
MonadMap[GA, GBB, A, func(B) B],
second,
)
}

14
io/generic/apply.go Normal file
View File

@@ -0,0 +1,14 @@
package generic
import (
M "github.com/ibm/fp-go/monoid"
S "github.com/ibm/fp-go/semigroup"
)
func ApplySemigroup[GA ~func() A, A any](s S.Semigroup[A]) S.Semigroup[GA] {
return S.ApplySemigroup(MonadMap[GA, func() func(A) A, A, func(A) A], MonadAp[GA, GA, func() func(A) A, A, A], s)
}
func ApplicativeMonoid[GA ~func() A, A any](m M.Monoid[A]) M.Monoid[GA] {
return M.ApplicativeMonoid(Of[GA, A], MonadMap[GA, func() func(A) A, A, func(A) A], MonadAp[GA, GA, func() func(A) A, A, A], m)
}

20
io/generic/eq.go Normal file
View File

@@ -0,0 +1,20 @@
package generic
import (
EQ "github.com/ibm/fp-go/eq"
G "github.com/ibm/fp-go/internal/eq"
)
// Eq implements the equals predicate for values contained in the IO monad
func Eq[GA ~func() A, A any](e EQ.Eq[A]) EQ.Eq[GA] {
// comparator for the monad
eq := G.Eq(
MonadMap[GA, func() func(A) bool, A, func(A) bool],
MonadAp[GA, func() bool, func() func(A) bool, A, bool],
e,
)
// eagerly execute
return EQ.FromEquals(func(l, r GA) bool {
return eq(l, r)()
})
}

133
io/generic/io.go Normal file
View File

@@ -0,0 +1,133 @@
package generic
import (
"sync"
"time"
F "github.com/ibm/fp-go/function"
C "github.com/ibm/fp-go/internal/chain"
)
// type IO[A any] = func() A
func MakeIO[GA ~func() A, A any](f func() A) GA {
return f
}
func Of[GA ~func() A, A any](a A) GA {
return MakeIO[GA](F.Constant(a))
}
func FromIO[GA ~func() A, A any](a GA) GA {
return a
}
// FromImpure converts a side effect without a return value into a side effect that returns any
func FromImpure[GA ~func() any, IMP ~func()](f IMP) GA {
return MakeIO[GA](func() any {
f()
return nil
})
}
func MonadOf[GA ~func() A, A any](a A) GA {
return MakeIO[GA](F.Constant(a))
}
func MonadMap[GA ~func() A, GB ~func() B, A, B any](fa GA, f func(A) B) GB {
return MakeIO[GB](func() B {
return F.Pipe1(fa(), f)
})
}
func Map[GA ~func() A, GB ~func() B, A, B any](f func(A) B) func(GA) GB {
return F.Bind2nd(MonadMap[GA, GB, A, B], f)
}
func MonadMapTo[GA ~func() A, GB ~func() B, A, B any](fa GA, b B) GB {
return MonadMap[GA, GB](fa, F.Constant1[A](b))
}
func MapTo[GA ~func() A, GB ~func() B, A, B any](b B) func(GA) GB {
return F.Bind2nd(MonadMapTo[GA, GB, A, B], b)
}
// MonadChain composes computations in sequence, using the return value of one computation to determine the next computation.
func MonadChain[GA ~func() A, GB ~func() B, A, B any](fa GA, f func(A) GB) GB {
return MakeIO[GB](func() B {
return F.Pipe1(fa(), f)()
})
}
// Chain composes computations in sequence, using the return value of one computation to determine the next computation.
func Chain[GA ~func() A, GB ~func() B, A, B any](f func(A) GB) func(GA) GB {
return F.Bind2nd(MonadChain[GA, GB, A, B], f)
}
// MonadChainTo composes computations in sequence, ignoring the return value of the first computation
func MonadChainTo[GA ~func() A, GB ~func() B, A, B any](fa GA, fb GB) GB {
return MonadChain(fa, F.Constant1[A](fb))
}
// ChainTo composes computations in sequence, ignoring the return value of the first computation
func ChainTo[GA ~func() A, GB ~func() B, A, B any](fb GB) func(GA) GB {
return F.Bind2nd(MonadChainTo[GA, GB, A, B], fb)
}
// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and
// keeping only the result of the first.
func MonadChainFirst[GA ~func() A, GB ~func() B, A, B any](fa GA, f func(A) GB) GA {
return C.MonadChainFirst(MonadChain[GA, GA, A, A], MonadMap[GB, GA, B, A], fa, f)
}
// ChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and
// keeping only the result of the first.
func ChainFirst[GA ~func() A, GB ~func() B, A, B any](f func(A) GB) func(GA) GA {
return C.ChainFirst(MonadChain[GA, GA, A, A], MonadMap[GB, GA, B, A], f)
}
func Ap[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](ma GA) func(GAB) GB {
return F.Bind2nd(MonadAp[GA, GB, GAB, A, B], ma)
}
func Flatten[GA ~func() A, GAA ~func() GA, A any](mma GAA) GA {
return mma()
}
// Memoize computes the value of the provided IO monad lazily but exactly once
func Memoize[GA ~func() A, A any](ma GA) GA {
// synchronization primitives
var once sync.Once
var result A
// callback
gen := func() {
result = ma()
}
// returns our memoized wrapper
return func() A {
once.Do(gen)
return result
}
}
// Delay creates an operation that passes in the value after some delay
func Delay[GA ~func() A, A any](delay time.Duration) func(GA) GA {
return func(ga GA) GA {
return MakeIO[GA](func() A {
time.Sleep(delay)
return ga()
})
}
}
// Now returns the current timestamp
func Now[GA ~func() time.Time]() GA {
return MakeIO[GA](time.Now)
}
// Defer creates an IO by creating a brand new IO via a generator function, each time
func Defer[GA ~func() A, A any](gen func() GA) GA {
return MakeIO[GA](func() A {
return gen()()
})
}

29
io/generic/logging.go Normal file
View File

@@ -0,0 +1,29 @@
package generic
import (
"log"
Logging "github.com/ibm/fp-go/logging"
)
func Logger[GA ~func() any, A any](loggers ...*log.Logger) func(string) func(A) GA {
_, right := Logging.LoggingCallbacks(loggers...)
return func(prefix string) func(A) GA {
return func(a A) GA {
return FromImpure[GA](func() {
right("%s: %v", prefix, a)
})
}
}
}
func Logf[GA ~func() any, A any](loggers ...*log.Logger) func(string) func(A) GA {
_, right := Logging.LoggingCallbacks(loggers...)
return func(prefix string) func(A) GA {
return func(a A) GA {
return FromImpure[GA](func() {
right(prefix, a)
})
}
}
}

34
io/generic/retry.go Normal file
View File

@@ -0,0 +1,34 @@
package generic
import (
R "github.com/ibm/fp-go/retry"
G "github.com/ibm/fp-go/retry/generic"
)
type retryStatusIO = func() R.RetryStatus
// Retry combinator for actions that don't raise exceptions, but
// signal in their type the outcome has failed. Examples are the
// `Option`, `Either` and `EitherT` monads.
//
// policy - refers to the retry policy
// action - converts a status into an operation to be executed
// check - checks if the result of the action needs to be retried
func Retrying[GA ~func() A, A any](
policy R.RetryPolicy,
action func(R.RetryStatus) GA,
check func(A) bool,
) GA {
// get an implementation for the types
return G.Retrying(
Chain[GA, GA, A, A],
Chain[retryStatusIO, GA, R.RetryStatus, A],
Of[GA, A],
Of[retryStatusIO, R.RetryStatus],
Delay[retryStatusIO, R.RetryStatus],
policy,
action,
check,
)
}

45
io/generic/sequence.go Normal file
View File

@@ -0,0 +1,45 @@
package generic
import (
"github.com/ibm/fp-go/internal/apply"
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[GA ~func() A, GTA ~func() T.Tuple1[A], A any](a GA) GTA {
return apply.SequenceT1(
Map[GA, GTA, A, T.Tuple1[A]],
a,
)
}
func SequenceT2[GA ~func() A, GB ~func() B, GTAB ~func() T.Tuple2[A, B], A, B any](a GA, b GB) GTAB {
return apply.SequenceT2(
Map[GA, func() func(B) T.Tuple2[A, B], A, func(B) T.Tuple2[A, B]],
Ap[GB, GTAB, func() func(B) T.Tuple2[A, B], B, T.Tuple2[A, B]],
a, b,
)
}
func SequenceT3[GA ~func() A, GB ~func() B, GC ~func() C, GTABC ~func() T.Tuple3[A, B, C], A, B, C any](a GA, b GB, c GC) GTABC {
return apply.SequenceT3(
Map[GA, func() func(B) func(C) T.Tuple3[A, B, C], A, func(B) func(C) T.Tuple3[A, B, C]],
Ap[GB, func() func(C) T.Tuple3[A, B, C], func() func(B) func(C) T.Tuple3[A, B, C], B, func(C) T.Tuple3[A, B, C]],
Ap[GC, GTABC, func() func(C) T.Tuple3[A, B, C], C, T.Tuple3[A, B, C]],
a, b, c,
)
}
func SequenceT4[GA ~func() A, GB ~func() B, GC ~func() C, GD ~func() D, GTABCD ~func() T.Tuple4[A, B, C, D], A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD {
return apply.SequenceT4(
Map[GA, func() func(B) func(C) func(D) T.Tuple4[A, B, C, D], A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
Ap[GB, func() func(C) func(D) T.Tuple4[A, B, C, D], func() func(B) func(C) func(D) T.Tuple4[A, B, C, D], B, func(C) func(D) T.Tuple4[A, B, C, D]],
Ap[GC, func() func(D) T.Tuple4[A, B, C, D], func() func(C) func(D) T.Tuple4[A, B, C, D], C, func(D) T.Tuple4[A, B, C, D]],
Ap[GD, GTABCD, func() func(D) T.Tuple4[A, B, C, D], D, T.Tuple4[A, B, C, D]],
a, b, c, d,
)
}

56
io/generic/traverse.go Normal file
View File

@@ -0,0 +1,56 @@
package generic
import (
F "github.com/ibm/fp-go/function"
RA "github.com/ibm/fp-go/internal/array"
RR "github.com/ibm/fp-go/internal/record"
)
func MonadTraverseArray[GB ~func() B, GBS ~func() BBS, AAS ~[]A, BBS ~[]B, A, B any](tas AAS, f func(A) GB) GBS {
return RA.MonadTraverse(
Of[GBS, BBS],
Map[GBS, func() func(B) BBS, BBS, func(B) BBS],
Ap[GB, GBS, func() func(B) BBS, B, BBS],
tas,
f,
)
}
func TraverseArray[GB ~func() B, GBS ~func() BBS, AAS ~[]A, BBS ~[]B, A, B any](f func(A) GB) func(AAS) GBS {
return RA.Traverse[AAS](
Of[GBS, BBS],
Map[GBS, func() func(B) BBS, BBS, func(B) BBS],
Ap[GB, GBS, func() func(B) BBS, B, BBS],
f,
)
}
func SequenceArray[GA ~func() A, GAS ~func() AAS, AAS ~[]A, GAAS ~[]GA, A any](tas GAAS) GAS {
return MonadTraverseArray[GA, GAS](tas, F.Identity[GA])
}
// MonadTraverseRecord transforms a record using an IO transform an IO of a record
func MonadTraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, K comparable, A, B any](ma MA, f func(A) GB) GBS {
return RR.MonadTraverse[MA](
Of[GBS, MB],
Map[GBS, func() func(B) MB, MB, func(B) MB],
Ap[GB, GBS, func() func(B) MB, B, MB],
ma, f,
)
}
// TraverseRecord transforms a record using an IO transform an IO of a record
func TraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, K comparable, A, B any](f func(A) GB) func(MA) GBS {
return RR.Traverse[MA](
Of[GBS, MB],
Map[GBS, func() func(B) MB, MB, func(B) MB],
Ap[GB, GBS, func() func(B) MB, B, MB],
f,
)
}
func SequenceRecord[GA ~func() A, GAS ~func() AAS, AAS ~map[K]A, GAAS ~map[K]GA, K comparable, A any](tas GAAS) GAS {
return MonadTraverseRecord[GA, GAS](tas, F.Identity[GA])
}

125
io/io.go Normal file
View File

@@ -0,0 +1,125 @@
package io
import (
"time"
G "github.com/ibm/fp-go/io/generic"
)
// IO represents a synchronous computation that cannot fail
// refer to [https://andywhite.xyz/posts/2021-01-27-rte-foundations/#ioltagt] for more details
type IO[A any] func() A
func MakeIO[A any](f func() A) IO[A] {
return G.MakeIO[IO[A]](f)
}
func Of[A any](a A) IO[A] {
return G.Of[IO[A]](a)
}
func FromIO[A any](a IO[A]) IO[A] {
return G.FromIO(a)
}
// FromImpure converts a side effect without a return value into a side effect that returns any
func FromImpure(f func()) IO[any] {
return G.FromImpure[IO[any]](f)
}
func MonadOf[A any](a A) IO[A] {
return G.MonadOf[IO[A]](a)
}
func MonadMap[A, B any](fa IO[A], f func(A) B) IO[B] {
return G.MonadMap[IO[A], IO[B]](fa, f)
}
func Map[A, B any](f func(A) B) func(fa IO[A]) IO[B] {
return G.Map[IO[A], IO[B]](f)
}
func MonadMapTo[A, B any](fa IO[A], b B) IO[B] {
return G.MonadMapTo[IO[A], IO[B]](fa, b)
}
func MapTo[A, B any](b B) func(IO[A]) IO[B] {
return G.MapTo[IO[A], IO[B]](b)
}
// MonadChain composes computations in sequence, using the return value of one computation to determine the next computation.
func MonadChain[A, B any](fa IO[A], f func(A) IO[B]) IO[B] {
return G.MonadChain(fa, f)
}
// Chain composes computations in sequence, using the return value of one computation to determine the next computation.
func Chain[A, B any](f func(A) IO[B]) func(IO[A]) IO[B] {
return G.Chain[IO[A]](f)
}
func MonadAp[B, A any](mab IO[func(A) B], ma IO[A]) IO[B] {
return G.MonadAp[IO[A], IO[B]](mab, ma)
}
func Ap[B, A any](ma IO[A]) func(IO[func(A) B]) IO[B] {
return G.Ap[IO[A], IO[B], IO[func(A) B]](ma)
}
func Flatten[A any](mma IO[IO[A]]) IO[A] {
return G.Flatten(mma)
}
// Memoize computes the value of the provided IO monad lazily but exactly once
func Memoize[A any](ma IO[A]) IO[A] {
return G.Memoize(ma)
}
// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and
// keeping only the result of the first.
func MonadChainFirst[A, B any](fa IO[A], f func(A) IO[B]) IO[A] {
return G.MonadChainFirst(fa, f)
}
// ChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and
// keeping only the result of the first.
func ChainFirst[A, B any](f func(A) IO[B]) func(IO[A]) IO[A] {
return G.ChainFirst[IO[A]](f)
}
// MonadApFirst combines two effectful actions, keeping only the result of the first.
func MonadApFirst[A, B any](first IO[A], second IO[B]) IO[A] {
return G.MonadApFirst[IO[A], IO[B], IO[func(B) A]](first, second)
}
// ApFirst combines two effectful actions, keeping only the result of the first.
func ApFirst[A, B any](second IO[B]) func(IO[A]) IO[A] {
return G.ApFirst[IO[A], IO[B], IO[func(B) A]](second)
}
// MonadApSecond combines two effectful actions, keeping only the result of the second.
func MonadApSecond[A, B any](first IO[A], second IO[B]) IO[B] {
return G.MonadApSecond[IO[A], IO[B], IO[func(B) B]](first, second)
}
// ApSecond combines two effectful actions, keeping only the result of the second.
func ApSecond[A, B any](second IO[B]) func(IO[A]) IO[B] {
return G.ApSecond[IO[A], IO[B], IO[func(B) B]](second)
}
// MonadChainTo composes computations in sequence, ignoring the return value of the first computation
func MonadChainTo[A, B any](fa IO[A], fb IO[B]) IO[B] {
return G.MonadChainTo(fa, fb)
}
// ChainTo composes computations in sequence, ignoring the return value of the first computation
func ChainTo[A, B any](fb IO[B]) func(IO[A]) IO[B] {
return G.ChainTo[IO[A]](fb)
}
// Now returns the current timestamp
var Now = G.Now[IO[time.Time]]()
// Defer creates an IO by creating a brand new IO via a generator function, each time
func Defer[A any](gen func() IO[A]) IO[A] {
return G.Defer[IO[A]](gen)
}

58
io/io_test.go Normal file
View File

@@ -0,0 +1,58 @@
package io
import (
"math/rand"
"testing"
F "github.com/ibm/fp-go/function"
"github.com/ibm/fp-go/internal/utils"
"github.com/stretchr/testify/assert"
)
func TestMap(t *testing.T) {
assert.Equal(t, 2, F.Pipe1(Of(1), Map(utils.Double))())
}
func TestChain(t *testing.T) {
f := func(n int) IO[int] {
return Of(n * 2)
}
assert.Equal(t, 2, F.Pipe1(Of(1), Chain(f))())
}
func TestAp(t *testing.T) {
assert.Equal(t, 2, F.Pipe1(Of(utils.Double), Ap[int, int](Of(1)))())
}
func TestFlatten(t *testing.T) {
assert.Equal(t, 1, F.Pipe1(Of(Of(1)), Flatten[int])())
}
func TestMemoize(t *testing.T) {
data := Memoize(MakeIO(rand.Int))
value1 := data()
value2 := data()
assert.Equal(t, value1, value2)
}
func TestApFirst(t *testing.T) {
x := F.Pipe1(
Of("a"),
ApFirst[string](Of("b")),
)
assert.Equal(t, "a", x())
}
func TestApSecond(t *testing.T) {
x := F.Pipe1(
Of("a"),
ApSecond[string](Of("b")),
)
assert.Equal(t, "b", x())
}

18
io/logging.go Normal file
View File

@@ -0,0 +1,18 @@
package io
import (
"log"
G "github.com/ibm/fp-go/io/generic"
)
// Logger constructs a logger function that can be used with ChainXXXIOK
func Logger[A any](loggers ...*log.Logger) func(string) func(A) IO[any] {
return G.Logger[IO[any], A](loggers...)
}
// Logf constructs a logger function that can be used with ChainXXXIOK
// the string prefix contains the format string for the log value
func Logf[A any](loggers ...*log.Logger) func(string) func(A) IO[any] {
return G.Logf[IO[any], A](loggers...)
}

25
io/logging_test.go Normal file
View File

@@ -0,0 +1,25 @@
package io
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogger(t *testing.T) {
l := Logger[int]()
lio := l("out")
assert.Equal(t, nil, lio(10)())
}
func TestLogf(t *testing.T) {
l := Logf[int]()
lio := l("Value is %d")
assert.Equal(t, nil, lio(10)())
}

19
io/retry.go Normal file
View File

@@ -0,0 +1,19 @@
package io
import (
G "github.com/ibm/fp-go/io/generic"
R "github.com/ibm/fp-go/retry"
)
// Retrying will retry the actions according to the check policy
//
// policy - refers to the retry policy
// action - converts a status into an operation to be executed
// check - checks if the result of the action needs to be retried
func Retrying[A any](
policy R.RetryPolicy,
action func(R.RetryStatus) IO[A],
check func(A) bool,
) IO[A] {
return G.Retrying(policy, action, check)
}

32
io/retry_test.go Normal file
View File

@@ -0,0 +1,32 @@
package io
import (
"fmt"
"strings"
"testing"
"time"
R "github.com/ibm/fp-go/retry"
"github.com/stretchr/testify/assert"
)
var expLogBackoff = R.ExponentialBackoff(10)
// our retry policy with a 1s cap
var testLogPolicy = R.CapDelay(
2*time.Second,
R.Monoid.Concat(expLogBackoff, R.LimitRetries(20)),
)
func TestRetry(t *testing.T) {
action := func(status R.RetryStatus) IO[string] {
return Of(fmt.Sprintf("Retrying %d", status.IterNumber))
}
check := func(value string) bool {
return !strings.Contains(value, "5")
}
r := Retrying(testLogPolicy, action, check)
assert.Equal(t, "Retrying 5", r())
}

24
io/sequence.go Normal file
View File

@@ -0,0 +1,24 @@
package io
import (
G "github.com/ibm/fp-go/io/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 IO[A]) IO[T.Tuple1[A]] {
return G.SequenceT1[IO[A], IO[T.Tuple1[A]]](a)
}
func SequenceT2[A, B any](a IO[A], b IO[B]) IO[T.Tuple2[A, B]] {
return G.SequenceT2[IO[A], IO[B], IO[T.Tuple2[A, B]]](a, b)
}
func SequenceT3[A, B, C any](a IO[A], b IO[B], c IO[C]) IO[T.Tuple3[A, B, C]] {
return G.SequenceT3[IO[A], IO[B], IO[C], IO[T.Tuple3[A, B, C]]](a, b, c)
}
func SequenceT4[A, B, C, D any](a IO[A], b IO[B], c IO[C], d IO[D]) IO[T.Tuple4[A, B, C, D]] {
return G.SequenceT4[IO[A], IO[B], IO[C], IO[D], IO[T.Tuple4[A, B, C, D]]](a, b, c, d)
}

59
io/testing/laws.go Normal file
View File

@@ -0,0 +1,59 @@
package testing
import (
"testing"
EQ "github.com/ibm/fp-go/eq"
L "github.com/ibm/fp-go/internal/monad/testing"
"github.com/ibm/fp-go/io"
)
// AssertLaws asserts the apply monad laws for the `Either` monad
func AssertLaws[A, B, C any](t *testing.T,
eqa EQ.Eq[A],
eqb EQ.Eq[B],
eqc EQ.Eq[C],
ab func(A) B,
bc func(B) C,
) func(a A) bool {
return L.AssertLaws(t,
io.Eq(eqa),
io.Eq(eqb),
io.Eq(eqc),
io.Of[A],
io.Of[B],
io.Of[C],
io.Of[func(A) A],
io.Of[func(A) B],
io.Of[func(B) C],
io.Of[func(func(A) B) B],
io.MonadMap[A, A],
io.MonadMap[A, B],
io.MonadMap[A, C],
io.MonadMap[B, C],
io.MonadMap[func(B) C, func(func(A) B) func(A) C],
io.MonadChain[A, A],
io.MonadChain[A, B],
io.MonadChain[A, C],
io.MonadChain[B, C],
io.MonadAp[A, A],
io.MonadAp[B, A],
io.MonadAp[C, B],
io.MonadAp[C, A],
io.MonadAp[B, func(A) B],
io.MonadAp[func(A) C, func(A) B],
ab,
bc,
)
}

32
io/testing/laws_test.go Normal file
View File

@@ -0,0 +1,32 @@
package testing
import (
"fmt"
"testing"
EQ "github.com/ibm/fp-go/eq"
"github.com/stretchr/testify/assert"
)
func TestMonadLaws(t *testing.T) {
// some comparison
eqa := EQ.FromStrictEquals[bool]()
eqb := EQ.FromStrictEquals[int]()
eqc := EQ.FromStrictEquals[string]()
ab := func(a bool) int {
if a {
return 1
}
return 0
}
bc := func(b int) string {
return fmt.Sprintf("value %d", b)
}
laws := AssertLaws(t, eqa, eqb, eqc, ab, bc)
assert.True(t, laws(true))
assert.True(t, laws(false))
}

35
io/traverse.go Normal file
View File

@@ -0,0 +1,35 @@
package io
import (
G "github.com/ibm/fp-go/io/generic"
)
func MonadTraverseArray[A, B any](tas []A, f func(A) IO[B]) IO[[]B] {
return G.MonadTraverseArray[IO[B], IO[[]B]](tas, f)
}
// TraverseArray applies a function returning an [IO] to all elements in an array and the
// transforms this into an [IO] of that array
func TraverseArray[A, B any](f func(A) IO[B]) func([]A) IO[[]B] {
return G.TraverseArray[IO[B], IO[[]B], []A](f)
}
// SequenceArray converts an array of [IO] to an [IO] of an array
func SequenceArray[A any](tas []IO[A]) IO[[]A] {
return G.SequenceArray[IO[A], IO[[]A]](tas)
}
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) IO[B]) IO[map[K]B] {
return G.MonadTraverseRecord[IO[B], IO[map[K]B]](tas, f)
}
// TraverseArray applies a function returning an [IO] to all elements in a record and the
// transforms this into an [IO] of that record
func TraverseRecord[K comparable, A, B any](f func(A) IO[B]) func(map[K]A) IO[map[K]B] {
return G.TraverseRecord[IO[B], IO[map[K]B], map[K]A](f)
}
// SequenceRecord converts a record of [IO] to an [IO] of a record
func SequenceRecord[K comparable, A any](tas map[K]IO[A]) IO[map[K]A] {
return G.SequenceRecord[IO[A], IO[map[K]A]](tas)
}