forked from destel/rill
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcore.go
211 lines (173 loc) · 6.01 KB
/
core.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package rill
import (
"sync"
"github.com/destel/rill/internal/core"
)
// Map applies a transformation function to each item in an input channel, using n goroutines for concurrency.
// If an error is encountered, either from the function f itself or from upstream it is forwarded to the output for further handling.
// The output order is not guaranteed: results are written to the output as soon as they're ready.
// Use [OrderedMap] to preserve the input order.
func Map[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan Try[B] {
return core.MapOrFilter(in, n, func(a Try[A]) (Try[B], bool) {
if a.Error != nil {
return Try[B]{Error: a.Error}, true
}
b, err := f(a.Value)
if err != nil {
return Try[B]{Error: err}, true
}
return Try[B]{Value: b}, true
})
}
// OrderedMap is similar to [Map], but it guarantees that the output order is the same as the input order.
func OrderedMap[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan Try[B] {
return core.OrderedMapOrFilter(in, n, func(a Try[A]) (Try[B], bool) {
if a.Error != nil {
return Try[B]{Error: a.Error}, true
}
b, err := f(a.Value)
if err != nil {
return Try[B]{Error: err}, true
}
return Try[B]{Value: b}, true
})
}
// Filter removes items that do not meet a specified condition, using n goroutines for concurrency.
// If an error is encountered, either from the function f itself or from upstream it is forwarded to the output for further handling.
// The output order is not guaranteed: results are written to the output as soon as they're ready.
// Use [OrderedFilter] to preserve the input order.
func Filter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-chan Try[A] {
return core.MapOrFilter(in, n, func(a Try[A]) (Try[A], bool) {
if a.Error != nil {
return a, true // never filter out errors
}
keep, err := f(a.Value)
if err != nil {
return Try[A]{Error: err}, true // never filter out errors
}
return a, keep
})
}
// OrderedFilter is similar to [Filter], but it guarantees that the output order is the same as the input order.
func OrderedFilter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-chan Try[A] {
return core.OrderedMapOrFilter(in, n, func(a Try[A]) (Try[A], bool) {
if a.Error != nil {
return a, true // never filter out errors
}
keep, err := f(a.Value)
if err != nil {
return Try[A]{Error: err}, true // never filter out errors
}
return a, keep
})
}
// FlatMap applies a function to each item in an input channel, where the function returns a channel of items.
// These items are then flattened into a single output channel using n goroutines for concurrency.
// The output order is not guaranteed: results are written to the output as soon as they're ready.
// Use [OrderedFlatMap] to preserve the input order.
func FlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan Try[B] {
if in == nil {
return nil
}
out := make(chan Try[B])
core.Loop(in, out, n, func(a Try[A]) {
if a.Error != nil {
out <- Try[B]{Error: a.Error}
return
}
bb := f(a.Value)
for b := range bb {
out <- b
}
})
return out
}
// OrderedFlatMap is similar to [FlatMap], but it guarantees that the output order is the same as the input order.
func OrderedFlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan Try[B] {
if in == nil {
return nil
}
out := make(chan Try[B])
core.OrderedLoop(in, out, n, func(a Try[A], canWrite <-chan struct{}) {
if a.Error != nil {
<-canWrite
out <- Try[B]{Error: a.Error}
return
}
bb := f(a.Value)
<-canWrite
for b := range bb {
out <- b
}
})
return out
}
// Catch allows handling errors from the input channel using n goroutines for concurrency.
// When f returns nil, error is considered handled and filtered out; otherwise it is replaced by the result of f.
// The output order is not guaranteed: results are written to the output as soon as they're ready.
// Use [OrderedCatch] to preserve the input order.
func Catch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
return core.MapOrFilter(in, n, func(a Try[A]) (Try[A], bool) {
if a.Error == nil {
return a, true
}
err := f(a.Error)
if err == nil {
return a, false // error handled, filter out
}
return Try[A]{Error: err}, true // error replaced by f(a.Error)
})
}
// OrderedCatch is similar to [Catch], but it guarantees that the output order is the same as the input order.
func OrderedCatch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
return core.OrderedMapOrFilter(in, n, func(a Try[A]) (Try[A], bool) {
if a.Error == nil {
return a, true
}
err := f(a.Error)
if err == nil {
return a, false // error handled, filter out
}
return Try[A]{Error: err}, true // error replaced by f(a.Error)
})
}
// ForEach applies a function f to each item in an input channel using n goroutines for parallel processing. The function
// blocks until all items are processed or an error is encountered, either from the function f itself or from upstream.
// In case of an error leading to early termination, ForEach ensures the input channel is drained to avoid goroutine leaks,
// making it safe for use in environments where cleanup is crucial. The function returns the first encountered error, or nil
// if all items were processed successfully.
// While this function does not guarantee the order of item processing due to its concurrent nature,
// using n = 1 results in sequential processing, as in a simple for-range loop.
func ForEach[A any](in <-chan Try[A], n int, f func(A) error) error {
if n == 1 {
for a := range in {
err := a.Error
if err == nil {
err = f(a.Value)
}
if err != nil {
DrainNB(in)
return err
}
}
return nil
}
var retErr error
var once sync.Once
in, earlyExit := core.Breakable(in)
done := make(chan struct{})
core.Loop(in, done, n, func(a Try[A]) {
err := a.Error
if err == nil {
err = f(a.Value)
}
if err != nil {
earlyExit()
once.Do(func() {
retErr = err
})
}
})
<-done
return retErr
}