1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-04-03 15:14:05 +02:00

Compare commits

..

3 Commits

Author SHA1 Message Date
renovate[bot]
c754cacf1f fix(deps): update module github.com/urfave/cli/v3 to v3.8.0 (#159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-25 20:40:07 +00:00
Dr. Carsten Leue
d357b32847 fix: add TapThunkK
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-23 18:43:49 +01:00
Dr. Carsten Leue
a3af003e74 fix: undo Pipe and Flow changes, did not have the desired effect
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-20 23:20:07 +01:00
5 changed files with 736 additions and 46 deletions

View File

@@ -204,6 +204,102 @@ func ChainFirst[C, A, B any](f Kleisli[C, A, B]) Operator[C, A, A] {
return readerreaderioresult.ChainFirst(f)
}
// ChainFirstThunkK chains an effect with a function that returns a Thunk,
// but discards the result and returns the original value.
// This is useful for performing side effects (like logging or IO operations) that don't
// need the effect's context, without changing the value flowing through the computation.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - A: The value type (preserved)
// - B: The type produced by the Thunk (discarded)
//
// # Parameters
//
// - f: A function that takes A and returns Thunk[B] for side effects
//
// # Returns
//
// - Operator[C, A, A]: A function that executes the Thunk but preserves the original value
//
// # Example
//
// logToFile := func(n int) readerioresult.ReaderIOResult[any] {
// return func(ctx context.Context) io.IO[result.Result[any]] {
// return func() result.Result[any] {
// // Perform IO operation that doesn't need effect context
// fmt.Printf("Logging: %d\n", n)
// return result.Of[any](nil)
// }
// }
// }
//
// eff := effect.Of[MyContext](42)
// logged := effect.ChainFirstThunkK[MyContext](logToFile)(eff)
// // Prints "Logging: 42" but still produces 42
//
// # See Also
//
// - ChainThunkK: Chains with a Thunk and uses its result
// - TapThunkK: Alias for ChainFirstThunkK
// - ChainFirstIOK: Similar but for IO operations
//
//go:inline
func ChainFirstThunkK[C, A, B any](f thunk.Kleisli[A, B]) Operator[C, A, A] {
return fromreader.ChainFirstReaderK(
ChainFirst[C, A, B],
FromThunk[C, B],
f,
)
}
// TapThunkK is an alias for ChainFirstThunkK.
// It chains an effect with a function that returns a Thunk for side effects,
// but preserves the original value. This is useful for logging, debugging, or
// performing IO operations that don't need the effect's context.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - A: The value type (preserved)
// - B: The type produced by the Thunk (discarded)
//
// # Parameters
//
// - f: A function that takes A and returns Thunk[B] for side effects
//
// # Returns
//
// - Operator[C, A, A]: A function that executes the Thunk but preserves the original value
//
// # Example
//
// performSideEffect := func(n int) readerioresult.ReaderIOResult[any] {
// return func(ctx context.Context) io.IO[result.Result[any]] {
// return func() result.Result[any] {
// // Perform context-independent IO operation
// log.Printf("Processing value: %d", n)
// return result.Of[any](nil)
// }
// }
// }
//
// eff := effect.Of[MyContext](42)
// tapped := effect.TapThunkK[MyContext](performSideEffect)(eff)
// // Logs "Processing value: 42" but still produces 42
//
// # See Also
//
// - ChainFirstThunkK: The underlying implementation
// - TapIOK: Similar but for IO operations
// - Tap: Similar but for full effects
//
//go:inline
func TapThunkK[C, A, B any](f thunk.Kleisli[A, B]) Operator[C, A, A] {
return ChainFirstThunkK[C](f)
}
// ChainIOK chains an effect with a function that returns an IO action.
// This is useful for integrating IO-based computations (synchronous side effects)
// into effect chains. The IO action is automatically lifted into the Effect context.

View File

@@ -678,6 +678,587 @@ func TestChainThunkK_Integration(t *testing.T) {
})
}
func TestChainFirstThunkK_Success(t *testing.T) {
t.Run("executes thunk but preserves original value", func(t *testing.T) {
sideEffectExecuted := false
sideEffect := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
sideEffectExecuted = true
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Of[TestConfig](42),
ChainFirstThunkK[TestConfig](sideEffect),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(42), outcome)
assert.True(t, sideEffectExecuted)
})
t.Run("chains multiple side effects", func(t *testing.T) {
log := []string{}
logValue := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("log: %d", n))
return result.Of[any](nil)
}
}
}
computation := F.Pipe2(
Of[TestConfig](10),
ChainFirstThunkK[TestConfig](logValue),
ChainFirstThunkK[TestConfig](logValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(10), outcome)
assert.Equal(t, 2, len(log))
assert.Equal(t, "log: 10", log[0])
assert.Equal(t, "log: 10", log[1])
})
t.Run("side effect can access runtime context", func(t *testing.T) {
var capturedCtx context.Context
captureContext := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
capturedCtx = ctx
return result.Of[any](nil)
}
}
}
ctx := context.Background()
computation := F.Pipe1(
Of[TestConfig](42),
ChainFirstThunkK[TestConfig](captureContext),
)
outcome := computation(testConfig)(ctx)()
assert.Equal(t, result.Of(42), outcome)
assert.Equal(t, ctx, capturedCtx)
})
t.Run("side effect result is discarded", func(t *testing.T) {
returnDifferentValue := func(n int) readerioresult.ReaderIOResult[string] {
return func(ctx context.Context) io.IO[result.Result[string]] {
return func() result.Result[string] {
return result.Of("different value")
}
}
}
computation := F.Pipe1(
Of[TestConfig](42),
ChainFirstThunkK[TestConfig](returnDifferentValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(42), outcome)
})
}
func TestChainFirstThunkK_Failure(t *testing.T) {
t.Run("propagates error from previous effect", func(t *testing.T) {
testErr := fmt.Errorf("previous error")
sideEffectExecuted := false
sideEffect := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
sideEffectExecuted = true
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Fail[TestConfig, int](testErr),
ChainFirstThunkK[TestConfig](sideEffect),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Left[int](testErr), outcome)
assert.False(t, sideEffectExecuted)
})
t.Run("propagates error from thunk side effect", func(t *testing.T) {
testErr := fmt.Errorf("side effect error")
failingSideEffect := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
return result.Left[any](testErr)
}
}
}
computation := F.Pipe1(
Of[TestConfig](42),
ChainFirstThunkK[TestConfig](failingSideEffect),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Left[int](testErr), outcome)
})
t.Run("stops execution on first error", func(t *testing.T) {
testErr := fmt.Errorf("first error")
secondEffectExecuted := false
failingEffect := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
return result.Left[any](testErr)
}
}
}
secondEffect := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
secondEffectExecuted = true
return result.Of[any](nil)
}
}
}
computation := F.Pipe2(
Of[TestConfig](42),
ChainFirstThunkK[TestConfig](failingEffect),
ChainFirstThunkK[TestConfig](secondEffect),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Left[int](testErr), outcome)
assert.False(t, secondEffectExecuted)
})
}
func TestChainFirstThunkK_EdgeCases(t *testing.T) {
t.Run("handles zero value", func(t *testing.T) {
callCount := 0
countCalls := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
callCount++
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Of[TestConfig](0),
ChainFirstThunkK[TestConfig](countCalls),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(0), outcome)
assert.Equal(t, 1, callCount)
})
t.Run("handles empty string", func(t *testing.T) {
var capturedValue string
captureValue := func(s string) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
capturedValue = s
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Of[TestConfig](""),
ChainFirstThunkK[TestConfig](captureValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(""), outcome)
assert.Equal(t, "", capturedValue)
})
t.Run("handles nil pointer", func(t *testing.T) {
var capturedPtr *int
capturePtr := func(ptr *int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
capturedPtr = ptr
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Of[TestConfig]((*int)(nil)),
ChainFirstThunkK[TestConfig](capturePtr),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of((*int)(nil)), outcome)
assert.Nil(t, capturedPtr)
})
}
func TestChainFirstThunkK_Integration(t *testing.T) {
t.Run("composes with Map and Chain", func(t *testing.T) {
log := []string{}
logValue := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("value: %d", n))
return result.Of[any](nil)
}
}
}
computation := F.Pipe3(
Of[TestConfig](5),
Map[TestConfig](func(x int) int { return x * 2 }),
ChainFirstThunkK[TestConfig](logValue),
Map[TestConfig](func(x int) int { return x + 3 }),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(13), outcome) // (5 * 2) + 3
assert.Equal(t, 1, len(log))
assert.Equal(t, "value: 10", log[0])
})
t.Run("composes with ChainThunkK", func(t *testing.T) {
log := []string{}
logSideEffect := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("side-effect: %d", n))
return result.Of[any](nil)
}
}
}
transformValue := func(n int) readerioresult.ReaderIOResult[string] {
return func(ctx context.Context) io.IO[result.Result[string]] {
return func() result.Result[string] {
log = append(log, fmt.Sprintf("transform: %d", n))
return result.Of(fmt.Sprintf("Result: %d", n))
}
}
}
computation := F.Pipe2(
Of[TestConfig](42),
ChainFirstThunkK[TestConfig](logSideEffect),
ChainThunkK[TestConfig](transformValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of("Result: 42"), outcome)
assert.Equal(t, 2, len(log))
assert.Equal(t, "side-effect: 42", log[0])
assert.Equal(t, "transform: 42", log[1])
})
t.Run("composes with ChainReaderK and ChainReaderIOK", func(t *testing.T) {
log := []string{}
addMultiplier := func(n int) reader.Reader[TestConfig, int] {
return func(cfg TestConfig) int {
return n + cfg.Multiplier
}
}
logReaderIO := func(n int) readerio.ReaderIO[TestConfig, int] {
return func(cfg TestConfig) io.IO[int] {
return func() int {
log = append(log, fmt.Sprintf("reader-io: %d", n))
return n * 2
}
}
}
logThunk := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("thunk: %d", n))
return result.Of[any](nil)
}
}
}
computation := F.Pipe3(
Of[TestConfig](5),
ChainReaderK(addMultiplier),
ChainReaderIOK(logReaderIO),
ChainFirstThunkK[TestConfig](logThunk),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(16), outcome) // (5 + 3) * 2
assert.Equal(t, 2, len(log))
assert.Equal(t, "reader-io: 8", log[0])
assert.Equal(t, "thunk: 16", log[1])
})
}
func TestTapThunkK_Success(t *testing.T) {
t.Run("is alias for ChainFirstThunkK", func(t *testing.T) {
log := []string{}
logValue := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("tapped: %d", n))
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Of[TestConfig](42),
TapThunkK[TestConfig](logValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(42), outcome)
assert.Equal(t, 1, len(log))
assert.Equal(t, "tapped: 42", log[0])
})
t.Run("useful for logging without changing value", func(t *testing.T) {
log := []string{}
logStep := func(step string) func(int) readerioresult.ReaderIOResult[any] {
return func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("%s: %d", step, n))
return result.Of[any](nil)
}
}
}
}
computation := F.Pipe4(
Of[TestConfig](10),
TapThunkK[TestConfig](logStep("start")),
Map[TestConfig](func(x int) int { return x * 2 }),
TapThunkK[TestConfig](logStep("after-map")),
Map[TestConfig](func(x int) int { return x + 5 }),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(25), outcome) // (10 * 2) + 5
assert.Equal(t, 2, len(log))
assert.Equal(t, "start: 10", log[0])
assert.Equal(t, "after-map: 20", log[1])
})
t.Run("can perform IO operations", func(t *testing.T) {
var ioExecuted bool
performIO := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
// Simulate IO operation
ioExecuted = true
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Of[TestConfig](42),
TapThunkK[TestConfig](performIO),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(42), outcome)
assert.True(t, ioExecuted)
})
}
func TestTapThunkK_Failure(t *testing.T) {
t.Run("propagates error from previous effect", func(t *testing.T) {
testErr := fmt.Errorf("previous error")
tapExecuted := false
tapValue := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
tapExecuted = true
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
Fail[TestConfig, int](testErr),
TapThunkK[TestConfig](tapValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Left[int](testErr), outcome)
assert.False(t, tapExecuted)
})
t.Run("propagates error from tap operation", func(t *testing.T) {
testErr := fmt.Errorf("tap error")
failingTap := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
return result.Left[any](testErr)
}
}
}
computation := F.Pipe1(
Of[TestConfig](42),
TapThunkK[TestConfig](failingTap),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Left[int](testErr), outcome)
})
}
func TestTapThunkK_EdgeCases(t *testing.T) {
t.Run("handles multiple taps in sequence", func(t *testing.T) {
log := []string{}
tap1 := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, "tap1")
return result.Of[any](nil)
}
}
}
tap2 := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, "tap2")
return result.Of[any](nil)
}
}
}
tap3 := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, "tap3")
return result.Of[any](nil)
}
}
}
computation := F.Pipe3(
Of[TestConfig](42),
TapThunkK[TestConfig](tap1),
TapThunkK[TestConfig](tap2),
TapThunkK[TestConfig](tap3),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(42), outcome)
assert.Equal(t, []string{"tap1", "tap2", "tap3"}, log)
})
}
func TestTapThunkK_Integration(t *testing.T) {
t.Run("real-world logging scenario", func(t *testing.T) {
log := []string{}
logStart := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("Starting computation with: %d", n))
return result.Of[any](nil)
}
}
}
logIntermediate := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("Intermediate result: %d", n))
return result.Of[any](nil)
}
}
}
logFinal := func(s string) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("Final result: %s", s))
return result.Of[any](nil)
}
}
}
computation := F.Pipe5(
Of[TestConfig](10),
TapThunkK[TestConfig](logStart),
Map[TestConfig](func(x int) int { return x * 3 }),
TapThunkK[TestConfig](logIntermediate),
Map[TestConfig](func(x int) string { return fmt.Sprintf("Value: %d", x) }),
TapThunkK[TestConfig](logFinal),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of("Value: 30"), outcome)
assert.Equal(t, 3, len(log))
assert.Equal(t, "Starting computation with: 10", log[0])
assert.Equal(t, "Intermediate result: 30", log[1])
assert.Equal(t, "Final result: Value: 30", log[2])
})
t.Run("composes with FromThunk", func(t *testing.T) {
log := []string{}
thunk := func(ctx context.Context) io.IO[result.Result[int]] {
return func() result.Result[int] {
return result.Of(100)
}
}
logValue := func(n int) readerioresult.ReaderIOResult[any] {
return func(ctx context.Context) io.IO[result.Result[any]] {
return func() result.Result[any] {
log = append(log, fmt.Sprintf("value: %d", n))
return result.Of[any](nil)
}
}
}
computation := F.Pipe1(
FromThunk[TestConfig](thunk),
TapThunkK[TestConfig](logValue),
)
outcome := computation(testConfig)(context.Background())()
assert.Equal(t, result.Of(100), outcome)
assert.Equal(t, 1, len(log))
assert.Equal(t, "value: 100", log[0])
})
}
func TestAsks_Success(t *testing.T) {
t.Run("extracts a field from context", func(t *testing.T) {
type Config struct {
@@ -685,7 +1266,7 @@ func TestAsks_Success(t *testing.T) {
Port int
}
getHost := Asks[Config](func(cfg Config) string {
getHost := Asks(func(cfg Config) string {
return cfg.Host
})
@@ -701,7 +1282,7 @@ func TestAsks_Success(t *testing.T) {
Port int
}
getURL := Asks[Config](func(cfg Config) string {
getURL := Asks(func(cfg Config) string {
return fmt.Sprintf("http://%s:%d", cfg.Host, cfg.Port)
})
@@ -712,7 +1293,7 @@ func TestAsks_Success(t *testing.T) {
})
t.Run("extracts numeric field", func(t *testing.T) {
getPort := Asks[TestConfig](func(cfg TestConfig) int {
getPort := Asks(func(cfg TestConfig) int {
return cfg.Multiplier
})
@@ -728,7 +1309,7 @@ func TestAsks_Success(t *testing.T) {
Height int
}
getArea := Asks[Config](func(cfg Config) int {
getArea := Asks(func(cfg Config) int {
return cfg.Width * cfg.Height
})
@@ -739,7 +1320,7 @@ func TestAsks_Success(t *testing.T) {
})
t.Run("transforms string field", func(t *testing.T) {
getUpperPrefix := Asks[TestConfig](func(cfg TestConfig) string {
getUpperPrefix := Asks(func(cfg TestConfig) string {
return fmt.Sprintf("[%s]", cfg.Prefix)
})
@@ -756,7 +1337,7 @@ func TestAsks_EdgeCases(t *testing.T) {
Value int
}
getValue := Asks[Config](func(cfg Config) int {
getValue := Asks(func(cfg Config) int {
return cfg.Value
})
@@ -771,7 +1352,7 @@ func TestAsks_EdgeCases(t *testing.T) {
Name string
}
getName := Asks[Config](func(cfg Config) string {
getName := Asks(func(cfg Config) string {
return cfg.Name
})
@@ -786,7 +1367,7 @@ func TestAsks_EdgeCases(t *testing.T) {
Data *string
}
hasData := Asks[Config](func(cfg Config) bool {
hasData := Asks(func(cfg Config) bool {
return cfg.Data != nil
})
@@ -805,7 +1386,7 @@ func TestAsks_EdgeCases(t *testing.T) {
DB Database
}
getDBHost := Asks[Config](func(cfg Config) string {
getDBHost := Asks(func(cfg Config) string {
return cfg.DB.Host
})
@@ -825,7 +1406,7 @@ func TestAsks_Integration(t *testing.T) {
}
computation := F.Pipe1(
Asks[Config](func(cfg Config) int {
Asks(func(cfg Config) int {
return cfg.Value
}),
Map[Config](func(x int) int { return x * 2 }),
@@ -843,7 +1424,7 @@ func TestAsks_Integration(t *testing.T) {
}
computation := F.Pipe1(
Asks[Config](func(cfg Config) int {
Asks(func(cfg Config) int {
return cfg.Multiplier
}),
Chain(func(mult int) Effect[Config, int] {
@@ -859,7 +1440,7 @@ func TestAsks_Integration(t *testing.T) {
t.Run("composes with ChainReaderK", func(t *testing.T) {
computation := F.Pipe1(
Asks[TestConfig](func(cfg TestConfig) int {
Asks(func(cfg TestConfig) int {
return cfg.Multiplier
}),
ChainReaderK(func(mult int) reader.Reader[TestConfig, int] {
@@ -879,7 +1460,7 @@ func TestAsks_Integration(t *testing.T) {
log := []string{}
computation := F.Pipe1(
Asks[TestConfig](func(cfg TestConfig) string {
Asks(func(cfg TestConfig) string {
return cfg.Prefix
}),
ChainReaderIOK(func(prefix string) readerio.ReaderIO[TestConfig, string] {
@@ -906,11 +1487,11 @@ func TestAsks_Integration(t *testing.T) {
}
computation := F.Pipe2(
Asks[Config](func(cfg Config) string {
Asks(func(cfg Config) string {
return cfg.First
}),
Chain(func(_ string) Effect[Config, string] {
return Asks[Config](func(cfg Config) string {
return Asks(func(cfg Config) string {
return cfg.Second
})
}),
@@ -933,7 +1514,7 @@ func TestAsks_Integration(t *testing.T) {
computation := F.Pipe1(
Ask[Config](),
Chain(func(cfg Config) Effect[Config, int] {
return Asks[Config](func(c Config) int {
return Asks(func(c Config) int {
return c.Value * 2
})
}),
@@ -953,7 +1534,7 @@ func TestAsks_Comparison(t *testing.T) {
}
// Using Asks
asksVersion := Asks[Config](func(cfg Config) int {
asksVersion := Asks(func(cfg Config) int {
return cfg.Port
})
@@ -983,7 +1564,7 @@ func TestAsks_Comparison(t *testing.T) {
}
// Asks is more direct for field extraction
getHost := Asks[Config](func(cfg Config) string {
getHost := Asks(func(cfg Config) string {
return cfg.Host
})
@@ -1003,7 +1584,7 @@ func TestAsks_RealWorldScenarios(t *testing.T) {
User string
}
getConnectionString := Asks[DatabaseConfig](func(cfg DatabaseConfig) string {
getConnectionString := Asks(func(cfg DatabaseConfig) string {
return fmt.Sprintf("postgres://%s@%s:%d/%s",
cfg.User, cfg.Host, cfg.Port, cfg.Database)
})
@@ -1027,7 +1608,7 @@ func TestAsks_RealWorldScenarios(t *testing.T) {
BasePath string
}
getEndpoint := Asks[APIConfig](func(cfg APIConfig) string {
getEndpoint := Asks(func(cfg APIConfig) string {
return fmt.Sprintf("%s://%s:%d%s",
cfg.Protocol, cfg.Host, cfg.Port, cfg.BasePath)
})
@@ -1049,7 +1630,7 @@ func TestAsks_RealWorldScenarios(t *testing.T) {
MaxRetries int
}
isValid := Asks[Config](func(cfg Config) bool {
isValid := Asks(func(cfg Config) bool {
return cfg.Timeout > 0 && cfg.MaxRetries >= 0
})

View File

@@ -1,3 +1,7 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-03-09 23:52:50.7205396 +0100 CET m=+0.001580301
package function
// Pipe0 takes an initial value t0 and successively applies 0 functions where the input of a function is the return value of the previous function
@@ -93,7 +97,6 @@ func Unsliced1[F ~func([]T) R, T, R any](f F) func(T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe2[F1 ~func(T0) T1, F2 ~func(T1) T2, T0, T1, T2 any](t0 T0, f1 F1, f2 F2) T2 {
//go:inline
return f2(f1(t0))
}
@@ -161,7 +164,6 @@ func Unsliced2[F ~func([]T) R, T, R any](f F) func(T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe3[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, T0, T1, T2, T3 any](t0 T0, f1 F1, f2 F2, f3 F3) T3 {
//go:inline
return f3(f2(f1(t0)))
}
@@ -227,8 +229,7 @@ func Unsliced3[F ~func([]T) R, T, R any](f F) func(T, T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T0, T1, T2, T3, T4 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4) T4 {
//go:inline
return Pipe2(Pipe2(t0, f1, f2), f3, f4)
return f4(f3(f2(f1(t0))))
}
// Flow4 creates a function that takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function
@@ -236,7 +237,9 @@ func Pipe4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T
//go:inline
func Flow4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T0, T1, T2, T3, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T0) T4 {
//go:inline
return Flow2(Flow2(f1, f2), Flow2(f3, f4))
return func(t0 T0) T4 {
return Pipe4(t0, f1, f2, f3, f4)
}
}
// Nullary4 creates a parameter less function from a parameter less function and 3 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -293,8 +296,7 @@ func Unsliced4[F ~func([]T) R, T, R any](f F) func(T, T, T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T0, T1, T2, T3, T4, T5 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) T5 {
//go:inline
return Pipe3(Pipe2(t0, f1, f2), f3, f4, f5)
return f5(f4(f3(f2(f1(t0)))))
}
// Flow5 creates a function that takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function
@@ -302,7 +304,9 @@ func Pipe5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
//go:inline
func Flow5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T0, T1, T2, T3, T4, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T0) T5 {
//go:inline
return Flow2(Flow2(f1, f2), Flow3(f3, f4, f5))
return func(t0 T0) T5 {
return Pipe5(t0, f1, f2, f3, f4, f5)
}
}
// Nullary5 creates a parameter less function from a parameter less function and 4 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -361,8 +365,7 @@ func Unsliced5[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) T6 {
//go:inline
return Pipe3(Pipe3(t0, f1, f2, f3), f4, f5, f6)
return f6(f5(f4(f3(f2(f1(t0))))))
}
// Flow6 creates a function that takes an initial value t0 and successively applies 6 functions where the input of a function is the return value of the previous function
@@ -370,7 +373,9 @@ func Pipe6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
//go:inline
func Flow6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(T0) T6 {
//go:inline
return Flow2(Flow3(f1, f2, f3), Flow3(f4, f5, f6))
return func(t0 T0) T6 {
return Pipe6(t0, f1, f2, f3, f4, f5, f6)
}
}
// Nullary6 creates a parameter less function from a parameter less function and 5 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -431,8 +436,7 @@ func Unsliced6[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) T7 {
//go:inline
return Pipe3(Pipe4(t0, f1, f2, f3, f4), f5, f6, f7)
return f7(f6(f5(f4(f3(f2(f1(t0)))))))
}
// Flow7 creates a function that takes an initial value t0 and successively applies 7 functions where the input of a function is the return value of the previous function
@@ -440,7 +444,9 @@ func Pipe7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
//go:inline
func Flow7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(T0) T7 {
//go:inline
return Flow2(Flow3(f1, f2, f3), Flow4(f4, f5, f6, f7))
return func(t0 T0) T7 {
return Pipe7(t0, f1, f2, f3, f4, f5, f6, f7)
}
}
// Nullary7 creates a parameter less function from a parameter less function and 6 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -503,8 +509,7 @@ func Unsliced7[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) T8 {
//go:inline
return Pipe4(Pipe4(t0, f1, f2, f3, f4), f5, f6, f7, f8)
return f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))
}
// Flow8 creates a function that takes an initial value t0 and successively applies 8 functions where the input of a function is the return value of the previous function
@@ -512,7 +517,9 @@ func Pipe8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
//go:inline
func Flow8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(T0) T8 {
//go:inline
return Flow2(Flow4(f1, f2, f3, f4), Flow4(f5, f6, f7, f8))
return func(t0 T0) T8 {
return Pipe8(t0, f1, f2, f3, f4, f5, f6, f7, f8)
}
}
// Nullary8 creates a parameter less function from a parameter less function and 7 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -577,8 +584,7 @@ func Unsliced8[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T) R {
// The final return value is the result of the last function application
//go:inline
func Pipe9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) T9 {
//go:inline
return Pipe4(Pipe5(t0, f1, f2, f3, f4, f5), f6, f7, f8, f9)
return f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))
}
// Flow9 creates a function that takes an initial value t0 and successively applies 9 functions where the input of a function is the return value of the previous function
@@ -586,7 +592,9 @@ func Pipe9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
//go:inline
func Flow9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(T0) T9 {
//go:inline
return Flow2(Flow4(f1, f2, f3, f4), Flow5(f5, f6, f7, f8, f9))
return func(t0 T0) T9 {
return Pipe9(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9)
}
}
// Nullary9 creates a parameter less function from a parameter less function and 8 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -653,8 +661,7 @@ func Unsliced9[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T) R
// The final return value is the result of the last function application
//go:inline
func Pipe10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) T10 {
//go:inline
return Pipe5(Pipe5(t0, f1, f2, f3, f4, f5), f6, f7, f8, f9, f10)
return f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))
}
// Flow10 creates a function that takes an initial value t0 and successively applies 10 functions where the input of a function is the return value of the previous function
@@ -662,7 +669,9 @@ func Pipe10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
//go:inline
func Flow10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(T0) T10 {
//go:inline
return Flow2(Flow5(f1, f2, f3, f4, f5), Flow5(f6, f7, f8, f9, f10))
return func(t0 T0) T10 {
return Pipe10(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10)
}
}
// Nullary10 creates a parameter less function from a parameter less function and 9 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
@@ -739,7 +748,9 @@ func Pipe11[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
//go:inline
func Flow11[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(T0) T11 {
//go:inline
return Flow2(Flow5(f1, f2, f3, f4, f5), Flow6(f6, f7, f8, f9, f10, f11))
return func(t0 T0) T11 {
return Pipe11(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11)
}
}
// Nullary11 creates a parameter less function from a parameter less function and 10 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions

View File

@@ -4,7 +4,7 @@ go 1.24
require (
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v3 v3.7.0
github.com/urfave/cli/v3 v3.8.0
)
require (

View File

@@ -8,6 +8,8 @@ github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=