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:
15
io/apply.go
Normal file
15
io/apply.go
Normal 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
11
io/eq.go
Normal 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
81
io/generic/ap.go
Normal 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
14
io/generic/apply.go
Normal 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
20
io/generic/eq.go
Normal 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
133
io/generic/io.go
Normal 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
29
io/generic/logging.go
Normal 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
34
io/generic/retry.go
Normal 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
45
io/generic/sequence.go
Normal 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
56
io/generic/traverse.go
Normal 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
125
io/io.go
Normal 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
58
io/io_test.go
Normal 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
18
io/logging.go
Normal 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
25
io/logging_test.go
Normal 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
19
io/retry.go
Normal 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
32
io/retry_test.go
Normal 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
24
io/sequence.go
Normal 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
59
io/testing/laws.go
Normal 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
32
io/testing/laws_test.go
Normal 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
35
io/traverse.go
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user