1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-07 23:03:15 +02:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Dr. Carsten Leue
eb7fc9f77b fix: better tests for Lazy
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 10:46:07 +01:00
Dr. Carsten Leue
fd0550e71b fix: better test coverage
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 10:35:53 +01:00
44 changed files with 12764 additions and 2224 deletions

View File

@@ -19,8 +19,8 @@ import (
"fmt"
"testing"
E "github.com/IBM/fp-go/v2/either"
EQ "github.com/IBM/fp-go/v2/eq"
"github.com/IBM/fp-go/v2/eq"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
@@ -28,82 +28,82 @@ var (
errTest = fmt.Errorf("test failure")
// Eq is the equal predicate checking if objects are equal
Eq = EQ.FromEquals(assert.ObjectsAreEqual)
Eq = eq.FromEquals(assert.ObjectsAreEqual)
)
func wrap1[T any](wrapped func(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool, t *testing.T, expected T) func(actual T) E.Either[error, T] {
return func(actual T) E.Either[error, T] {
func wrap1[T any](wrapped func(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool, t *testing.T, expected T) result.Kleisli[T, T] {
return func(actual T) Result[T] {
ok := wrapped(t, expected, actual)
if ok {
return E.Of[error](actual)
return result.Of(actual)
}
return E.Left[T](errTest)
return result.Left[T](errTest)
}
}
// NotEqual tests if the expected and the actual values are not equal
func NotEqual[T any](t *testing.T, expected T) func(actual T) E.Either[error, T] {
func NotEqual[T any](t *testing.T, expected T) result.Kleisli[T, T] {
return wrap1(assert.NotEqual, t, expected)
}
// Equal tests if the expected and the actual values are equal
func Equal[T any](t *testing.T, expected T) func(actual T) E.Either[error, T] {
func Equal[T any](t *testing.T, expected T) result.Kleisli[T, T] {
return wrap1(assert.Equal, t, expected)
}
// Length tests if an array has the expected length
func Length[T any](t *testing.T, expected int) func(actual []T) E.Either[error, []T] {
return func(actual []T) E.Either[error, []T] {
func Length[T any](t *testing.T, expected int) result.Kleisli[[]T, []T] {
return func(actual []T) Result[[]T] {
ok := assert.Len(t, actual, expected)
if ok {
return E.Of[error](actual)
return result.Of(actual)
}
return E.Left[[]T](errTest)
return result.Left[[]T](errTest)
}
}
// NoError validates that there is no error
func NoError[T any](t *testing.T) func(actual E.Either[error, T]) E.Either[error, T] {
return func(actual E.Either[error, T]) E.Either[error, T] {
return E.MonadFold(actual, func(e error) E.Either[error, T] {
func NoError[T any](t *testing.T) result.Operator[T, T] {
return func(actual Result[T]) Result[T] {
return result.MonadFold(actual, func(e error) Result[T] {
assert.NoError(t, e)
return E.Left[T](e)
}, func(value T) E.Either[error, T] {
return result.Left[T](e)
}, func(value T) Result[T] {
assert.NoError(t, nil)
return E.Right[error](value)
return result.Of(value)
})
}
}
// ArrayContains tests if a value is contained in an array
func ArrayContains[T any](t *testing.T, expected T) func(actual []T) E.Either[error, []T] {
return func(actual []T) E.Either[error, []T] {
func ArrayContains[T any](t *testing.T, expected T) result.Kleisli[[]T, []T] {
return func(actual []T) Result[[]T] {
ok := assert.Contains(t, actual, expected)
if ok {
return E.Of[error](actual)
return result.Of(actual)
}
return E.Left[[]T](errTest)
return result.Left[[]T](errTest)
}
}
// ContainsKey tests if a key is contained in a map
func ContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] {
return func(actual map[K]T) E.Either[error, map[K]T] {
func ContainsKey[T any, K comparable](t *testing.T, expected K) result.Kleisli[map[K]T, map[K]T] {
return func(actual map[K]T) Result[map[K]T] {
ok := assert.Contains(t, actual, expected)
if ok {
return E.Of[error](actual)
return result.Of(actual)
}
return E.Left[map[K]T](errTest)
return result.Left[map[K]T](errTest)
}
}
// NotContainsKey tests if a key is not contained in a map
func NotContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] {
return func(actual map[K]T) E.Either[error, map[K]T] {
func NotContainsKey[T any, K comparable](t *testing.T, expected K) result.Kleisli[map[K]T, map[K]T] {
return func(actual map[K]T) Result[map[K]T] {
ok := assert.NotContains(t, actual, expected)
if ok {
return E.Of[error](actual)
return result.Of(actual)
}
return E.Left[map[K]T](errTest)
return result.Left[map[K]T](errTest)
}
}

7
v2/assert/types.go Normal file
View File

@@ -0,0 +1,7 @@
package assert
import "github.com/IBM/fp-go/v2/result"
type (
Result[T any] = result.Result[T]
)

View File

@@ -19,13 +19,17 @@ import (
"context"
"time"
"github.com/IBM/fp-go/v2/context/readerresult"
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/errors"
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readerio"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/readeroption"
)
const (
@@ -747,3 +751,88 @@ func GetOrElse[A any](onLeft func(error) ReaderIO[A]) func(ReaderIOResult[A]) Re
func OrLeft[A any](onLeft func(error) ReaderIO[error]) Operator[A, A] {
return RIOR.OrLeft[A](onLeft)
}
//go:inline
func FromReaderEither[A any](ma ReaderEither[context.Context, error, A]) ReaderIOResult[A] {
return RIOR.FromReaderEither(ma)
}
//go:inline
func FromReaderResult[A any](ma ReaderResult[A]) ReaderIOResult[A] {
return RIOR.FromReaderEither(ma)
}
//go:inline
func FromReaderOption[A any](onNone func() error) Kleisli[ReaderOption[context.Context, A], A] {
return RIOR.FromReaderOption[context.Context, A](onNone)
}
//go:inline
func MonadChainReaderK[A, B any](ma ReaderIOResult[A], f reader.Kleisli[context.Context, A, B]) ReaderIOResult[B] {
return RIOR.MonadChainReaderK(ma, f)
}
//go:inline
func ChainReaderK[A, B any](f reader.Kleisli[context.Context, A, B]) Operator[A, B] {
return RIOR.ChainReaderK(f)
}
//go:inline
func MonadChainFirstReaderK[A, B any](ma ReaderIOResult[A], f reader.Kleisli[context.Context, A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstReaderK(ma, f)
}
//go:inline
func ChainFirstReaderK[A, B any](f reader.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderK(f)
}
//go:inline
func MonadChainReaderResultK[A, B any](ma ReaderIOResult[A], f readerresult.Kleisli[A, B]) ReaderIOResult[B] {
return RIOR.MonadChainReaderResultK(ma, f)
}
//go:inline
func ChainReaderResultK[A, B any](f readerresult.Kleisli[A, B]) Operator[A, B] {
return RIOR.ChainReaderResultK(f)
}
//go:inline
func MonadChainFirstReaderResultK[A, B any](ma ReaderIOResult[A], f readerresult.Kleisli[A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstReaderResultK(ma, f)
}
//go:inline
func ChainFirstReaderResultK[A, B any](f readerresult.Kleisli[A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderResultK(f)
}
//go:inline
func MonadChainReaderIOK[A, B any](ma ReaderIOResult[A], f readerio.Kleisli[context.Context, A, B]) ReaderIOResult[B] {
return RIOR.MonadChainReaderIOK(ma, f)
}
//go:inline
func ChainReaderIOK[A, B any](f readerio.Kleisli[context.Context, A, B]) Operator[A, B] {
return RIOR.ChainReaderIOK(f)
}
//go:inline
func MonadChainFirstReaderIOK[A, B any](ma ReaderIOResult[A], f readerio.Kleisli[context.Context, A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstReaderIOK(ma, f)
}
//go:inline
func ChainFirstReaderIOK[A, B any](f readerio.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderIOK(f)
}
//go:inline
func ChainReaderOptionK[A, B any](onNone func() error) func(readeroption.Kleisli[context.Context, A, B]) Operator[A, B] {
return RIOR.ChainReaderOptionK[context.Context, A, B](onNone)
}
//go:inline
func ChainFirstReaderOptionK[A, B any](onNone func() error) func(readeroption.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderOptionK[context.Context, A, B](onNone)
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,14 +19,17 @@ import (
"context"
"github.com/IBM/fp-go/v2/context/ioresult"
"github.com/IBM/fp-go/v2/context/readerresult"
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readereither"
"github.com/IBM/fp-go/v2/readerio"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/readeroption"
"github.com/IBM/fp-go/v2/result"
)
@@ -119,4 +122,8 @@ type (
// // Apply the transformation
// result := toUpper(computation)
Operator[A, B any] = Kleisli[ReaderIOResult[A], B]
ReaderResult[A any] = readerresult.ReaderResult[A]
ReaderEither[R, E, A any] = readereither.ReaderEither[R, E, A]
ReaderOption[R, A any] = readeroption.ReaderOption[R, A]
)

8340
v2/coverage.out Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ import (
DIE "github.com/IBM/fp-go/v2/di/erasure"
F "github.com/IBM/fp-go/v2/function"
IO "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
IOR "github.com/IBM/fp-go/v2/ioresult"
)
var (
@@ -34,5 +34,5 @@ var (
var RunMain = F.Flow3(
DIE.MakeInjector,
Main,
IOE.Fold(IO.Of[error], F.Constant1[any](IO.Of[error](nil))),
IOR.Fold(IO.Of[error], F.Constant1[any](IO.Of[error](nil))),
)

View File

@@ -64,8 +64,8 @@ Creating and using dependencies:
dbProvider := di.MakeProvider1(
DBToken,
ConfigToken.Identity(),
func(cfg Config) IOE.IOEither[error, Database] {
return IOE.Of[error](NewDatabase(cfg))
func(cfg Config) IOResult[Database] {
return ioresult.Of(NewDatabase(cfg))
},
)
@@ -73,8 +73,8 @@ Creating and using dependencies:
APIToken,
ConfigToken.Identity(),
DBToken.Identity(),
func(cfg Config, db Database) IOE.IOEither[error, APIService] {
return IOE.Of[error](NewAPIService(cfg, db))
func(cfg Config, db Database) IOResult[APIService] {
return ioresult.Of(NewAPIService(cfg, db))
},
)
@@ -116,7 +116,7 @@ MakeProvider0 - No dependencies:
provider := di.MakeProvider0(
token,
IOE.Of[error](value),
ioresult.Of(value),
)
MakeProvider1 - One dependency:
@@ -124,8 +124,8 @@ MakeProvider1 - One dependency:
provider := di.MakeProvider1(
resultToken,
dep1Token.Identity(),
func(dep1 Dep1Type) IOE.IOEither[error, ResultType] {
return IOE.Of[error](createResult(dep1))
func(dep1 Dep1Type) IOResult[ResultType] {
return ioresult.Of(createResult(dep1))
},
)
@@ -135,8 +135,8 @@ MakeProvider2 - Two dependencies:
resultToken,
dep1Token.Identity(),
dep2Token.Identity(),
func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] {
return IOE.Of[error](createResult(dep1, dep2))
func(dep1 Dep1Type, dep2 Dep2Type) IOResult[ResultType] {
return ioresult.Of(createResult(dep1, dep2))
},
)
@@ -153,7 +153,7 @@ provider is registered:
token := di.MakeTokenWithDefault0(
"ServiceName",
IOE.Of[error](defaultImplementation),
ioresult.Of(defaultImplementation),
)
// Or with dependencies
@@ -161,8 +161,8 @@ provider is registered:
"ServiceName",
dep1Token.Identity(),
dep2Token.Identity(),
func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] {
return IOE.Of[error](createDefault(dep1, dep2))
func(dep1 Dep1Type, dep2 Dep2Type) IOResult[ResultType] {
return ioresult.Of(createDefault(dep1, dep2))
},
)
@@ -208,8 +208,8 @@ The framework provides a convenient pattern for running applications:
mainProvider := di.MakeProvider1(
di.InjMain,
APIToken.Identity(),
func(api APIService) IOE.IOEither[error, any] {
return IOE.Of[error](api.Start())
func(api APIService) IOResult[any] {
return ioresult.Of(api.Start())
},
)
@@ -247,8 +247,8 @@ Example 1: Configuration-based Service
clientProvider := di.MakeProvider1(
ClientToken,
ConfigToken.Identity(),
func(cfg Config) IOE.IOEither[error, HTTPClient] {
return IOE.Of[error](HTTPClient{config: cfg})
func(cfg Config) IOResult[HTTPClient] {
return ioresult.Of(HTTPClient{config: cfg})
},
)
@@ -263,8 +263,8 @@ Example 2: Optional Dependencies
serviceProvider := di.MakeProvider1(
ServiceToken,
CacheToken.Option(), // Optional dependency
func(cache O.Option[Cache]) IOE.IOEither[error, Service] {
return IOE.Of[error](NewService(cache))
func(cache Option[Cache]) IOResult[Service] {
return ioresult.Of(NewService(cache))
},
)
@@ -279,8 +279,8 @@ Example 3: Lazy Dependencies
reporterProvider := di.MakeProvider1(
ReporterToken,
DBToken.IOEither(), // Lazy dependency
func(dbIO IOE.IOEither[error, Database]) IOE.IOEither[error, Reporter] {
return IOE.Of[error](NewReporter(dbIO))
func(dbIO IOResult[Database]) IOResult[Reporter] {
return ioresult.Of(NewReporter(dbIO))
},
)

View File

@@ -20,7 +20,7 @@ import (
"github.com/IBM/fp-go/v2/errors"
F "github.com/IBM/fp-go/v2/function"
I "github.com/IBM/fp-go/v2/identity"
IOE "github.com/IBM/fp-go/v2/ioeither"
IOR "github.com/IBM/fp-go/v2/ioresult"
L "github.com/IBM/fp-go/v2/lazy"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/record"
@@ -42,8 +42,8 @@ var (
missingProviderError = F.Flow4(
Dependency.String,
errors.OnSome[string]("no provider for dependency [%s]"),
IOE.Left[any, error],
F.Constant1[InjectableFactory, IOE.IOEither[error, any]],
IOR.Left[any],
F.Constant1[InjectableFactory, IOResult[any]],
)
// missingProviderErrorOrDefault returns the default [ProviderFactory] or an error
@@ -56,7 +56,7 @@ var (
emptyMulti any = A.Empty[any]()
// emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency
emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti)))
emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOR.Of(emptyMulti)))
// handleMissingProvider covers the case of a missing provider. It either
// returns an error or an empty multi value provider
@@ -93,21 +93,21 @@ var (
// isMultiDependency tests if a dependency is a container dependency
func isMultiDependency(dep Dependency) bool {
return dep.Flag()&Multi == Multi
return dep.Flag()&MULTI == MULTI
}
// isItemProvider tests if a provivder provides a single item
func isItemProvider(provider Provider) bool {
return provider.Provides().Flag()&Item == Item
return provider.Provides().Flag()&ITEM == ITEM
}
// itemProviderFactory combines multiple factories into one, returning an array
func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
return func(inj InjectableFactory) IOE.IOEither[error, any] {
return func(inj InjectableFactory) IOResult[any] {
return F.Pipe2(
fcts,
IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)),
IOE.Map[error](F.ToAny[[]any]),
IOR.TraverseArray(I.Flap[IOResult[any]](inj)),
IOR.Map(F.ToAny[[]any]),
)
}
}
@@ -118,7 +118,7 @@ func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
// makes sure to transitively resolve the required dependencies.
func MakeInjector(providers []Provider) InjectableFactory {
type Result = IOE.IOEither[error, any]
type Result = IOResult[any]
type LazyResult = L.Lazy[Result]
// resolved stores the values resolved so far, key is the string ID
@@ -148,11 +148,11 @@ func MakeInjector(providers []Provider) InjectableFactory {
T.Map2(F.Flow3(
Dependency.Id,
R.Lookup[ProviderFactory, string],
I.Ap[O.Option[ProviderFactory]](factoryByID),
I.Ap[Option[ProviderFactory]](factoryByID),
), handleMissingProvider),
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
I.Ap[IOE.IOEither[error, any]](injFct),
IOE.Memoize[error, any],
I.Ap[IOResult[any]](injFct),
IOR.Memoize[any],
)
}

View File

@@ -19,25 +19,23 @@ import (
"fmt"
A "github.com/IBM/fp-go/v2/array"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
I "github.com/IBM/fp-go/v2/identity"
IO "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption"
Int "github.com/IBM/fp-go/v2/number/integer"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/record"
"github.com/IBM/fp-go/v2/result"
)
type (
// InjectableFactory is a factory function that can create an untyped instance of a service based on its [Dependency] identifier
InjectableFactory = func(Dependency) IOE.IOEither[error, any]
ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
InjectableFactory = func(Dependency) IOResult[any]
ProviderFactory = func(InjectableFactory) IOResult[any]
paramIndex = map[int]int
paramValue = map[int]any
handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue]
handler = func(paramIndex) func([]IOResult[any]) IOResult[paramValue]
mapping = map[int]paramIndex
Provider interface {
@@ -83,50 +81,50 @@ var (
mergeMaps = R.UnionLastMonoid[int, any]()
collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any])
mapDeps = F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]])
mapDeps = F.Curry2(A.MonadMap[Dependency, IOResult[any]])
handlers = map[int]handler{
Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
IDENTITY: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe1(
mp,
IOE.TraverseRecord[int](getAt(res)),
)
}
},
Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
OPTION: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe3(
mp,
IO.TraverseRecord[int](getAt(res)),
IO.Map(R.Map[int](F.Flow2(
E.ToOption[error, any],
F.ToAny[O.Option[any]],
result.ToOption[any],
F.ToAny[Option[any]],
))),
IOE.FromIO[error, paramValue],
)
}
},
IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
IOEITHER: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe2(
mp,
R.Map[int](F.Flow2(
getAt(res),
F.ToAny[IOE.IOEither[error, any]],
F.ToAny[IOResult[any]],
)),
IOE.Of[error, paramValue],
)
}
},
IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
IOOPTION: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe2(
mp,
R.Map[int](F.Flow3(
getAt(res),
IOE.ToIOOption[error, any],
F.ToAny[IOO.IOOption[any]],
F.ToAny[IOOption[any]],
)),
IOE.Of[error, paramValue],
)
@@ -141,23 +139,23 @@ func getAt[T any](ar []T) func(idx int) T {
}
}
func handleMapping(mp mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
func handleMapping(mp mapping) func(res []IOResult[any]) IOResult[[]any] {
preFct := F.Pipe1(
mp,
R.Collect(func(idx int, p paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
R.Collect(func(idx int, p paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return handlers[idx](p)
}),
)
doFct := F.Flow2(
I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]],
IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue],
I.Flap[IOResult[paramValue], []IOResult[any]],
IOE.TraverseArray[error, func([]IOResult[any]) IOResult[paramValue], paramValue],
)
postFct := IOE.Map[error](F.Flow2(
A.Fold(mergeMaps),
collectParams,
))
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
return func(res []IOResult[any]) IOResult[[]any] {
return F.Pipe2(
preFct,
doFct(res),
@@ -170,7 +168,7 @@ func handleMapping(mp mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither
// a function that accepts the resolved dependencies to return a result
func MakeProviderFactory(
deps []Dependency,
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory {
fct func(param ...any) IOResult[any]) ProviderFactory {
return F.Flow3(
mapDeps(deps),

View File

@@ -17,20 +17,18 @@ package erasure
import (
"fmt"
O "github.com/IBM/fp-go/v2/option"
)
const (
BehaviourMask = 0x0f
Identity = 0 // required dependency
Option = 1 // optional dependency
IOEither = 2 // lazy and required
IOOption = 3 // lazy and optional
IDENTITY = 0 // required dependency
OPTION = 1 // optional dependency
IOEITHER = 2 // lazy and required
IOOPTION = 3 // lazy and optional
TypeMask = 0xf0
Multi = 1 << 4 // array of implementations
Item = 2 << 4 // item of a multi token
MULTI = 1 << 4 // array of implementations
ITEM = 2 << 4 // item of a multi token
)
// Dependency describes the relationship to a service
@@ -41,5 +39,5 @@ type Dependency interface {
// Flag returns a tag that identifies the behaviour of the dependency
Flag() int
// ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency
ProviderFactory() O.Option[ProviderFactory]
ProviderFactory() Option[ProviderFactory]
}

13
v2/di/erasure/types.go Normal file
View File

@@ -0,0 +1,13 @@
package erasure
import (
"github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/option"
)
type (
Option[T any] = option.Option[T]
IOResult[T any] = ioresult.IOResult[T]
IOOption[T any] = iooption.IOOption[T]
)

File diff suppressed because it is too large Load Diff

View File

@@ -19,14 +19,14 @@ import (
DIE "github.com/IBM/fp-go/v2/di/erasure"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/identity"
IOE "github.com/IBM/fp-go/v2/ioeither"
RIOE "github.com/IBM/fp-go/v2/readerioeither"
IOR "github.com/IBM/fp-go/v2/ioresult"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
)
// Resolve performs a type safe resolution of a dependency
func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] {
func Resolve[T any](token InjectionToken[T]) RIOR.ReaderIOResult[DIE.InjectableFactory, T] {
return F.Flow2(
identity.Ap[IOE.IOEither[error, any]](asDependency(token)),
IOE.ChainEitherK(token.Unerase),
identity.Ap[IOResult[any]](asDependency(token)),
IOR.ChainResultK(token.Unerase),
)
}

View File

@@ -22,9 +22,10 @@ import (
"github.com/IBM/fp-go/v2/errors"
F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
)
func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[error, T] {
func lookupAt[T any](idx int, token Dependency[T]) func(params []any) Result[T] {
return F.Flow3(
A.Lookup[any](idx),
E.FromOption[any](errors.OnNone("No parameter at position %d", idx)),
@@ -32,7 +33,7 @@ func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[e
)
}
func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error, A]) IOE.IOEither[error, any] {
func eraseTuple[A, R any](f func(A) IOResult[R]) func(Result[A]) IOResult[any] {
return F.Flow3(
IOE.FromEither[error, A],
IOE.Chain(f),
@@ -40,8 +41,8 @@ func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error,
)
}
func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
return func(_ ...any) IOE.IOEither[error, any] {
func eraseProviderFactory0[R any](f IOResult[R]) func(params ...any) IOResult[any] {
return func(_ ...any) IOResult[any] {
return F.Pipe1(
f,
IOE.Map[error](F.ToAny[R]),
@@ -50,7 +51,7 @@ func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any)
}
func MakeProviderFactory0[R any](
fct IOE.IOEither[error, R],
fct IOResult[R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.Empty[DIE.Dependency](),
@@ -59,13 +60,13 @@ func MakeProviderFactory0[R any](
}
// MakeTokenWithDefault0 creates a unique [InjectionToken] for a specific type with an attached default [DIE.Provider]
func MakeTokenWithDefault0[R any](name string, fct IOE.IOEither[error, R]) InjectionToken[R] {
func MakeTokenWithDefault0[R any](name string, fct IOResult[R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct))
}
func MakeProvider0[R any](
token InjectionToken[R],
fct IOE.IOEither[error, R],
fct IOResult[R],
) DIE.Provider {
return DIE.MakeProvider(
token,
@@ -75,5 +76,5 @@ func MakeProvider0[R any](
// ConstProvider simple implementation for a provider with a constant value
func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider {
return MakeProvider0(token, IOE.Of[error](value))
return MakeProvider0(token, ioresult.Of(value))
}

View File

@@ -25,7 +25,8 @@ import (
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
@@ -39,19 +40,19 @@ func TestSimpleProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticValue := func(value string) IOResult[string] {
return func() Result[string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicValue := func(value string) IOResult[string] {
return func() Result[string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
@@ -81,19 +82,19 @@ func TestOptionalProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticValue := func(value string) IOResult[string] {
return func() Result[string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicValue := func(value Option[string]) IOResult[string] {
return func() Result[string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
@@ -123,10 +124,10 @@ func TestOptionalProviderMissingDependency(t *testing.T) {
var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicValue := func(value Option[string]) IOResult[string] {
return func() Result[string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
@@ -151,10 +152,10 @@ func TestProviderMissingDependency(t *testing.T) {
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicValue := func(value string) IOResult[string] {
return func() Result[string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
@@ -179,31 +180,31 @@ func TestEagerAndLazyProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticValue := func(value string) IOResult[string] {
return func() Result[string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicValue := func(value string) IOResult[string] {
return func() Result[string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
var lazyEagerCount int
lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] {
lazyEager := func(laz IOResult[string], eager string) IOResult[string] {
return F.Pipe1(
laz,
IOE.Chain(func(lazValue string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
IOE.Chain(func(lazValue string) IOResult[string] {
return func() Result[string] {
lazyEagerCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now()))
return result.Of(fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now()))
}
}),
)
@@ -248,7 +249,7 @@ func TestItemProvider(t *testing.T) {
value := multiInj()
assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value)
assert.Equal(t, result.Of(A.From("Value1", "Value2")), value)
}
func TestEmptyItemProvider(t *testing.T) {
@@ -269,7 +270,7 @@ func TestEmptyItemProvider(t *testing.T) {
value := multiInj()
assert.Equal(t, E.Of[error](A.Empty[string]()), value)
assert.Equal(t, result.Of(A.Empty[string]()), value)
}
func TestDependencyOnMultiProvider(t *testing.T) {
@@ -283,8 +284,8 @@ func TestDependencyOnMultiProvider(t *testing.T) {
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
fromMulti := func(val string, multi []string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi))
fromMulti := func(val string, multi []string) IOResult[string] {
return ioresult.Of(fmt.Sprintf("Val: %s, Multi: %s", val, multi))
}
p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti)
@@ -295,19 +296,19 @@ func TestDependencyOnMultiProvider(t *testing.T) {
v := r3(inj)()
assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v)
assert.Equal(t, result.Of("Val: Value3, Multi: [Value1 Value2]"), v)
}
func TestTokenWithDefaultProvider(t *testing.T) {
// token without a default
injToken1 := MakeToken[string]("Token1")
// token with a default
injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten"))
injToken2 := MakeTokenWithDefault0("Token2", ioresult.Of("Carsten"))
// dependency
injToken3 := MakeToken[string]("Token3")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOResult[string] {
return ioresult.Of(fmt.Sprintf("Token: %s", data))
})
// populate the injector
@@ -320,19 +321,19 @@ func TestTokenWithDefaultProvider(t *testing.T) {
// inj1 should not be available
assert.True(t, E.IsLeft(r1(inj)()))
// r3 should work
assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)())
assert.Equal(t, result.Of("Token: Carsten"), r3(inj)())
}
func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
// token with a default
injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten"))
injToken2 := MakeTokenWithDefault0("Token2", ioresult.Of("Carsten"))
// dependency
injToken3 := MakeToken[string]("Token3")
p2 := ConstProvider(injToken2, "Override")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOResult[string] {
return ioresult.Of(fmt.Sprintf("Token: %s", data))
})
// populate the injector
@@ -342,5 +343,5 @@ func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
r3 := Resolve(injToken3)
// r3 should work
assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)())
assert.Equal(t, result.Of("Token: Override"), r3(inj)())
}

View File

@@ -21,10 +21,7 @@ import (
"sync/atomic"
DIE "github.com/IBM/fp-go/v2/di/erasure"
E "github.com/IBM/fp-go/v2/either"
IO "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption"
O "github.com/IBM/fp-go/v2/option"
)
@@ -33,7 +30,7 @@ import (
type Dependency[T any] interface {
DIE.Dependency
// Unerase converts a value with erased type signature into a strongly typed value
Unerase(val any) E.Either[error, T]
Unerase(val any) Result[T]
}
// InjectionToken uniquely identifies a dependency by giving it an Id, Type and name
@@ -42,17 +39,17 @@ type InjectionToken[T any] interface {
// Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`.
// If the dependency cannot be resolved, the resolution process fails
Identity() Dependency[T]
// Option identifies this dependency as optional, it will be resolved eagerly and injected as [O.Option[T]].
// Option identifies this dependency as optional, it will be resolved eagerly and injected as [Option[T]].
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as [O.None[T]]
Option() Dependency[O.Option[T]]
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOE.IOEither[error, T]]. This
Option() Dependency[Option[T]]
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOResult[T]]. This
// value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process fails
IOEither() Dependency[IOE.IOEither[error, T]]
// IOOption identifies this dependency as optional but it will be resolved lazily as a [IOO.IOOption[T]]. This
IOEither() Dependency[IOResult[T]]
// IOOption identifies this dependency as optional but it will be resolved lazily as a [IOOption[T]]. This
// value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value.
IOOption() Dependency[IOO.IOOption[T]]
IOOption() Dependency[IOOption[T]]
}
// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name that can have multiple implementations.
@@ -79,12 +76,12 @@ type tokenBase struct {
name string
id string
flag int
providerFactory O.Option[DIE.ProviderFactory]
providerFactory Option[DIE.ProviderFactory]
}
type token[T any] struct {
base *tokenBase
toType func(val any) E.Either[error, T]
toType func(val any) Result[T]
}
func (t *token[T]) Id() string {
@@ -99,26 +96,26 @@ func (t *token[T]) String() string {
return t.base.name
}
func (t *token[T]) Unerase(val any) E.Either[error, T] {
func (t *token[T]) Unerase(val any) Result[T] {
return t.toType(val)
}
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
func (t *token[T]) ProviderFactory() Option[DIE.ProviderFactory] {
return t.base.providerFactory
}
func makeTokenBase(name string, id string, typ int, providerFactory O.Option[DIE.ProviderFactory]) *tokenBase {
func makeTokenBase(name string, id string, typ int, providerFactory Option[DIE.ProviderFactory]) *tokenBase {
return &tokenBase{name, id, typ, providerFactory}
}
func makeToken[T any](name string, id string, typ int, unerase func(val any) E.Either[error, T], providerFactory O.Option[DIE.ProviderFactory]) Dependency[T] {
func makeToken[T any](name string, id string, typ int, unerase func(val any) Result[T], providerFactory Option[DIE.ProviderFactory]) Dependency[T] {
return &token[T]{makeTokenBase(name, id, typ, providerFactory), unerase}
}
type injectionToken[T any] struct {
token[T]
option Dependency[O.Option[T]]
ioeither Dependency[IOE.IOEither[error, T]]
iooption Dependency[IOO.IOOption[T]]
option Dependency[Option[T]]
ioeither Dependency[IOResult[T]]
iooption Dependency[IOOption[T]]
}
type multiInjectionToken[T any] struct {
@@ -130,19 +127,19 @@ func (i *injectionToken[T]) Identity() Dependency[T] {
return i
}
func (i *injectionToken[T]) Option() Dependency[O.Option[T]] {
func (i *injectionToken[T]) Option() Dependency[Option[T]] {
return i.option
}
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] {
func (i *injectionToken[T]) IOEither() Dependency[IOResult[T]] {
return i.ioeither
}
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
func (i *injectionToken[T]) IOOption() Dependency[IOOption[T]] {
return i.iooption
}
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
func (i *injectionToken[T]) ProviderFactory() Option[DIE.ProviderFactory] {
return i.base.providerFactory
}
@@ -155,14 +152,14 @@ func (m *multiInjectionToken[T]) Item() InjectionToken[T] {
}
// makeToken create a unique [InjectionToken] for a specific type
func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] {
func makeInjectionToken[T any](name string, providerFactory Option[DIE.ProviderFactory]) InjectionToken[T] {
id := genID()
toIdentity := toType[T]()
return &injectionToken[T]{
token[T]{makeTokenBase(name, id, DIE.Identity, providerFactory), toIdentity},
makeToken(fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory),
makeToken(fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory),
makeToken(fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory),
token[T]{makeTokenBase(name, id, DIE.IDENTITY, providerFactory), toIdentity},
makeToken(fmt.Sprintf("Option[%s]", name), id, DIE.OPTION, toOptionType(toIdentity), providerFactory),
makeToken(fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEITHER, toIOEitherType(toIdentity), providerFactory),
makeToken(fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOPTION, toIOOptionType(toIdentity), providerFactory),
}
}
@@ -187,17 +184,17 @@ func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
providerFactory := O.None[DIE.ProviderFactory]()
// container
container := &injectionToken[[]T]{
token[[]T]{makeTokenBase(containerName, id, DIE.Multi|DIE.Identity, providerFactory), toContainer},
makeToken(fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory),
makeToken(fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory),
makeToken(fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory),
token[[]T]{makeTokenBase(containerName, id, DIE.MULTI|DIE.IDENTITY, providerFactory), toContainer},
makeToken(fmt.Sprintf("Option[%s]", containerName), id, DIE.MULTI|DIE.OPTION, toOptionType(toContainer), providerFactory),
makeToken(fmt.Sprintf("IOEither[%s]", containerName), id, DIE.OPTION|DIE.IOEITHER, toIOEitherType(toContainer), providerFactory),
makeToken(fmt.Sprintf("IOOption[%s]", containerName), id, DIE.OPTION|DIE.IOOPTION, toIOOptionType(toContainer), providerFactory),
}
// item
item := &injectionToken[T]{
token[T]{makeTokenBase(itemName, id, DIE.Item|DIE.Identity, providerFactory), toItem},
makeToken(fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory),
makeToken(fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory),
makeToken(fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory),
token[T]{makeTokenBase(itemName, id, DIE.ITEM|DIE.IDENTITY, providerFactory), toItem},
makeToken(fmt.Sprintf("Option[%s]", itemName), id, DIE.ITEM|DIE.OPTION, toOptionType(toItem), providerFactory),
makeToken(fmt.Sprintf("IOEither[%s]", itemName), id, DIE.ITEM|DIE.IOEITHER, toIOEitherType(toItem), providerFactory),
makeToken(fmt.Sprintf("IOOption[%s]", itemName), id, DIE.ITEM|DIE.IOOPTION, toIOOptionType(toItem), providerFactory),
}
// returns the token
return &multiInjectionToken[T]{container, item}

View File

@@ -23,7 +23,9 @@ import (
F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/ioresult"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
@@ -75,9 +77,9 @@ func TestTokenUnerase(t *testing.T) {
token := MakeToken[int]("IntToken")
// Test successful unerase
result := token.Unerase(42)
assert.True(t, E.IsRight(result))
assert.Equal(t, E.Of[error](42), result)
res := token.Unerase(42)
assert.True(t, E.IsRight(res))
assert.Equal(t, result.Of(42), res)
// Test failed unerase (wrong type)
result2 := token.Unerase("not an int")
@@ -104,7 +106,7 @@ func TestTokenProviderFactory(t *testing.T) {
assert.True(t, O.IsNone(token1.ProviderFactory()))
// Token with default
token2 := MakeTokenWithDefault0("Token2", IOE.Of[error](42))
token2 := MakeTokenWithDefault0("Token2", ioresult.Of(42))
assert.True(t, O.IsSome(token2.ProviderFactory()))
}
@@ -148,13 +150,13 @@ func TestOptionTokenUnerase(t *testing.T) {
optionToken := token.Option()
// Test successful unerase with Some
result := optionToken.Unerase(O.Of[any](42))
assert.True(t, E.IsRight(result))
res := optionToken.Unerase(O.Of[any](42))
assert.True(t, E.IsRight(res))
// Test successful unerase with None
noneResult := optionToken.Unerase(O.None[any]())
assert.True(t, E.IsRight(noneResult))
assert.Equal(t, E.Of[error](O.None[int]()), noneResult)
assert.Equal(t, result.Of(O.None[int]()), noneResult)
// Test failed unerase (wrong type)
badResult := optionToken.Unerase(42) // Not an Option
@@ -166,7 +168,7 @@ func TestIOEitherTokenUnerase(t *testing.T) {
ioeitherToken := token.IOEither()
// Test successful unerase
ioValue := IOE.Of[error](any(42))
ioValue := ioresult.Of(any(42))
result := ioeitherToken.Unerase(ioValue)
assert.True(t, E.IsRight(result))
@@ -222,7 +224,7 @@ func TestMultiTokenContainerUnerase(t *testing.T) {
}
func TestMakeTokenWithDefault(t *testing.T) {
factory := MakeProviderFactory0(IOE.Of[error](42))
factory := MakeProviderFactory0(ioresult.Of(42))
token := MakeTokenWithDefault[int]("TokenWithDefault", factory)
assert.NotNil(t, token)

15
v2/di/types.go Normal file
View File

@@ -0,0 +1,15 @@
package di
import (
"github.com/IBM/fp-go/v2/context/ioresult"
"github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
)
type (
Option[T any] = option.Option[T]
Result[T any] = result.Result[T]
IOResult[T any] = ioresult.IOResult[T]
IOOption[T any] = iooption.IOOption[T]
)

View File

@@ -23,12 +23,13 @@ import (
IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
)
var (
toOptionAny = toType[O.Option[any]]()
toIOEitherAny = toType[IOE.IOEither[error, any]]()
toIOOptionAny = toType[IOO.IOOption[any]]()
toOptionAny = toType[Option[any]]()
toIOEitherAny = toType[IOResult[any]]()
toIOOptionAny = toType[IOOption[any]]()
toArrayAny = toType[[]any]()
)
@@ -38,45 +39,45 @@ func asDependency[T DIE.Dependency](t T) DIE.Dependency {
}
// toType converts an any to a T
func toType[T any]() func(t any) E.Either[error, T] {
func toType[T any]() result.Kleisli[any, T] {
return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted."))
}
// toOptionType converts an any to an Option[any] and then to an Option[T]
func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, O.Option[T]] {
func toOptionType[T any](item result.Kleisli[any, T]) result.Kleisli[any, Option[T]] {
return F.Flow2(
toOptionAny,
E.Chain(O.Fold(
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]),
F.Nullary2(O.None[T], E.Of[error, Option[T]]),
F.Flow2(
item,
E.Map[error](O.Of[T]),
result.Map(O.Of[T]),
),
)),
)
}
// toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T]
func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOE.IOEither[error, T]] {
func toIOEitherType[T any](item result.Kleisli[any, T]) result.Kleisli[any, IOResult[T]] {
return F.Flow2(
toIOEitherAny,
E.Map[error](IOE.ChainEitherK(item)),
result.Map(IOE.ChainEitherK(item)),
)
}
// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T]
func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOO.IOOption[T]] {
func toIOOptionType[T any](item result.Kleisli[any, T]) result.Kleisli[any, IOOption[T]] {
return F.Flow2(
toIOOptionAny,
E.Map[error](IOO.ChainOptionK(F.Flow2(
result.Map(IOO.ChainOptionK(F.Flow2(
item,
E.ToOption[error, T],
result.ToOption[T],
))),
)
}
// toArrayType converts an any to a []T
func toArrayType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, []T] {
func toArrayType[T any](item result.Kleisli[any, T]) result.Kleisli[any, []T] {
return F.Flow2(
toArrayAny,
E.Chain(E.TraverseArray(item)),

View File

@@ -21,8 +21,9 @@ import (
A "github.com/IBM/fp-go/v2/array"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
@@ -33,13 +34,13 @@ var (
func TestToType(t *testing.T) {
// good cases
assert.Equal(t, E.Of[error](10), toInt(any(10)))
assert.Equal(t, E.Of[error]("Carsten"), toString(any("Carsten")))
assert.Equal(t, E.Of[error](O.Of("Carsten")), toType[O.Option[string]]()(any(O.Of("Carsten"))))
assert.Equal(t, E.Of[error](O.Of(any("Carsten"))), toType[O.Option[any]]()(any(O.Of(any("Carsten")))))
assert.Equal(t, result.Of(10), toInt(any(10)))
assert.Equal(t, result.Of("Carsten"), toString(any("Carsten")))
assert.Equal(t, result.Of(O.Of("Carsten")), toType[Option[string]]()(any(O.Of("Carsten"))))
assert.Equal(t, result.Of(O.Of(any("Carsten"))), toType[Option[any]]()(any(O.Of(any("Carsten")))))
// failure
assert.False(t, E.IsRight(toInt(any("Carsten"))))
assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten")))))
assert.False(t, E.IsRight(toType[Option[string]]()(O.Of(any("Carsten")))))
}
func TestToOptionType(t *testing.T) {
@@ -47,17 +48,17 @@ func TestToOptionType(t *testing.T) {
toOptInt := toOptionType(toInt)
toOptString := toOptionType(toString)
// good cases
assert.Equal(t, E.Of[error](O.Of(10)), toOptInt(any(O.Of(any(10)))))
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
assert.Equal(t, result.Of(O.Of(10)), toOptInt(any(O.Of(any(10)))))
assert.Equal(t, result.Of(O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
// bad cases
assert.False(t, E.IsRight(toOptInt(any(10))))
assert.False(t, E.IsRight(toOptInt(any(O.Of(10)))))
}
func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[error, T] {
func invokeIOEither[T any](e Result[IOResult[T]]) Result[T] {
return F.Pipe1(
e,
E.Chain(func(ioe IOE.IOEither[error, T]) E.Either[error, T] {
E.Chain(func(ioe IOResult[T]) Result[T] {
return ioe()
}),
)
@@ -68,11 +69,11 @@ func TestToIOEitherType(t *testing.T) {
toIOEitherInt := toIOEitherType(toInt)
toIOEitherString := toIOEitherType(toString)
// good cases
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherInt(any(IOE.Of[error](any(10))))))
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherString(any(IOE.Of[error](any("Carsten"))))))
assert.Equal(t, result.Of(10), invokeIOEither(toIOEitherInt(any(ioresult.Of(any(10))))))
assert.Equal(t, result.Of("Carsten"), invokeIOEither(toIOEitherString(any(ioresult.Of(any("Carsten"))))))
// bad cases
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error](any(10)))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error]("Carsten"))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(ioresult.Of(any(10)))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(ioresult.Of("Carsten"))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any("Carsten")))))
}
@@ -80,5 +81,5 @@ func TestToArrayType(t *testing.T) {
// shortcuts
toArrayString := toArrayType(toString)
// good cases
assert.Equal(t, E.Of[error](A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
assert.Equal(t, result.Of(A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
}

View File

@@ -41,7 +41,7 @@ import (
// curriedAdd := endomorphism.Curry2(add)
// addFive := curriedAdd(5) // Returns an endomorphism that adds 5
// result := addFive(10) // Returns: 15
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] {
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) Kleisli[T0, T1] {
return function.Curry2(f)
}
@@ -68,6 +68,6 @@ func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] {
// curriedCombine := endomorphism.Curry3(combine)
// addTen := curriedCombine(5)(5) // Returns an endomorphism that adds 10
// result := addTen(20) // Returns: 30
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2] {
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) Kleisli[T1, T2] {
return function.Curry3(f)
}

View File

@@ -37,6 +37,8 @@ type (
// var g endomorphism.Endomorphism[int] = increment
Endomorphism[A any] = func(A) A
Kleisli[A, B any] = func(A) Endomorphism[B]
// Operator represents a transformation from one endomorphism to another.
//
// An Operator takes an endomorphism on type A and produces an endomorphism on type B.
@@ -52,5 +54,5 @@ type (
// return strconv.Itoa(result)
// }
// }
Operator[A, B any] = func(Endomorphism[A]) Endomorphism[B]
Operator[A, B any] = Kleisli[Endomorphism[A], B]
)

View File

@@ -46,9 +46,26 @@ func MonadChainReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
}
func ChainReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
mchain func(HKTRA, func(A) HKTRB) HKTRB,
mchain func(func(A) HKTRB) func(HKTRA) HKTRB,
fromReader func(GB) HKTRB,
f func(A) GB,
) func(HKTRA) HKTRB {
return F.Bind2nd(mchain, FromReaderK(fromReader, f))
return mchain(FromReaderK(fromReader, f))
}
func MonadChainFirstReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
mchain func(HKTRA, func(A) HKTRB) HKTRA,
fromReader func(GB) HKTRB,
ma HKTRA,
f func(A) GB,
) HKTRA {
return mchain(ma, FromReaderK(fromReader, f))
}
func ChainFirstReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
mchain func(func(A) HKTRB) func(HKTRA) HKTRA,
fromReader func(GB) HKTRB,
f func(A) GB,
) func(HKTRA) HKTRA {
return mchain(FromReaderK(fromReader, f))
}

View File

@@ -21,10 +21,74 @@ import (
S "github.com/IBM/fp-go/v2/semigroup"
)
// ApplySemigroup lifts a Semigroup[A] to a Semigroup[Lazy[A]].
// This allows you to combine lazy computations using the semigroup operation
// on their underlying values.
//
// The resulting semigroup's Concat operation will evaluate both lazy computations
// and combine their results using the original semigroup's operation.
//
// Parameters:
// - s: A semigroup for values of type A
//
// Returns:
// - A semigroup for lazy computations of type A
//
// Example:
//
// import (
// M "github.com/IBM/fp-go/v2/monoid"
// "github.com/IBM/fp-go/v2/lazy"
// )
//
// // Create a semigroup for lazy integers using addition
// intAddSemigroup := lazy.ApplySemigroup(M.MonoidSum[int]())
//
// lazy1 := lazy.Of(5)
// lazy2 := lazy.Of(10)
//
// // Combine the lazy computations
// result := intAddSemigroup.Concat(lazy1, lazy2)() // 15
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Lazy[A]] {
return IO.ApplySemigroup(s)
}
// ApplicativeMonoid lifts a Monoid[A] to a Monoid[Lazy[A]].
// This allows you to combine lazy computations using the monoid operation
// on their underlying values, with an identity element.
//
// The resulting monoid's Concat operation will evaluate both lazy computations
// and combine their results using the original monoid's operation. The Empty
// operation returns a lazy computation that produces the monoid's identity element.
//
// Parameters:
// - m: A monoid for values of type A
//
// Returns:
// - A monoid for lazy computations of type A
//
// Example:
//
// import (
// M "github.com/IBM/fp-go/v2/monoid"
// "github.com/IBM/fp-go/v2/lazy"
// )
//
// // Create a monoid for lazy integers using addition
// intAddMonoid := lazy.ApplicativeMonoid(M.MonoidSum[int]())
//
// // Get the identity element (0 wrapped in lazy)
// empty := intAddMonoid.Empty()() // 0
//
// lazy1 := lazy.Of(5)
// lazy2 := lazy.Of(10)
//
// // Combine the lazy computations
// result := intAddMonoid.Concat(lazy1, lazy2)() // 15
//
// // Identity laws hold:
// // Concat(Empty(), x) == x
// // Concat(x, Empty()) == x
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Lazy[A]] {
return IO.ApplicativeMonoid(m)
}

269
v2/lazy/doc.go Normal file
View File

@@ -0,0 +1,269 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package lazy provides a functional programming abstraction for synchronous computations
// without side effects. It represents deferred computations that are evaluated only when
// their result is needed.
//
// # Overview
//
// A Lazy[A] is simply a function that takes no arguments and returns a value of type A:
//
// type Lazy[A any] = func() A
//
// This allows you to defer the evaluation of a computation until it's actually needed,
// which is useful for:
// - Avoiding unnecessary computations
// - Creating infinite data structures
// - Implementing memoization
// - Composing computations in a pure functional style
//
// # Core Concepts
//
// The lazy package implements several functional programming patterns:
//
// **Functor**: Transform values inside a Lazy context using Map
//
// **Applicative**: Combine multiple Lazy computations using Ap and ApS
//
// **Monad**: Chain dependent computations using Chain and Bind
//
// **Memoization**: Cache computation results using Memoize
//
// # Basic Usage
//
// Creating and evaluating lazy computations:
//
// import (
// "fmt"
// "github.com/IBM/fp-go/v2/lazy"
// F "github.com/IBM/fp-go/v2/function"
// )
//
// // Create a lazy computation
// computation := lazy.Of(42)
//
// // Transform it
// doubled := F.Pipe1(
// computation,
// lazy.Map(func(x int) int { return x * 2 }),
// )
//
// // Evaluate when needed
// result := doubled() // 84
//
// # Memoization
//
// Lazy computations can be memoized to ensure they're evaluated only once:
//
// import "math/rand"
//
// // Without memoization - generates different values each time
// random := lazy.FromLazy(rand.Int)
// value1 := random() // e.g., 12345
// value2 := random() // e.g., 67890 (different)
//
// // With memoization - caches the first result
// memoized := lazy.Memoize(rand.Int)
// value1 := memoized() // e.g., 12345
// value2 := memoized() // 12345 (same as value1)
//
// # Chaining Computations
//
// Use Chain to compose dependent computations:
//
// getUserId := lazy.Of(123)
//
// getUser := F.Pipe1(
// getUserId,
// lazy.Chain(func(id int) lazy.Lazy[User] {
// return lazy.Of(fetchUser(id))
// }),
// )
//
// user := getUser()
//
// # Do-Notation Style
//
// The package supports do-notation style composition using Bind and ApS:
//
// type Config struct {
// Host string
// Port int
// }
//
// result := F.Pipe2(
// lazy.Do(Config{}),
// lazy.Bind(
// func(host string) func(Config) Config {
// return func(c Config) Config { c.Host = host; return c }
// },
// func(c Config) lazy.Lazy[string] {
// return lazy.Of("localhost")
// },
// ),
// lazy.Bind(
// func(port int) func(Config) Config {
// return func(c Config) Config { c.Port = port; return c }
// },
// func(c Config) lazy.Lazy[int] {
// return lazy.Of(8080)
// },
// ),
// )
//
// config := result() // Config{Host: "localhost", Port: 8080}
//
// # Traverse and Sequence
//
// Transform collections of values into lazy computations:
//
// // Transform array elements
// numbers := []int{1, 2, 3}
// doubled := F.Pipe1(
// numbers,
// lazy.TraverseArray(func(x int) lazy.Lazy[int] {
// return lazy.Of(x * 2)
// }),
// )
// result := doubled() // []int{2, 4, 6}
//
// // Sequence array of lazy computations
// computations := []lazy.Lazy[int]{
// lazy.Of(1),
// lazy.Of(2),
// lazy.Of(3),
// }
// result := lazy.SequenceArray(computations)() // []int{1, 2, 3}
//
// # Retry Logic
//
// The package includes retry functionality for computations that may fail:
//
// import (
// R "github.com/IBM/fp-go/v2/retry"
// "time"
// )
//
// policy := R.CapDelay(
// 2*time.Second,
// R.Monoid.Concat(
// R.ExponentialBackoff(10),
// R.LimitRetries(5),
// ),
// )
//
// action := func(status R.RetryStatus) lazy.Lazy[string] {
// return lazy.Of(fetchData())
// }
//
// check := func(value string) bool {
// return value == "" // retry if empty
// }
//
// result := lazy.Retrying(policy, action, check)()
//
// # Algebraic Structures
//
// The package provides algebraic structures for combining lazy computations:
//
// **Semigroup**: Combine two lazy values using a semigroup operation
//
// import M "github.com/IBM/fp-go/v2/monoid"
//
// intAddSemigroup := lazy.ApplySemigroup(M.MonoidSum[int]())
// result := intAddSemigroup.Concat(lazy.Of(5), lazy.Of(10))() // 15
//
// **Monoid**: Combine lazy values with an identity element
//
// intAddMonoid := lazy.ApplicativeMonoid(M.MonoidSum[int]())
// empty := intAddMonoid.Empty()() // 0
// result := intAddMonoid.Concat(lazy.Of(5), lazy.Of(10))() // 15
//
// # Comparison
//
// Compare lazy computations by evaluating and comparing their results:
//
// import EQ "github.com/IBM/fp-go/v2/eq"
//
// eq := lazy.Eq(EQ.FromEquals[int]())
// result := eq.Equals(lazy.Of(42), lazy.Of(42)) // true
//
// # Key Functions
//
// **Creation**:
// - Of: Create a lazy computation from a value
// - FromLazy: Create a lazy computation from another lazy computation
// - FromImpure: Convert a side effect into a lazy computation
// - Defer: Create a lazy computation from a generator function
//
// **Transformation**:
// - Map: Transform the value inside a lazy computation
// - MapTo: Replace the value with a constant
// - Chain: Chain dependent computations
// - ChainFirst: Chain computations but keep the first result
// - Flatten: Flatten nested lazy computations
//
// **Combination**:
// - Ap: Apply a lazy function to a lazy value
// - ApFirst: Combine two computations, keeping the first result
// - ApSecond: Combine two computations, keeping the second result
//
// **Memoization**:
// - Memoize: Cache the result of a computation
//
// **Do-Notation**:
// - Do: Start a do-notation context
// - Bind: Bind a computation result to a context
// - Let: Attach a pure value to a context
// - LetTo: Attach a constant to a context
// - BindTo: Initialize a context from a value
// - ApS: Attach a value using applicative style
//
// **Lens-Based Operations**:
// - BindL: Bind using a lens
// - LetL: Let using a lens
// - LetToL: LetTo using a lens
// - ApSL: ApS using a lens
//
// **Collections**:
// - TraverseArray: Transform array elements into lazy computations
// - SequenceArray: Convert array of lazy computations to lazy array
// - TraverseRecord: Transform record values into lazy computations
// - SequenceRecord: Convert record of lazy computations to lazy record
//
// **Tuples**:
// - SequenceT1, SequenceT2, SequenceT3, SequenceT4: Combine lazy computations into tuples
//
// **Retry**:
// - Retrying: Retry a computation according to a policy
//
// **Algebraic**:
// - ApplySemigroup: Create a semigroup for lazy values
// - ApplicativeMonoid: Create a monoid for lazy values
// - Eq: Create an equality predicate for lazy values
//
// # Relationship to IO
//
// The lazy package is built on top of the io package and shares the same underlying
// implementation. The key difference is conceptual:
// - lazy.Lazy[A] represents a pure, synchronous computation without side effects
// - io.IO[A] represents a computation that may have side effects
//
// In practice, they are the same type, but the lazy package provides a more focused
// API for pure computations.
package lazy
// Made with Bob

View File

@@ -21,10 +21,28 @@ import (
"github.com/IBM/fp-go/v2/io"
)
// Of creates a lazy computation that returns the given value.
// This is the most basic way to lift a value into the Lazy context.
//
// The computation is pure and will always return the same value when evaluated.
//
// Example:
//
// computation := lazy.Of(42)
// result := computation() // 42
func Of[A any](a A) Lazy[A] {
return io.Of(a)
}
// FromLazy creates a lazy computation from another lazy computation.
// This is an identity function that can be useful for type conversions or
// making the intent explicit in code.
//
// Example:
//
// original := func() int { return 42 }
// wrapped := lazy.FromLazy(original)
// result := wrapped() // 42
func FromLazy[A any](a Lazy[A]) Lazy[A] {
return io.FromIO(a)
}
@@ -34,22 +52,73 @@ func FromImpure(f func()) Lazy[any] {
return io.FromImpure(f)
}
// MonadOf creates a lazy computation that returns the given value.
// This is an alias for Of, provided for consistency with monadic naming conventions.
//
// Example:
//
// computation := lazy.MonadOf(42)
// result := computation() // 42
func MonadOf[A any](a A) Lazy[A] {
return io.MonadOf(a)
}
// MonadMap transforms the value inside a lazy computation using the provided function.
// The transformation is not applied until the lazy computation is evaluated.
//
// This is the monadic version of Map, taking the lazy computation as the first parameter.
//
// Example:
//
// computation := lazy.Of(5)
// doubled := lazy.MonadMap(computation, func(x int) int { return x * 2 })
// result := doubled() // 10
func MonadMap[A, B any](fa Lazy[A], f func(A) B) Lazy[B] {
return io.MonadMap(fa, f)
}
// Map transforms the value inside a lazy computation using the provided function.
// Returns a function that can be applied to a lazy computation.
//
// This is the curried version of MonadMap, useful for function composition.
//
// Example:
//
// double := lazy.Map(func(x int) int { return x * 2 })
// computation := lazy.Of(5)
// result := double(computation)() // 10
//
// // Or with pipe:
// result := F.Pipe1(lazy.Of(5), double)() // 10
func Map[A, B any](f func(A) B) func(fa Lazy[A]) Lazy[B] {
return io.Map(f)
}
// MonadMapTo replaces the value inside a lazy computation with a constant value.
// The original computation is still evaluated, but its result is discarded.
//
// This is useful when you want to sequence computations but only care about
// the side effects (though Lazy should represent pure computations).
//
// Example:
//
// computation := lazy.Of("ignored")
// replaced := lazy.MonadMapTo(computation, 42)
// result := replaced() // 42
func MonadMapTo[A, B any](fa Lazy[A], b B) Lazy[B] {
return io.MonadMapTo(fa, b)
}
// MapTo replaces the value inside a lazy computation with a constant value.
// Returns a function that can be applied to a lazy computation.
//
// This is the curried version of MonadMapTo.
//
// Example:
//
// replaceWith42 := lazy.MapTo[string](42)
// computation := lazy.Of("ignored")
// result := replaceWith42(computation)() // 42
func MapTo[A, B any](b B) Kleisli[Lazy[A], B] {
return io.MapTo[A](b)
}
@@ -64,10 +133,32 @@ func Chain[A, B any](f Kleisli[A, B]) Kleisli[Lazy[A], B] {
return io.Chain(f)
}
// MonadAp applies a lazy function to a lazy value.
// Both the function and the value are evaluated when the result is evaluated.
//
// This is the applicative functor operation, allowing you to apply functions
// that are themselves wrapped in a lazy context.
//
// Example:
//
// lazyFunc := lazy.Of(func(x int) int { return x * 2 })
// lazyValue := lazy.Of(5)
// result := lazy.MonadAp(lazyFunc, lazyValue)() // 10
func MonadAp[B, A any](mab Lazy[func(A) B], ma Lazy[A]) Lazy[B] {
return io.MonadApSeq(mab, ma)
}
// Ap applies a lazy function to a lazy value.
// Returns a function that takes a lazy function and returns a lazy result.
//
// This is the curried version of MonadAp, useful for function composition.
//
// Example:
//
// lazyValue := lazy.Of(5)
// applyTo5 := lazy.Ap[int](lazyValue)
// lazyFunc := lazy.Of(func(x int) int { return x * 2 })
// result := applyTo5(lazyFunc)() // 10
func Ap[B, A any](ma Lazy[A]) func(Lazy[func(A) B]) Lazy[B] {
return io.ApSeq[B](ma)
}
@@ -123,7 +214,15 @@ func ChainTo[A, B any](fb Lazy[B]) Kleisli[Lazy[A], B] {
return io.ChainTo[A](fb)
}
// Now returns the current timestamp
// Now is a lazy computation that returns the current timestamp when evaluated.
// Each evaluation will return the current time at the moment of evaluation.
//
// Example:
//
// time1 := lazy.Now()
// // ... some time passes ...
// time2 := lazy.Now()
// // time1 and time2 will be different
var Now Lazy[time.Time] = io.Now
// Defer creates an IO by creating a brand new IO via a generator function, each time

View File

@@ -0,0 +1,505 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lazy
import (
"testing"
"time"
EQ "github.com/IBM/fp-go/v2/eq"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
M "github.com/IBM/fp-go/v2/monoid"
L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/stretchr/testify/assert"
)
func TestOf(t *testing.T) {
result := Of(42)
assert.Equal(t, 42, result())
}
func TestFromLazy(t *testing.T) {
original := func() int { return 42 }
wrapped := FromLazy(original)
assert.Equal(t, 42, wrapped())
}
func TestFromImpure(t *testing.T) {
counter := 0
impure := func() {
counter++
}
lazy := FromImpure(impure)
lazy()
assert.Equal(t, 1, counter)
}
func TestMonadOf(t *testing.T) {
result := MonadOf(42)
assert.Equal(t, 42, result())
}
func TestMonadMap(t *testing.T) {
result := MonadMap(Of(5), func(x int) int { return x * 2 })
assert.Equal(t, 10, result())
}
func TestMonadMapTo(t *testing.T) {
result := MonadMapTo(Of("ignored"), 42)
assert.Equal(t, 42, result())
}
func TestMapTo(t *testing.T) {
mapper := MapTo[string](42)
result := mapper(Of("ignored"))
assert.Equal(t, 42, result())
}
func TestMonadChain(t *testing.T) {
result := MonadChain(Of(5), func(x int) Lazy[int] {
return Of(x * 2)
})
assert.Equal(t, 10, result())
}
func TestMonadChainFirst(t *testing.T) {
result := MonadChainFirst(Of(5), func(x int) Lazy[string] {
return Of("ignored")
})
assert.Equal(t, 5, result())
}
func TestChainFirst(t *testing.T) {
chainer := ChainFirst(func(x int) Lazy[string] {
return Of("ignored")
})
result := chainer(Of(5))
assert.Equal(t, 5, result())
}
func TestMonadChainTo(t *testing.T) {
result := MonadChainTo(Of(5), Of(10))
assert.Equal(t, 10, result())
}
func TestChainTo(t *testing.T) {
chainer := ChainTo[int](Of(10))
result := chainer(Of(5))
assert.Equal(t, 10, result())
}
func TestMonadAp(t *testing.T) {
lazyFunc := Of(func(x int) int { return x * 2 })
lazyValue := Of(5)
result := MonadAp(lazyFunc, lazyValue)
assert.Equal(t, 10, result())
}
func TestMonadApFirst(t *testing.T) {
result := MonadApFirst(Of(5), Of(10))
assert.Equal(t, 5, result())
}
func TestMonadApSecond(t *testing.T) {
result := MonadApSecond(Of(5), Of(10))
assert.Equal(t, 10, result())
}
func TestNow(t *testing.T) {
before := time.Now()
result := Now()
after := time.Now()
assert.True(t, result.After(before) || result.Equal(before))
assert.True(t, result.Before(after) || result.Equal(after))
}
func TestDefer(t *testing.T) {
counter := 0
deferred := Defer(func() Lazy[int] {
counter++
return Of(counter)
})
// First execution
result1 := deferred()
assert.Equal(t, 1, result1)
// Second execution should generate a new computation
result2 := deferred()
assert.Equal(t, 2, result2)
}
func TestDo(t *testing.T) {
type State struct {
Value int
}
result := Do(State{Value: 42})
assert.Equal(t, State{Value: 42}, result())
}
func TestLet(t *testing.T) {
type State struct {
Value int
}
result := F.Pipe2(
Do(State{}),
Let(
func(v int) func(State) State {
return func(s State) State { s.Value = v; return s }
},
func(s State) int { return 42 },
),
Map(func(s State) int { return s.Value }),
)
assert.Equal(t, 42, result())
}
func TestLetTo(t *testing.T) {
type State struct {
Value int
}
result := F.Pipe2(
Do(State{}),
LetTo(
func(v int) func(State) State {
return func(s State) State { s.Value = v; return s }
},
42,
),
Map(func(s State) int { return s.Value }),
)
assert.Equal(t, 42, result())
}
func TestBindTo(t *testing.T) {
type State struct {
Value int
}
result := F.Pipe2(
Of(42),
BindTo(func(v int) State { return State{Value: v} }),
Map(func(s State) int { return s.Value }),
)
assert.Equal(t, 42, result())
}
func TestBindL(t *testing.T) {
type Config struct {
Port int
}
type State struct {
Config Config
}
// Create a lens manually
configLens := L.MakeLens(
func(s State) Config { return s.Config },
func(s State, cfg Config) State { s.Config = cfg; return s },
)
result := F.Pipe2(
Do(State{Config: Config{Port: 8080}}),
BindL(configLens, func(cfg Config) Lazy[Config] {
return Of(Config{Port: cfg.Port + 1})
}),
Map(func(s State) int { return s.Config.Port }),
)
assert.Equal(t, 8081, result())
}
func TestLetL(t *testing.T) {
type Config struct {
Port int
}
type State struct {
Config Config
}
// Create a lens manually
configLens := L.MakeLens(
func(s State) Config { return s.Config },
func(s State, cfg Config) State { s.Config = cfg; return s },
)
result := F.Pipe2(
Do(State{Config: Config{Port: 8080}}),
LetL(configLens, func(cfg Config) Config {
return Config{Port: cfg.Port + 1}
}),
Map(func(s State) int { return s.Config.Port }),
)
assert.Equal(t, 8081, result())
}
func TestLetToL(t *testing.T) {
type Config struct {
Port int
}
type State struct {
Config Config
}
// Create a lens manually
configLens := L.MakeLens(
func(s State) Config { return s.Config },
func(s State, cfg Config) State { s.Config = cfg; return s },
)
result := F.Pipe2(
Do(State{}),
LetToL(configLens, Config{Port: 8080}),
Map(func(s State) int { return s.Config.Port }),
)
assert.Equal(t, 8080, result())
}
func TestApSL(t *testing.T) {
type Config struct {
Port int
}
type State struct {
Config Config
}
// Create a lens manually
configLens := L.MakeLens(
func(s State) Config { return s.Config },
func(s State, cfg Config) State { s.Config = cfg; return s },
)
result := F.Pipe2(
Do(State{}),
ApSL(configLens, Of(Config{Port: 8080})),
Map(func(s State) int { return s.Config.Port }),
)
assert.Equal(t, 8080, result())
}
func TestSequenceT1(t *testing.T) {
result := SequenceT1(Of(42))
tuple := result()
assert.Equal(t, 42, tuple.F1)
}
func TestSequenceT2(t *testing.T) {
result := SequenceT2(Of(42), Of("hello"))
tuple := result()
assert.Equal(t, 42, tuple.F1)
assert.Equal(t, "hello", tuple.F2)
}
func TestSequenceT3(t *testing.T) {
result := SequenceT3(Of(42), Of("hello"), Of(true))
tuple := result()
assert.Equal(t, 42, tuple.F1)
assert.Equal(t, "hello", tuple.F2)
assert.Equal(t, true, tuple.F3)
}
func TestSequenceT4(t *testing.T) {
result := SequenceT4(Of(42), Of("hello"), Of(true), Of(3.14))
tuple := result()
assert.Equal(t, 42, tuple.F1)
assert.Equal(t, "hello", tuple.F2)
assert.Equal(t, true, tuple.F3)
assert.Equal(t, 3.14, tuple.F4)
}
func TestTraverseArray(t *testing.T) {
numbers := []int{1, 2, 3}
result := F.Pipe1(
numbers,
TraverseArray(func(x int) Lazy[int] {
return Of(x * 2)
}),
)
assert.Equal(t, []int{2, 4, 6}, result())
}
func TestTraverseArrayWithIndex(t *testing.T) {
numbers := []int{10, 20, 30}
result := F.Pipe1(
numbers,
TraverseArrayWithIndex(func(i int, x int) Lazy[int] {
return Of(x + i)
}),
)
assert.Equal(t, []int{10, 21, 32}, result())
}
func TestSequenceArray(t *testing.T) {
lazies := []Lazy[int]{Of(1), Of(2), Of(3)}
result := SequenceArray(lazies)
assert.Equal(t, []int{1, 2, 3}, result())
}
func TestMonadTraverseArray(t *testing.T) {
numbers := []int{1, 2, 3}
result := MonadTraverseArray(numbers, func(x int) Lazy[int] {
return Of(x * 2)
})
assert.Equal(t, []int{2, 4, 6}, result())
}
func TestTraverseRecord(t *testing.T) {
record := map[string]int{"a": 1, "b": 2}
result := F.Pipe1(
record,
TraverseRecord[string](func(x int) Lazy[int] {
return Of(x * 2)
}),
)
resultMap := result()
assert.Equal(t, 2, resultMap["a"])
assert.Equal(t, 4, resultMap["b"])
}
func TestTraverseRecordWithIndex(t *testing.T) {
record := map[string]int{"a": 10, "b": 20}
result := F.Pipe1(
record,
TraverseRecordWithIndex(func(k string, x int) Lazy[int] {
if k == "a" {
return Of(x + 1)
}
return Of(x + 2)
}),
)
resultMap := result()
assert.Equal(t, 11, resultMap["a"])
assert.Equal(t, 22, resultMap["b"])
}
func TestSequenceRecord(t *testing.T) {
record := map[string]Lazy[int]{
"a": Of(1),
"b": Of(2),
}
result := SequenceRecord(record)
resultMap := result()
assert.Equal(t, 1, resultMap["a"])
assert.Equal(t, 2, resultMap["b"])
}
func TestMonadTraverseRecord(t *testing.T) {
record := map[string]int{"a": 1, "b": 2}
result := MonadTraverseRecord(record, func(x int) Lazy[int] {
return Of(x * 2)
})
resultMap := result()
assert.Equal(t, 2, resultMap["a"])
assert.Equal(t, 4, resultMap["b"])
}
func TestApplySemigroup(t *testing.T) {
sg := ApplySemigroup(M.MakeMonoid(
func(a, b int) int { return a + b },
0,
))
result := sg.Concat(Of(5), Of(10))
assert.Equal(t, 15, result())
}
func TestApplicativeMonoid(t *testing.T) {
mon := ApplicativeMonoid(M.MakeMonoid(
func(a, b int) int { return a + b },
0,
))
// Test Empty
empty := mon.Empty()
assert.Equal(t, 0, empty())
// Test Concat
result := mon.Concat(Of(5), Of(10))
assert.Equal(t, 15, result())
// Test identity laws
left := mon.Concat(mon.Empty(), Of(5))
assert.Equal(t, 5, left())
right := mon.Concat(Of(5), mon.Empty())
assert.Equal(t, 5, right())
}
func TestEq(t *testing.T) {
eq := Eq(EQ.FromEquals(func(a, b int) bool { return a == b }))
assert.True(t, eq.Equals(Of(42), Of(42)))
assert.False(t, eq.Equals(Of(42), Of(43)))
}
func TestComplexDoNotation(t *testing.T) {
// Test a more complex do-notation scenario
result := F.Pipe3(
Do(utils.Empty),
Bind(utils.SetLastName, func(s utils.Initial) Lazy[string] {
return Of("Doe")
}),
Bind(utils.SetGivenName, func(s utils.WithLastName) Lazy[string] {
return Of("John")
}),
Map(utils.GetFullName),
)
assert.Equal(t, "John Doe", result())
}
func TestChainComposition(t *testing.T) {
// Test chaining multiple operations
double := func(x int) Lazy[int] {
return Of(x * 2)
}
addTen := func(x int) Lazy[int] {
return Of(x + 10)
}
result := F.Pipe2(
Of(5),
Chain(double),
Chain(addTen),
)
assert.Equal(t, 20, result())
}
func TestMapComposition(t *testing.T) {
// Test mapping multiple transformations
result := F.Pipe3(
Of(5),
Map(func(x int) int { return x * 2 }),
Map(func(x int) int { return x + 10 }),
Map(func(x int) int { return x }),
)
assert.Equal(t, 20, result())
}
// Made with Bob

View File

@@ -22,18 +22,56 @@ import (
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
// SequenceT1 combines a single lazy computation into a lazy tuple.
// This is mainly useful for consistency with the other SequenceT functions.
//
// Example:
//
// lazy1 := lazy.Of(42)
// result := lazy.SequenceT1(lazy1)()
// // result is tuple.Tuple1[int]{F1: 42}
func SequenceT1[A any](a Lazy[A]) Lazy[tuple.Tuple1[A]] {
return io.SequenceT1(a)
}
// SequenceT2 combines two lazy computations into a lazy tuple of two elements.
// Both computations are evaluated when the result is evaluated.
//
// Example:
//
// lazy1 := lazy.Of(42)
// lazy2 := lazy.Of("hello")
// result := lazy.SequenceT2(lazy1, lazy2)()
// // result is tuple.Tuple2[int, string]{F1: 42, F2: "hello"}
func SequenceT2[A, B any](a Lazy[A], b Lazy[B]) Lazy[tuple.Tuple2[A, B]] {
return io.SequenceT2(a, b)
}
// SequenceT3 combines three lazy computations into a lazy tuple of three elements.
// All computations are evaluated when the result is evaluated.
//
// Example:
//
// lazy1 := lazy.Of(42)
// lazy2 := lazy.Of("hello")
// lazy3 := lazy.Of(true)
// result := lazy.SequenceT3(lazy1, lazy2, lazy3)()
// // result is tuple.Tuple3[int, string, bool]{F1: 42, F2: "hello", F3: true}
func SequenceT3[A, B, C any](a Lazy[A], b Lazy[B], c Lazy[C]) Lazy[tuple.Tuple3[A, B, C]] {
return io.SequenceT3(a, b, c)
}
// SequenceT4 combines four lazy computations into a lazy tuple of four elements.
// All computations are evaluated when the result is evaluated.
//
// Example:
//
// lazy1 := lazy.Of(42)
// lazy2 := lazy.Of("hello")
// lazy3 := lazy.Of(true)
// lazy4 := lazy.Of(3.14)
// result := lazy.SequenceT4(lazy1, lazy2, lazy3, lazy4)()
// // result is tuple.Tuple4[int, string, bool, float64]{F1: 42, F2: "hello", F3: true, F4: 3.14}
func SequenceT4[A, B, C, D any](a Lazy[A], b Lazy[B], c Lazy[C], d Lazy[D]) Lazy[tuple.Tuple4[A, B, C, D]] {
return io.SequenceT4(a, b, c, d)
}

View File

@@ -17,6 +17,18 @@ package lazy
import "github.com/IBM/fp-go/v2/io"
// MonadTraverseArray applies a function returning a lazy computation to all elements
// in an array and transforms this into a lazy computation of that array.
//
// This is the monadic version of TraverseArray, taking the array as the first parameter.
//
// Example:
//
// numbers := []int{1, 2, 3}
// result := lazy.MonadTraverseArray(numbers, func(x int) lazy.Lazy[int] {
// return lazy.Of(x * 2)
// })()
// // result is []int{2, 4, 6}
func MonadTraverseArray[A, B any](tas []A, f Kleisli[A, B]) Lazy[[]B] {
return io.MonadTraverseArray(tas, f)
}
@@ -38,6 +50,18 @@ func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] {
return io.SequenceArray(tas)
}
// MonadTraverseRecord applies a function returning a lazy computation to all values
// in a record (map) and transforms this into a lazy computation of that record.
//
// This is the monadic version of TraverseRecord, taking the record as the first parameter.
//
// Example:
//
// record := map[string]int{"a": 1, "b": 2}
// result := lazy.MonadTraverseRecord(record, func(x int) lazy.Lazy[int] {
// return lazy.Of(x * 2)
// })()
// // result is map[string]int{"a": 2, "b": 4}
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f Kleisli[A, B]) Lazy[map[K]B] {
return io.MonadTraverseRecord(tas, f)
}

View File

@@ -1,9 +1,60 @@
package lazy
type (
// Lazy represents a synchronous computation without side effects
// Lazy represents a synchronous computation without side effects.
// It is a function that takes no arguments and returns a value of type A.
//
// Lazy computations are evaluated only when their result is needed (lazy evaluation).
// This allows for:
// - Deferring expensive computations until they're actually required
// - Creating infinite data structures
// - Implementing memoization patterns
// - Composing pure computations in a functional style
//
// Example:
//
// // Create a lazy computation
// computation := lazy.Of(42)
//
// // Transform it (not evaluated yet)
// doubled := lazy.Map(func(x int) int { return x * 2 })(computation)
//
// // Evaluate when needed
// result := doubled() // 84
//
// Note: Lazy is an alias for io.IO[A] but represents pure computations
// without side effects, whereas IO represents computations that may have side effects.
Lazy[A any] = func() A
Kleisli[A, B any] = func(A) Lazy[B]
// Kleisli represents a function that takes a value of type A and returns
// a lazy computation producing a value of type B.
//
// Kleisli arrows are used for composing monadic computations. They allow
// you to chain operations where each step depends on the result of the previous step.
//
// Example:
//
// // A Kleisli arrow that doubles a number lazily
// double := func(x int) lazy.Lazy[int] {
// return lazy.Of(x * 2)
// }
//
// // Chain it with another operation
// result := lazy.Chain(double)(lazy.Of(5))() // 10
Kleisli[A, B any] = func(A) Lazy[B]
// Operator represents a function that takes a lazy computation of type A
// and returns a lazy computation of type B.
//
// Operators are used to transform lazy computations. They are essentially
// Kleisli arrows where the input is already wrapped in a Lazy context.
//
// Example:
//
// // An operator that doubles the value in a lazy computation
// doubleOp := lazy.Map(func(x int) int { return x * 2 })
//
// // Apply it to a lazy computation
// result := doubleOp(lazy.Of(5))() // 10
Operator[A, B any] = Kleisli[Lazy[A], B]
)

View File

@@ -352,8 +352,11 @@ func FromEither[E, T any]() Prism[Either[E, T], T] {
// - Working with optional fields that use zero as "not set"
// - Replacing zero values with defaults
func FromZero[T comparable]() Prism[T, T] {
var zero T
return MakePrism(option.FromPredicate(func(t T) bool { return t == zero }), F.Identity[T])
return MakePrism(option.FromZero[T](), F.Identity[T])
}
func FromNonZero[T comparable]() Prism[T, T] {
return MakePrism(option.FromNonZero[T](), F.Identity[T])
}
// Match represents a regex match result with full reconstruction capability.

View File

@@ -18,6 +18,7 @@ package prism
import (
"github.com/IBM/fp-go/v2/either"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
)
type (
@@ -93,4 +94,6 @@ type (
// - FromEither for creating prisms that work with Either types
// - Prism composition for building complex error-handling pipelines
Either[E, T any] = either.Either[E, T]
Reader[R, T any] = reader.Reader[R, T]
)

View File

@@ -17,9 +17,11 @@
package option
import (
"github.com/IBM/fp-go/v2/eq"
F "github.com/IBM/fp-go/v2/function"
C "github.com/IBM/fp-go/v2/internal/chain"
FC "github.com/IBM/fp-go/v2/internal/functor"
P "github.com/IBM/fp-go/v2/predicate"
)
// fromPredicate creates an Option based on a predicate function.
@@ -43,9 +45,19 @@ func FromPredicate[A any](pred func(A) bool) Kleisli[A, A] {
return F.Bind2nd(fromPredicate[A], pred)
}
//go:inline
func FromZero[A comparable]() Kleisli[A, A] {
var zero A
return FromPredicate(func(a A) bool { return zero == a })
return FromPredicate(P.IsZero[A]())
}
//go:inline
func FromNonZero[A comparable]() Kleisli[A, A] {
return FromPredicate(P.IsNonZero[A]())
}
//go:inline
func FromEq[A any](pred eq.Eq[A]) func(A) Kleisli[A, A] {
return F.Flow2(P.IsEqual(pred), FromPredicate[A])
}
// FromNillable converts a pointer to an Option.

View File

@@ -18,6 +18,7 @@ package predicate
import (
"testing"
"github.com/IBM/fp-go/v2/eq"
F "github.com/IBM/fp-go/v2/function"
"github.com/stretchr/testify/assert"
)
@@ -408,3 +409,272 @@ func TestComplexScenarios(t *testing.T) {
assert.False(t, canBuy(Item{Price: 150, Stock: 0}))
})
}
// TestIsEqual tests the IsEqual function
func TestIsEqual(t *testing.T) {
t.Run("works with custom equality", func(t *testing.T) {
type Person struct {
Name string
Age int
}
// Custom equality that only compares names
nameEq := eq.FromEquals(func(a, b Person) bool {
return a.Name == b.Name
})
isEqualToPerson := IsEqual(nameEq)
alice := Person{Name: "Alice", Age: 30}
isAlice := isEqualToPerson(alice)
assert.True(t, isAlice(Person{Name: "Alice", Age: 30}))
assert.True(t, isAlice(Person{Name: "Alice", Age: 25})) // Different age, same name
assert.False(t, isAlice(Person{Name: "Bob", Age: 30}))
})
t.Run("works with struct equality", func(t *testing.T) {
type Point struct {
X, Y int
}
pointEq := eq.FromStrictEquals[Point]()
isEqualToPoint := IsEqual(pointEq)
origin := Point{X: 0, Y: 0}
isOrigin := isEqualToPoint(origin)
assert.True(t, isOrigin(Point{X: 0, Y: 0}))
assert.False(t, isOrigin(Point{X: 1, Y: 0}))
assert.False(t, isOrigin(Point{X: 0, Y: 1}))
})
t.Run("can be used with And/Or", func(t *testing.T) {
intEq := eq.FromStrictEquals[int]()
isEqualTo5 := IsEqual(intEq)(5)
isEqualTo10 := IsEqual(intEq)(10)
is5Or10 := F.Pipe1(isEqualTo5, Or(isEqualTo10))
assert.True(t, is5Or10(5))
assert.True(t, is5Or10(10))
assert.False(t, is5Or10(7))
})
}
// TestIsStrictEqual tests the IsStrictEqual function
func TestIsStrictEqual(t *testing.T) {
t.Run("works with integers", func(t *testing.T) {
isEqualTo42 := IsStrictEqual[int]()(42)
assert.True(t, isEqualTo42(42))
assert.False(t, isEqualTo42(0))
assert.False(t, isEqualTo42(-42))
})
t.Run("works with strings", func(t *testing.T) {
isEqualToHello := IsStrictEqual[string]()("hello")
assert.True(t, isEqualToHello("hello"))
assert.False(t, isEqualToHello("Hello"))
assert.False(t, isEqualToHello("world"))
assert.False(t, isEqualToHello(""))
})
t.Run("works with booleans", func(t *testing.T) {
isEqualToTrue := IsStrictEqual[bool]()(true)
assert.True(t, isEqualToTrue(true))
assert.False(t, isEqualToTrue(false))
isEqualToFalse := IsStrictEqual[bool]()(false)
assert.True(t, isEqualToFalse(false))
assert.False(t, isEqualToFalse(true))
})
t.Run("works with floats", func(t *testing.T) {
isEqualTo3Point14 := IsStrictEqual[float64]()(3.14)
assert.True(t, isEqualTo3Point14(3.14))
assert.False(t, isEqualTo3Point14(3.15))
assert.False(t, isEqualTo3Point14(0.0))
})
t.Run("can be combined with other predicates", func(t *testing.T) {
isEqualTo5 := IsStrictEqual[int]()(5)
isNotEqualTo5 := Not(isEqualTo5)
assert.False(t, isNotEqualTo5(5))
assert.True(t, isNotEqualTo5(10))
assert.True(t, isNotEqualTo5(0))
})
}
// TestIsZero tests the IsZero function
func TestIsZero(t *testing.T) {
t.Run("works with integers", func(t *testing.T) {
isZeroInt := IsZero[int]()
assert.True(t, isZeroInt(0))
assert.False(t, isZeroInt(1))
assert.False(t, isZeroInt(-1))
assert.False(t, isZeroInt(100))
})
t.Run("works with strings", func(t *testing.T) {
isZeroString := IsZero[string]()
assert.True(t, isZeroString(""))
assert.False(t, isZeroString("hello"))
assert.False(t, isZeroString(" "))
assert.False(t, isZeroString("0"))
})
t.Run("works with booleans", func(t *testing.T) {
isZeroBool := IsZero[bool]()
assert.True(t, isZeroBool(false))
assert.False(t, isZeroBool(true))
})
t.Run("works with floats", func(t *testing.T) {
isZeroFloat := IsZero[float64]()
assert.True(t, isZeroFloat(0.0))
assert.False(t, isZeroFloat(0.1))
assert.False(t, isZeroFloat(-0.1))
})
t.Run("works with pointers", func(t *testing.T) {
isZeroPtr := IsZero[*int]()
assert.True(t, isZeroPtr(nil))
x := 42
assert.False(t, isZeroPtr(&x))
})
t.Run("works with structs", func(t *testing.T) {
type Point struct {
X, Y int
}
isZeroPoint := IsZero[Point]()
assert.True(t, isZeroPoint(Point{X: 0, Y: 0}))
assert.False(t, isZeroPoint(Point{X: 1, Y: 0}))
assert.False(t, isZeroPoint(Point{X: 0, Y: 1}))
})
t.Run("can be combined with other predicates", func(t *testing.T) {
isZeroInt := IsZero[int]()
isPositiveOrZero := F.Pipe1(isPositive, Or(isZeroInt))
assert.True(t, isPositiveOrZero(5))
assert.True(t, isPositiveOrZero(0))
assert.False(t, isPositiveOrZero(-5))
})
}
// TestIsNonZero tests the IsNonZero function
func TestIsNonZero(t *testing.T) {
t.Run("works with integers", func(t *testing.T) {
isNonZeroInt := IsNonZero[int]()
assert.False(t, isNonZeroInt(0))
assert.True(t, isNonZeroInt(1))
assert.True(t, isNonZeroInt(-1))
assert.True(t, isNonZeroInt(100))
})
t.Run("works with strings", func(t *testing.T) {
isNonZeroString := IsNonZero[string]()
assert.False(t, isNonZeroString(""))
assert.True(t, isNonZeroString("hello"))
assert.True(t, isNonZeroString(" "))
assert.True(t, isNonZeroString("0"))
})
t.Run("works with booleans", func(t *testing.T) {
isNonZeroBool := IsNonZero[bool]()
assert.False(t, isNonZeroBool(false))
assert.True(t, isNonZeroBool(true))
})
t.Run("works with floats", func(t *testing.T) {
isNonZeroFloat := IsNonZero[float64]()
assert.False(t, isNonZeroFloat(0.0))
assert.True(t, isNonZeroFloat(0.1))
assert.True(t, isNonZeroFloat(-0.1))
})
t.Run("works with pointers", func(t *testing.T) {
isNonZeroPtr := IsNonZero[*int]()
assert.False(t, isNonZeroPtr(nil))
x := 42
assert.True(t, isNonZeroPtr(&x))
y := 0
assert.True(t, isNonZeroPtr(&y)) // Pointer itself is non-nil
})
t.Run("is opposite of IsZero", func(t *testing.T) {
isZeroInt := IsZero[int]()
isNonZeroInt := IsNonZero[int]()
testValues := []int{-100, -1, 0, 1, 100}
for _, v := range testValues {
assert.Equal(t, !isZeroInt(v), isNonZeroInt(v), "IsNonZero should be opposite of IsZero for value %d", v)
}
})
t.Run("can be combined with other predicates", func(t *testing.T) {
isNonZeroInt := IsNonZero[int]()
isNonZeroAndPositive := F.Pipe1(isNonZeroInt, And(isPositive))
assert.True(t, isNonZeroAndPositive(5))
assert.False(t, isNonZeroAndPositive(0))
assert.False(t, isNonZeroAndPositive(-5))
})
}
// TestPredicatesIntegration tests integration of predicates.go functions with other predicate operations
func TestPredicatesIntegration(t *testing.T) {
t.Run("filter with IsZero", func(t *testing.T) {
numbers := []int{0, 1, 0, 2, 0, 3}
isZeroInt := IsZero[int]()
var nonZeros []int
for _, n := range numbers {
if !isZeroInt(n) {
nonZeros = append(nonZeros, n)
}
}
assert.Equal(t, []int{1, 2, 3}, nonZeros)
})
t.Run("validation with IsNonZero", func(t *testing.T) {
type Config struct {
Host string
Port int
}
isNonZeroString := IsNonZero[string]()
isNonZeroInt := IsNonZero[int]()
getHost := func(c Config) string { return c.Host }
getPort := func(c Config) int { return c.Port }
hasHost := F.Pipe1(isNonZeroString, ContraMap(getHost))
hasPort := F.Pipe1(isNonZeroInt, ContraMap(getPort))
isValid := F.Pipe1(hasHost, And(hasPort))
assert.True(t, isValid(Config{Host: "localhost", Port: 8080}))
assert.False(t, isValid(Config{Host: "", Port: 8080}))
assert.False(t, isValid(Config{Host: "localhost", Port: 0}))
assert.False(t, isValid(Config{Host: "", Port: 0}))
})
t.Run("equality with monoid", func(t *testing.T) {
m := MonoidAny[int]()
isEqualTo1 := IsStrictEqual[int]()(1)
isEqualTo2 := IsStrictEqual[int]()(2)
isEqualTo3 := IsStrictEqual[int]()(3)
is1Or2Or3 := m.Concat(m.Concat(isEqualTo1, isEqualTo2), isEqualTo3)
assert.True(t, is1Or2Or3(1))
assert.True(t, is1Or2Or3(2))
assert.True(t, is1Or2Or3(3))
assert.False(t, is1Or2Or3(4))
assert.False(t, is1Or2Or3(0))
})
}

120
v2/predicate/predicates.go Normal file
View File

@@ -0,0 +1,120 @@
// Copyright (c) 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package predicate
import (
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/eq"
)
// IsEqual creates a Kleisli arrow that tests if two values are equal using a custom equality function.
//
// This function takes an Eq instance (which defines how to compare values of type A) and returns
// a curried function that can be used to create predicates for equality testing.
//
// Parameters:
// - pred: An Eq[A] instance that defines equality for type A
//
// Returns:
// - A Kleisli[A, A] that takes a value and returns a predicate testing equality with that value
//
// Example:
//
// type Person struct { Name string; Age int }
// personEq := eq.MakeEq(func(a, b Person) bool {
// return a.Name == b.Name && a.Age == b.Age
// })
// isEqualToPerson := IsEqual(personEq)
// alice := Person{Name: "Alice", Age: 30}
// isAlice := isEqualToPerson(alice)
// isAlice(Person{Name: "Alice", Age: 30}) // true
// isAlice(Person{Name: "Bob", Age: 30}) // false
func IsEqual[A any](pred eq.Eq[A]) Kleisli[A, A] {
return F.Curry2(pred.Equals)
}
// IsStrictEqual creates a Kleisli arrow that tests if two values are equal using Go's == operator.
//
// This is a convenience function for comparable types that uses strict equality (==) for comparison.
// It's equivalent to IsEqual with an Eq instance based on ==.
//
// Returns:
// - A Kleisli[A, A] that takes a value and returns a predicate testing strict equality
//
// Example:
//
// isEqualTo5 := IsStrictEqual[int]()(5)
// isEqualTo5(5) // true
// isEqualTo5(10) // false
//
// isEqualToHello := IsStrictEqual[string]()("hello")
// isEqualToHello("hello") // true
// isEqualToHello("world") // false
func IsStrictEqual[A comparable]() Kleisli[A, A] {
return IsEqual(eq.FromStrictEquals[A]())
}
// IsZero creates a predicate that tests if a value equals the zero value for its type.
//
// The zero value is the default value for a type in Go (e.g., 0 for int, "" for string,
// false for bool, nil for pointers, etc.).
//
// Returns:
// - A Predicate[A] that returns true if the value is the zero value for type A
//
// Example:
//
// isZeroInt := IsZero[int]()
// isZeroInt(0) // true
// isZeroInt(5) // false
//
// isZeroString := IsZero[string]()
// isZeroString("") // true
// isZeroString("hello") // false
//
// isZeroBool := IsZero[bool]()
// isZeroBool(false) // true
// isZeroBool(true) // false
func IsZero[A comparable]() Predicate[A] {
var zero A
return IsStrictEqual[A]()(zero)
}
// IsNonZero creates a predicate that tests if a value is not equal to the zero value for its type.
//
// This is the negation of IsZero, returning true for any non-zero value.
//
// Returns:
// - A Predicate[A] that returns true if the value is not the zero value for type A
//
// Example:
//
// isNonZeroInt := IsNonZero[int]()
// isNonZeroInt(0) // false
// isNonZeroInt(5) // true
// isNonZeroInt(-3) // true
//
// isNonZeroString := IsNonZero[string]()
// isNonZeroString("") // false
// isNonZeroString("hello") // true
//
// isNonZeroPtr := IsNonZero[*int]()
// isNonZeroPtr(nil) // false
// isNonZeroPtr(new(int)) // true
func IsNonZero[A comparable]() Predicate[A] {
return Not(IsZero[A]())
}

View File

@@ -45,7 +45,9 @@ type (
// It is commonly used for filtering, validation, and conditional logic.
Predicate[A any] = func(A) bool
Kleisli[A, B any] = func(A) Predicate[B]
// Operator represents a function that transforms a Predicate[A] into a Predicate[B].
// This is useful for composing and transforming predicates.
Operator[A, B any] = func(Predicate[A]) Predicate[B]
Operator[A, B any] = Kleisli[Predicate[A], B]
)

View File

@@ -22,8 +22,12 @@ import (
)
type (
Option[A any] = option.Option[A]
Either[E, A any] = either.Either[E, A]
Reader[R, A any] = reader.Reader[R, A]
Option[A any] = option.Option[A]
Either[E, A any] = either.Either[E, A]
Reader[R, A any] = reader.Reader[R, A]
ReaderEither[R, E, A any] = Reader[R, Either[E, A]]
Kleisli[R, E, A, B any] = Reader[A, ReaderEither[R, E, B]]
Operator[R, E, A, B any] = Kleisli[R, E, ReaderEither[R, E, A], B]
)

View File

@@ -161,7 +161,7 @@ func MonadChainReaderK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either
// Deprecated:
func ChainReaderK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GB ~func(R) B, R, E, A, B any](f func(A) GB) func(GEA) GEB {
return FR.ChainReaderK(
MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B],
Chain[GEA, GEB, GIOA, GIOB, R, E, A, B],
FromReader[GB, GEB, GIOB, R, E, B],
f,
)
@@ -180,7 +180,7 @@ func MonadChainReaderIOK[GEA ~func(R) GIOEA, GEB ~func(R) GIOEB, GIOEA ~func() e
// Deprecated:
func ChainReaderIOK[GEA ~func(R) GIOEA, GEB ~func(R) GIOEB, GIOEA ~func() either.Either[E, A], GIOEB ~func() either.Either[E, B], GIOB ~func() B, GB ~func(R) GIOB, R, E, A, B any](f func(A) GB) func(GEA) GEB {
return FR.ChainReaderK(
MonadChain[GEA, GEB, GIOEA, GIOEB, R, E, A, B],
Chain[GEA, GEB, GIOEA, GIOEB, R, E, A, B],
RightReaderIO[GEB, GIOEB, GB, GIOB, R, E, B],
f,
)

View File

@@ -28,12 +28,18 @@ import (
"github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
L "github.com/IBM/fp-go/v2/lazy"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
RE "github.com/IBM/fp-go/v2/readereither"
"github.com/IBM/fp-go/v2/readerio"
"github.com/IBM/fp-go/v2/readeroption"
)
//go:inline
func FromReaderOption[R, A, E any](onNone func() E) Kleisli[R, E, ReaderOption[R, A], A] {
return function.Bind2nd(function.Flow2[ReaderOption[R, A], IOE.Kleisli[E, Option[A], A]], IOE.FromOption[A](onNone))
}
//go:inline
func FromReaderIO[E, R, A any](ma ReaderIO[R, A]) ReaderIOEither[R, E, A] {
return RightReaderIO[E](ma)
}
@@ -116,7 +122,7 @@ func MonadChainFirst[R, E, A, B any](fa ReaderIOEither[R, E, A], f func(A) Reade
// The Either is automatically lifted into the ReaderIOEither context.
//
//go:inline
func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) either.Either[E, B]) ReaderIOEither[R, E, B] {
func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f either.Kleisli[E, A, B]) ReaderIOEither[R, E, B] {
return fromeither.MonadChainEitherK(
MonadChain[R, E, A, B],
FromEither[R, E, B],
@@ -129,7 +135,7 @@ func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) eit
// This is the curried version of MonadChainEitherK.
//
//go:inline
func ChainEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E, A, B] {
func ChainEitherK[R, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, E, A, B] {
return fromeither.ChainEitherK(
Chain[R, E, A, B],
FromEither[R, E, B],
@@ -141,7 +147,7 @@ func ChainEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E,
// Useful for validation or side effects that return Either.
//
//go:inline
func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) either.Either[E, B]) ReaderIOEither[R, E, A] {
func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f either.Kleisli[E, A, B]) ReaderIOEither[R, E, A] {
return fromeither.MonadChainFirstEitherK(
MonadChain[R, E, A, A],
MonadMap[R, E, B, A],
@@ -155,7 +161,7 @@ func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A
// This is the curried version of MonadChainFirstEitherK.
//
//go:inline
func ChainFirstEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E, A, A] {
func ChainFirstEitherK[R, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, E, A, A] {
return fromeither.ChainFirstEitherK(
Chain[R, E, A, A],
Map[R, E, B, A],
@@ -168,7 +174,7 @@ func ChainFirstEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R
// The Reader is automatically lifted into the ReaderIOEither context.
//
//go:inline
func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) Reader[R, B]) ReaderIOEither[R, E, B] {
func MonadChainReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader.Kleisli[R, A, B]) ReaderIOEither[R, E, B] {
return fromreader.MonadChainReaderK(
MonadChain[R, E, A, B],
FromReader[E, R, B],
@@ -181,19 +187,147 @@ func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) Rea
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainReaderK[E, R, A, B any](f func(A) Reader[R, B]) Operator[R, E, A, B] {
func ChainReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E, A, B] {
return fromreader.ChainReaderK(
MonadChain[R, E, A, B],
Chain[R, E, A, B],
FromReader[E, R, B],
f,
)
}
//go:inline
func MonadChainFirstReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
return fromreader.MonadChainFirstReaderK(
MonadChainFirst[R, E, A, B],
FromReader[E, R, B],
ma,
f,
)
}
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither.
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainFirstReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E, A, A] {
return fromreader.ChainFirstReaderK(
ChainFirst[R, E, A, B],
FromReader[E, R, B],
f,
)
}
//go:inline
func MonadChainReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, B] {
return fromreader.MonadChainReaderK(
MonadChain[R, E, A, B],
FromReaderIO[E, R, B],
ma,
f,
)
}
//go:inline
func ChainReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, B] {
return fromreader.ChainReaderK(
Chain[R, E, A, B],
FromReaderIO[E, R, B],
f,
)
}
//go:inline
func MonadChainFirstReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
return fromreader.MonadChainFirstReaderK(
MonadChainFirst[R, E, A, B],
FromReaderIO[E, R, B],
ma,
f,
)
}
//go:inline
func ChainFirstReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, A] {
return fromreader.ChainFirstReaderK(
ChainFirst[R, E, A, B],
FromReaderIO[E, R, B],
f,
)
}
//go:inline
func MonadChainReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, B] {
return fromreader.MonadChainReaderK(
MonadChain[R, E, A, B],
FromReaderEither[R, E, B],
ma,
f,
)
}
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither.
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, E, A, B] {
return fromreader.ChainReaderK(
Chain[R, E, A, B],
FromReaderEither[R, E, B],
f,
)
}
//go:inline
func MonadChainFirstReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, A] {
return fromreader.MonadChainFirstReaderK(
MonadChainFirst[R, E, A, B],
FromReaderEither[R, E, B],
ma,
f,
)
}
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither.
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainFirstReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, E, A, A] {
return fromreader.ChainFirstReaderK(
ChainFirst[R, E, A, B],
FromReaderEither[R, E, B],
f,
)
}
//go:inline
func ChainReaderOptionK[R, A, B, E any](onNone func() E) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, B] {
fro := FromReaderOption[R, B](onNone)
return func(f readeroption.Kleisli[R, A, B]) Operator[R, E, A, B] {
return fromreader.ChainReaderK(
Chain[R, E, A, B],
fro,
f,
)
}
}
//go:inline
func ChainFirstReaderOptionK[R, A, B, E any](onNone func() E) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, A] {
fro := FromReaderOption[R, B](onNone)
return func(f readeroption.Kleisli[R, A, B]) Operator[R, E, A, A] {
return fromreader.ChainFirstReaderK(
ChainFirst[R, E, A, B],
fro,
f,
)
}
}
// MonadChainIOEitherK chains an IOEither-returning computation into a ReaderIOEither.
// The IOEither is automatically lifted into the ReaderIOEither context.
//
//go:inline
func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) IOE.IOEither[E, B]) ReaderIOEither[R, E, B] {
func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f IOE.Kleisli[E, A, B]) ReaderIOEither[R, E, B] {
return fromioeither.MonadChainIOEitherK(
MonadChain[R, E, A, B],
FromIOEither[R, E, B],
@@ -206,7 +340,7 @@ func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) I
// This is the curried version of MonadChainIOEitherK.
//
//go:inline
func ChainIOEitherK[R, E, A, B any](f func(A) IOE.IOEither[E, B]) Operator[R, E, A, B] {
func ChainIOEitherK[R, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, E, A, B] {
return fromioeither.ChainIOEitherK(
Chain[R, E, A, B],
FromIOEither[R, E, B],
@@ -218,7 +352,7 @@ func ChainIOEitherK[R, E, A, B any](f func(A) IOE.IOEither[E, B]) Operator[R, E,
// The IO is automatically lifted into the ReaderIOEither context (always succeeds).
//
//go:inline
func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B]) ReaderIOEither[R, E, B] {
func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f io.Kleisli[A, B]) ReaderIOEither[R, E, B] {
return fromio.MonadChainIOK(
MonadChain[R, E, A, B],
FromIO[R, E, B],
@@ -231,7 +365,7 @@ func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B
// This is the curried version of MonadChainIOK.
//
//go:inline
func ChainIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, B] {
func ChainIOK[R, E, A, B any](f io.Kleisli[A, B]) Operator[R, E, A, B] {
return fromio.ChainIOK(
Chain[R, E, A, B],
FromIO[R, E, B],
@@ -243,7 +377,7 @@ func ChainIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, B] {
// Useful for performing IO side effects while preserving the original value.
//
//go:inline
func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B]) ReaderIOEither[R, E, A] {
func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f io.Kleisli[A, B]) ReaderIOEither[R, E, A] {
return fromio.MonadChainFirstIOK(
MonadChain[R, E, A, A],
MonadMap[R, E, B, A],
@@ -257,7 +391,7 @@ func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io
// This is the curried version of MonadChainFirstIOK.
//
//go:inline
func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, A] {
func ChainFirstIOK[R, E, A, B any](f io.Kleisli[A, B]) Operator[R, E, A, A] {
return fromio.ChainFirstIOK(
Chain[R, E, A, A],
Map[R, E, B, A],
@@ -270,7 +404,7 @@ func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, A] {
// If the Option is None, the provided error function is called to produce the error value.
//
//go:inline
func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) O.Option[B]) Operator[R, E, A, B] {
func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) Option[B]) Operator[R, E, A, B] {
return fromeither.ChainOptionK(
MonadChain[R, E, A, B],
FromEither[R, E, B],
@@ -400,18 +534,18 @@ func FromReader[E, R, A any](ma Reader[R, A]) ReaderIOEither[R, E, A] {
}
// RightIO lifts an IO into a ReaderIOEither, placing the result in the Right side.
func RightIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] {
func RightIO[R, E, A any](ma IO[A]) ReaderIOEither[R, E, A] {
return function.Pipe2(ma, IOE.RightIO[E, A], FromIOEither[R, E, A])
}
// LeftIO lifts an IO into a ReaderIOEither, placing the result in the Left (error) side.
func LeftIO[R, A, E any](ma io.IO[E]) ReaderIOEither[R, E, A] {
func LeftIO[R, A, E any](ma IO[E]) ReaderIOEither[R, E, A] {
return function.Pipe2(ma, IOE.LeftIO[A, E], FromIOEither[R, E, A])
}
// FromIO lifts an IO into a ReaderIOEither context.
// The IO result is placed in the Right side (success).
func FromIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] {
func FromIO[R, E, A any](ma IO[A]) ReaderIOEither[R, E, A] {
return RightIO[R, E](ma)
}
@@ -419,7 +553,7 @@ func FromIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] {
// The computation becomes independent of any reader context.
//
//go:inline
func FromIOEither[R, E, A any](ma IOE.IOEither[E, A]) ReaderIOEither[R, E, A] {
func FromIOEither[R, E, A any](ma IOEither[E, A]) ReaderIOEither[R, E, A] {
return reader.Of[R](ma)
}
@@ -449,7 +583,7 @@ func Asks[E, R, A any](r Reader[R, A]) ReaderIOEither[R, E, A] {
// If the Option is None, the provided function is called to produce the error.
//
//go:inline
func FromOption[R, A, E any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] {
func FromOption[R, A, E any](onNone func() E) func(Option[A]) ReaderIOEither[R, E, A] {
return fromeither.FromOption(FromEither[R, E, A], onNone)
}

View File

@@ -19,8 +19,10 @@ import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/optics/lens/option"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readerio"
"github.com/IBM/fp-go/v2/readeroption"
)
type (
@@ -84,4 +86,7 @@ type (
// Example:
// var doubleOp Operator[Config, error, int, int] = Map(func(x int) int { return x * 2 })
Operator[R, E, A, B any] = Kleisli[R, E, ReaderIOEither[R, E, A], B]
ReaderOption[R, A any] = readeroption.ReaderOption[R, A]
Option[A any] = option.Option[A]
)

View File

@@ -16,12 +16,22 @@
package readerioresult
import (
"github.com/IBM/fp-go/v2/reader"
RE "github.com/IBM/fp-go/v2/readereither"
"github.com/IBM/fp-go/v2/readerio"
RIOE "github.com/IBM/fp-go/v2/readerioeither"
"github.com/IBM/fp-go/v2/readeroption"
)
//go:inline
func FromReaderOption[R, A any](onNone func() error) Kleisli[R, ReaderOption[R, A], A] {
return RIOE.FromReaderOption[R, A](onNone)
}
// FromReaderIO creates a function that lifts a ReaderIO-producing function into ReaderIOResult.
// The ReaderIO result is placed in the Right side of the Either.
//
//go:inline
func FromReaderIO[R, A any](ma ReaderIO[R, A]) ReaderIOResult[R, A] {
return RIOE.FromReaderIO[error](ma)
}
@@ -158,7 +168,7 @@ func ChainFirstResultK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
// The Reader is automatically lifted into the ReaderIOResult context.
//
//go:inline
func MonadChainReaderK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Reader[R, B]) ReaderIOResult[R, B] {
func MonadChainReaderK[R, A, B any](ma ReaderIOResult[R, A], f reader.Kleisli[R, A, B]) ReaderIOResult[R, B] {
return RIOE.MonadChainReaderK(ma, f)
}
@@ -166,10 +176,99 @@ func MonadChainReaderK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Reader[R,
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainReaderK[R, A, B any](f func(A) Reader[R, B]) Operator[R, A, B] {
func ChainReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, B] {
return RIOE.ChainReaderK[error](f)
}
//go:inline
func MonadChainFirstReaderK[R, A, B any](ma ReaderIOResult[R, A], f reader.Kleisli[R, A, B]) ReaderIOResult[R, A] {
return RIOE.MonadChainFirstReaderK(ma, f)
}
//go:inline
func ChainFirstReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A] {
return RIOE.ChainFirstReaderK[error](f)
}
//go:inline
func ChainReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, B] {
return RIOE.ChainReaderOptionK[R, A, B](onNone)
}
//go:inline
func ChainFirstReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
return RIOE.ChainFirstReaderOptionK[R, A, B](onNone)
}
// MonadChainReaderK chains a Reader-returning computation into a ReaderIOResult.
// The Reader is automatically lifted into the ReaderIOResult context.
//
//go:inline
func MonadChainReaderEitherK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, B] {
return RIOE.MonadChainReaderEitherK(ma, f)
}
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOResult.
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, B] {
return RIOE.ChainReaderEitherK(f)
}
//go:inline
func MonadChainFirstReaderEitherK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
return RIOE.MonadChainFirstReaderEitherK(ma, f)
}
//go:inline
func ChainFirstReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
return RIOE.ChainFirstReaderEitherK(f)
}
//go:inline
func MonadChainReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, B] {
return RIOE.MonadChainReaderEitherK(ma, f)
}
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOResult.
// This is the curried version of MonadChainReaderK.
//
//go:inline
func ChainReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, B] {
return RIOE.ChainReaderEitherK(f)
}
//go:inline
func MonadChainFirstReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
return RIOE.MonadChainFirstReaderEitherK(ma, f)
}
//go:inline
func ChainFirstReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
return RIOE.ChainFirstReaderEitherK(f)
}
//go:inline
func MonadChainReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, B] {
return RIOE.MonadChainReaderIOK(ma, f)
}
//go:inline
func ChainReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, B] {
return RIOE.ChainReaderIOK[error](f)
}
//go:inline
func MonadChainFirstReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, A] {
return RIOE.MonadChainFirstReaderIOK(ma, f)
}
//go:inline
func ChainFirstReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, A] {
return RIOE.ChainFirstReaderIOK[error](f)
}
// MonadChainIOEitherK chains an IOEither-returning computation into a ReaderIOResult.
// The IOEither is automatically lifted into the ReaderIOResult context.
//

View File

@@ -24,6 +24,7 @@ import (
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readerio"
"github.com/IBM/fp-go/v2/readeroption"
"github.com/IBM/fp-go/v2/result"
)
@@ -42,6 +43,8 @@ type (
// side effects to produce a value of type A.
ReaderIO[R, A any] = readerio.ReaderIO[R, A]
ReaderOption[R, A any] = readeroption.ReaderOption[R, A]
// IOEither represents a computation that performs side effects and can either
// fail with an error of type E or succeed with a value of type A.
IOEither[E, A any] = ioeither.IOEither[E, A]

View File

@@ -53,7 +53,7 @@ func TestTraverseArray(t *testing.T) {
input1 := []int{1, 2, 3}
g1 := F.Pipe1(
Of[context.Context](input1),
Chain(TraverseArray[context.Context](doubleIfPositive)),
Chain(TraverseArray(doubleIfPositive)),
)
assert.Equal(t, O.Of([]int{2, 4, 6}), g1(context.Background()))
@@ -61,7 +61,7 @@ func TestTraverseArray(t *testing.T) {
input2 := []int{1, -2, 3}
g2 := F.Pipe1(
Of[context.Context](input2),
Chain(TraverseArray[context.Context](doubleIfPositive)),
Chain(TraverseArray(doubleIfPositive)),
)
assert.Equal(t, O.None[[]int](), g2(context.Background()))
@@ -69,7 +69,7 @@ func TestTraverseArray(t *testing.T) {
input3 := []int{}
g3 := F.Pipe1(
Of[context.Context](input3),
Chain(TraverseArray[context.Context](doubleIfPositive)),
Chain(TraverseArray(doubleIfPositive)),
)
assert.Equal(t, O.Of([]int{}), g3(context.Background()))
}
@@ -84,7 +84,7 @@ func TestTraverseArrayWithIndex(t *testing.T) {
}
input := []int{10, 20, 30, 40}
g := TraverseArrayWithIndex[context.Context](multiplyByIndexIfEven)(input)
g := TraverseArrayWithIndex(multiplyByIndexIfEven)(input)
// Expected: [10*0, 20, 30*2, 40] = [0, 20, 60, 40]
assert.Equal(t, O.Of([]int{0, 20, 60, 40}), g(context.Background()))
@@ -100,7 +100,7 @@ func TestTraverseArrayWithIndexNone(t *testing.T) {
}
input := []int{10, 20, 30}
g := TraverseArrayWithIndex[context.Context](noneForOdd)(input)
g := TraverseArrayWithIndex(noneForOdd)(input)
// Should return None because index 1 returns None
assert.Equal(t, O.None[[]int](), g(context.Background()))