Skip to content

Commit 9d31752

Browse files
authored
Rewrite the Retry logic based on Trampoline (#149)
* fix: implement retry via tail rec Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: base retry on Trampoline Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: refactor retry Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> --------- Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
1 parent 14b5256 commit 9d31752

58 files changed

Lines changed: 3427 additions & 411 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

v2/array/nonempty/types.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ package nonempty
33
import "github.com/IBM/fp-go/v2/option"
44

55
type (
6-
7-
// NonEmptyArray represents an array with at least one element
6+
// NonEmptyArray represents an array that is guaranteed to have at least one element.
7+
// This provides compile-time safety for operations that require non-empty collections.
88
NonEmptyArray[A any] []A
99

10+
// Kleisli represents a Kleisli arrow for the NonEmptyArray monad.
11+
// It's a function from A to NonEmptyArray[B], used for composing operations that produce non-empty arrays.
1012
Kleisli[A, B any] = func(A) NonEmptyArray[B]
1113

14+
// Operator represents a function that transforms one NonEmptyArray into another.
15+
// It takes a NonEmptyArray[A] and produces a NonEmptyArray[B].
1216
Operator[A, B any] = Kleisli[NonEmptyArray[A], B]
1317

18+
// Option represents an optional value that may or may not be present.
1419
Option[A any] = option.Option[A]
1520
)

v2/array/types.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ package array
33
import "github.com/IBM/fp-go/v2/option"
44

55
type (
6-
Kleisli[A, B any] = func(A) []B
6+
// Kleisli represents a Kleisli arrow for arrays.
7+
// It's a function from A to []B, used for composing operations that produce arrays.
8+
Kleisli[A, B any] = func(A) []B
9+
10+
// Operator represents a function that transforms one array into another.
11+
// It takes a []A and produces a []B.
712
Operator[A, B any] = Kleisli[[]A, B]
8-
Option[A any] = option.Option[A]
13+
14+
// Option represents an optional value that may or may not be present.
15+
Option[A any] = option.Option[A]
916
)

v2/assert/types.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,24 @@ import (
1212
)
1313

1414
type (
15-
Result[T any] = result.Result[T]
16-
Reader = reader.Reader[*testing.T, bool]
17-
Kleisli[T any] = reader.Reader[T, Reader]
18-
Predicate[T any] = predicate.Predicate[T]
19-
Lens[S, T any] = lens.Lens[S, T]
15+
// Result represents a computation that may fail with an error.
16+
Result[T any] = result.Result[T]
17+
18+
// Reader represents a test assertion that depends on a testing.T context and returns a boolean.
19+
Reader = reader.Reader[*testing.T, bool]
20+
21+
// Kleisli represents a function that produces a test assertion Reader from a value of type T.
22+
Kleisli[T any] = reader.Reader[T, Reader]
23+
24+
// Predicate represents a function that tests a value of type T and returns a boolean.
25+
Predicate[T any] = predicate.Predicate[T]
26+
27+
// Lens is a functional reference to a subpart of a data structure.
28+
Lens[S, T any] = lens.Lens[S, T]
29+
30+
// Optional is an optic that focuses on a value that may or may not be present.
2031
Optional[S, T any] = optional.Optional[S, T]
21-
Prism[S, T any] = prism.Prism[S, T]
32+
33+
// Prism is an optic that focuses on a case of a sum type.
34+
Prism[S, T any] = prism.Prism[S, T]
2235
)

v2/boolean/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ package boolean
1818
import "github.com/IBM/fp-go/v2/monoid"
1919

2020
type (
21+
// Monoid represents a monoid structure for boolean values.
22+
// A monoid provides an associative binary operation and an identity element.
2123
Monoid = monoid.Monoid[bool]
2224
)

v2/builder/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import (
77
)
88

99
type (
10+
// Result represents a computation that may fail with an error.
11+
// It's an alias for Either[error, T].
1012
Result[T any] = result.Result[T]
1113

14+
// Prism is an optic that focuses on a case of a sum type.
15+
// It provides a way to extract and construct values of a specific variant.
1216
Prism[S, A any] = prism.Prism[S, A]
1317

18+
// Option represents an optional value that may or may not be present.
1419
Option[T any] = option.Option[T]
1520
)

v2/consumer/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,7 @@ type (
5252
// }
5353
Consumer[A any] = func(A)
5454

55+
// Operator represents a function that transforms a Consumer[A] into a Consumer[B].
56+
// This is useful for composing and adapting consumers to work with different types.
5557
Operator[A, B any] = func(Consumer[A]) Consumer[B]
5658
)

v2/context/ioresult/ioeither.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,34 @@ import (
2121
"github.com/IBM/fp-go/v2/result"
2222
)
2323

24-
// withContext wraps an existing IOEither and performs a context check for cancellation before delegating
24+
// WithContext wraps an IOResult and performs a context check for cancellation before executing.
25+
// This ensures that if the context is already cancelled, the computation short-circuits immediately
26+
// without executing the wrapped computation.
27+
//
28+
// This is useful for adding cancellation awareness to computations that might not check the context themselves.
29+
//
30+
// Type Parameters:
31+
// - A: The type of the success value
32+
//
33+
// Parameters:
34+
// - ctx: The context to check for cancellation
35+
// - ma: The IOResult to wrap with context checking
36+
//
37+
// Returns:
38+
// - An IOResult that checks for cancellation before executing
39+
//
40+
// Example:
41+
//
42+
// computation := func() Result[string] {
43+
// // Long-running operation
44+
// return result.Of("done")
45+
// }
46+
//
47+
// ctx, cancel := context.WithCancel(context.Background())
48+
// cancel() // Cancel immediately
49+
//
50+
// wrapped := WithContext(ctx, computation)
51+
// result := wrapped() // Returns Left with context.Canceled error
2552
func WithContext[A any](ctx context.Context, ma IOResult[A]) IOResult[A] {
2653
return func() Result[A] {
2754
if ctx.Err() != nil {

v2/context/readerio/bracket.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,65 @@ import (
44
RIO "github.com/IBM/fp-go/v2/readerio"
55
)
66

7+
// Bracket ensures that a resource is properly acquired, used, and released, even if an error occurs.
8+
// This implements the bracket pattern for safe resource management with [ReaderIO].
9+
//
10+
// The bracket pattern guarantees that:
11+
// - The acquire action is executed first to obtain the resource
12+
// - The use function is called with the acquired resource
13+
// - The release function is always called with the resource and result, regardless of success or failure
14+
// - The final result from the use function is returned
15+
//
16+
// This is particularly useful for managing resources like file handles, database connections,
17+
// or locks that must be cleaned up properly.
18+
//
19+
// Type Parameters:
20+
// - A: The type of the acquired resource
21+
// - B: The type of the result produced by the use function
22+
// - ANY: The type returned by the release function (typically ignored)
23+
//
24+
// Parameters:
25+
// - acquire: A ReaderIO that acquires the resource
26+
// - use: A Kleisli arrow that uses the resource and produces a result
27+
// - release: A function that releases the resource, receiving both the resource and the result
28+
//
29+
// Returns:
30+
// - A ReaderIO[B] that safely manages the resource lifecycle
31+
//
32+
// Example:
33+
//
34+
// // Acquire a file handle
35+
// acquireFile := func(ctx context.Context) IO[*os.File] {
36+
// return func() *os.File {
37+
// f, _ := os.Open("data.txt")
38+
// return f
39+
// }
40+
// }
41+
//
42+
// // Use the file
43+
// readFile := func(f *os.File) ReaderIO[string] {
44+
// return func(ctx context.Context) IO[string] {
45+
// return func() string {
46+
// data, _ := io.ReadAll(f)
47+
// return string(data)
48+
// }
49+
// }
50+
// }
51+
//
52+
// // Release the file
53+
// closeFile := func(f *os.File, result string) ReaderIO[any] {
54+
// return func(ctx context.Context) IO[any] {
55+
// return func() any {
56+
// f.Close()
57+
// return nil
58+
// }
59+
// }
60+
// }
61+
//
62+
// // Safely read file with automatic cleanup
63+
// safeRead := Bracket(acquireFile, readFile, closeFile)
64+
// result := safeRead(context.Background())()
65+
//
766
//go:inline
867
func Bracket[
968
A, B, ANY any](

v2/context/readerio/consumer.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,63 @@ package readerio
22

33
import "github.com/IBM/fp-go/v2/io"
44

5+
// ChainConsumer chains a consumer function into a ReaderIO computation, discarding the original value.
6+
// This is useful for performing side effects (like logging or metrics) that consume a value
7+
// but don't produce a meaningful result.
8+
//
9+
// The consumer is executed for its side effects, and the computation returns an empty struct.
10+
//
11+
// Type Parameters:
12+
// - A: The type of value to consume
13+
//
14+
// Parameters:
15+
// - c: A consumer function that performs side effects on the value
16+
//
17+
// Returns:
18+
// - An Operator that chains the consumer and returns struct{}
19+
//
20+
// Example:
21+
//
22+
// logUser := func(u User) {
23+
// log.Printf("Processing user: %s", u.Name)
24+
// }
25+
//
26+
// pipeline := F.Pipe2(
27+
// fetchUser(123),
28+
// ChainConsumer(logUser),
29+
// )
30+
//
531
//go:inline
632
func ChainConsumer[A any](c Consumer[A]) Operator[A, struct{}] {
733
return ChainIOK(io.FromConsumerK(c))
834
}
935

36+
// ChainFirstConsumer chains a consumer function into a ReaderIO computation, preserving the original value.
37+
// This is useful for performing side effects (like logging or metrics) while passing the value through unchanged.
38+
//
39+
// The consumer is executed for its side effects, but the original value is returned.
40+
//
41+
// Type Parameters:
42+
// - A: The type of value to consume and return
43+
//
44+
// Parameters:
45+
// - c: A consumer function that performs side effects on the value
46+
//
47+
// Returns:
48+
// - An Operator that chains the consumer and returns the original value
49+
//
50+
// Example:
51+
//
52+
// logUser := func(u User) {
53+
// log.Printf("User: %s", u.Name)
54+
// }
55+
//
56+
// pipeline := F.Pipe3(
57+
// fetchUser(123),
58+
// ChainFirstConsumer(logUser), // Logs but passes user through
59+
// Map(func(u User) string { return u.Email }),
60+
// )
61+
//
1062
//go:inline
1163
func ChainFirstConsumer[A any](c Consumer[A]) Operator[A, A] {
1264
return ChainFirstIOK(io.FromConsumerK(c))

v2/context/readerio/flip.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,108 @@ import (
77
RIO "github.com/IBM/fp-go/v2/readerio"
88
)
99

10+
// SequenceReader transforms a ReaderIO containing a Reader into a Reader containing a ReaderIO.
11+
// This "flips" the nested structure, allowing you to provide the Reader's environment first,
12+
// then get a ReaderIO that can be executed with a context.
13+
//
14+
// Type transformation:
15+
//
16+
// From: ReaderIO[Reader[R, A]]
17+
// = func(context.Context) func() func(R) A
18+
//
19+
// To: Reader[R, ReaderIO[A]]
20+
// = func(R) func(context.Context) func() A
21+
//
22+
// This is useful for point-free style programming where you want to partially apply
23+
// the Reader's environment before dealing with the context.
24+
//
25+
// Type Parameters:
26+
// - R: The environment type that the Reader depends on
27+
// - A: The value type
28+
//
29+
// Parameters:
30+
// - ma: A ReaderIO containing a Reader
31+
//
32+
// Returns:
33+
// - A Reader that produces a ReaderIO when given an environment
34+
//
35+
// Example:
36+
//
37+
// type Config struct {
38+
// Timeout int
39+
// }
40+
//
41+
// // A computation that produces a Reader
42+
// getMultiplier := func(ctx context.Context) IO[func(Config) int] {
43+
// return func() func(Config) int {
44+
// return func(cfg Config) int {
45+
// return cfg.Timeout * 2
46+
// }
47+
// }
48+
// }
49+
//
50+
// // Sequence it to apply Config first
51+
// sequenced := SequenceReader[Config, int](getMultiplier)
52+
// cfg := Config{Timeout: 30}
53+
// result := sequenced(cfg)(context.Background())() // Returns 60
54+
//
1055
//go:inline
1156
func SequenceReader[R, A any](ma ReaderIO[Reader[R, A]]) Reader[R, ReaderIO[A]] {
1257
return RIO.SequenceReader(ma)
1358
}
1459

60+
// TraverseReader applies a Reader-based transformation to a ReaderIO, introducing a new environment dependency.
61+
//
62+
// This function takes a Reader-based Kleisli arrow and returns a function that can transform
63+
// a ReaderIO. The result allows you to provide the Reader's environment (R) first, which then
64+
// produces a ReaderIO that depends on the context.
65+
//
66+
// Type transformation:
67+
//
68+
// From: ReaderIO[A]
69+
// = func(context.Context) func() A
70+
//
71+
// With: reader.Kleisli[R, A, B]
72+
// = func(A) func(R) B
73+
//
74+
// To: func(ReaderIO[A]) func(R) ReaderIO[B]
75+
// = func(ReaderIO[A]) func(R) func(context.Context) func() B
76+
//
77+
// This enables transforming values within a ReaderIO using environment-dependent logic.
78+
//
79+
// Type Parameters:
80+
// - R: The environment type that the Reader depends on
81+
// - A: The input value type
82+
// - B: The output value type
83+
//
84+
// Parameters:
85+
// - f: A Reader-based Kleisli arrow that transforms A to B using environment R
86+
//
87+
// Returns:
88+
// - A function that takes a ReaderIO[A] and returns a function from R to ReaderIO[B]
89+
//
90+
// Example:
91+
//
92+
// type Config struct {
93+
// Multiplier int
94+
// }
95+
//
96+
// // A Reader-based transformation
97+
// multiply := func(x int) func(Config) int {
98+
// return func(cfg Config) int {
99+
// return x * cfg.Multiplier
100+
// }
101+
// }
102+
//
103+
// // Apply TraverseReader
104+
// traversed := TraverseReader[Config, int, int](multiply)
105+
// computation := Of(10)
106+
// result := traversed(computation)
107+
//
108+
// // Provide Config to get final result
109+
// cfg := Config{Multiplier: 5}
110+
// finalResult := result(cfg)(context.Background())() // Returns 50
111+
//
15112
//go:inline
16113
func TraverseReader[R, A, B any](
17114
f reader.Kleisli[R, A, B],

0 commit comments

Comments
 (0)