Repository navigation
Expand file tree
/
Copy pathtransform.go
More file actions
285 lines (240 loc) · 8.2 KB
/
Copy pathtransform.go
File metadata and controls
285 lines (240 loc) · 8.2 KB
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package rill
import (
"github.com/destel/rill/internal/core"
)
// Map takes a stream of values of type A and returns a stream of values of
// type B, using f to transform each. When f returns an error, it's written
// to the output instead of a value.
//
// The argument n bounds the number of concurrent calls to f.
// Results and errors are written to the output in completion order.
// 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] {
validateN(n)
validateNilFunc(f == nil)
return core.FilterMap(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 the ordered version of [Map]:
// it writes results and errors in input order rather than completion order.
func OrderedMap[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan Try[B] {
validateN(n)
validateNilFunc(f == nil)
return core.OrderedFilterMap(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 takes a stream of values and returns a new stream, keeping
// only the values that match the condition f. When f returns an error,
// it's written to the output instead of the value.
// Errors are never filtered out.
//
// The argument n bounds the number of concurrent calls to f.
// Results and errors are written to the output in completion order.
// 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] {
validateN(n)
validateNilFunc(f == nil)
return core.FilterMap(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 the ordered version of [Filter]:
// it writes results and errors in input order rather than completion order.
func OrderedFilter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-chan Try[A] {
validateN(n)
validateNilFunc(f == nil)
return core.OrderedFilterMap(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
})
}
// FilterMap takes a stream of values of type A and returns a stream of
// values of type B, using f to transform each value and decide whether
// to keep the result. When f returns an error, it's written to the
// output instead of a value. Errors are never filtered out.
//
// The argument n bounds the number of concurrent calls to f.
// Results and errors are written to the output in completion order.
// Use [OrderedFilterMap] to preserve the input order.
func FilterMap[A, B any](in <-chan Try[A], n int, f func(A) (B, bool, error)) <-chan Try[B] {
validateN(n)
validateNilFunc(f == nil)
return core.FilterMap(in, n, func(a Try[A]) (Try[B], bool) {
if a.Error != nil {
return Try[B]{Error: a.Error}, true
}
b, keep, err := f(a.Value)
if err != nil {
return Try[B]{Error: err}, true
}
return Try[B]{Value: b}, keep
})
}
// OrderedFilterMap is the ordered version of [FilterMap]:
// it writes results and errors in input order rather than completion order.
func OrderedFilterMap[A, B any](in <-chan Try[A], n int, f func(A) (B, bool, error)) <-chan Try[B] {
validateN(n)
validateNilFunc(f == nil)
return core.OrderedFilterMap(in, n, func(a Try[A]) (Try[B], bool) {
if a.Error != nil {
return Try[B]{Error: a.Error}, true
}
b, keep, err := f(a.Value)
if err != nil {
return Try[B]{Error: err}, true
}
return Try[B]{Value: b}, keep
})
}
// FlatMap takes a stream of values of type A and returns a stream of
// values of type B, using f to expand each value into its own sub-stream.
// The sub-streams are flattened into the output: every item is forwarded,
// values and errors alike.
//
// The argument n bounds the number of sub-streams consumed concurrently:
// each worker consumes one sub-stream to the end before starting the next.
// When n > 1, items from different sub-streams can interleave in the
// output. Use [OrderedFlatMap] to concatenate the sub-streams in the
// input order.
func FlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan Try[B] {
validateN(n)
validateNilFunc(f == nil)
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 the ordered version of [FlatMap]: the output consists
// of the sub-streams concatenated in the input order.
//
// The argument n bounds the number of concurrent calls to f. The
// sub-streams are prepared concurrently but - unlike in [FlatMap] -
// are consumed one at a time and in order: nothing reads from a sub-stream
// before its turn. In practice, to keep the stage concurrent, a
// sub-stream must do all or part of its expensive work ahead of its
// turn.
//
// Consider a stream of URLs: each file should be downloaded, and its
// lines should be streamed to the output, all in order. Downloading is the
// expensive work here.
//
// Example 1: f downloads the whole file into memory and then streams the
// lines from there. Up to 5 downloads run concurrently.
//
// rill.OrderedFlatMap(urls, 5, func(u string) <-chan rill.Try[string] {
// lines, err := getFileLines(u)
// return rill.FromSlice(lines, err)
// })
//
// Example 2: as the file is being downloaded, f streams the lines
// through a [Buffer] that lets the sub-stream run ahead of its turn.
// Again, up to 5 downloads run concurrently, but a download that runs
// ahead of its turn pauses after its first 100 lines until the turn comes.
//
// rill.OrderedFlatMap(urls, 5, func(u string) <-chan rill.Try[string] {
// lines := streamFileLines(u)
// return rill.Buffer(lines, 100)
// })
//
// The two examples do the same thing: they buffer the lines, with or
// without a bound. Without any buffering, the downloads would run one
// at a time, and the stage would become sequential.
func OrderedFlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan Try[B] {
validateN(n)
validateNilFunc(f == nil)
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 takes a stream and returns a new stream with the errors
// optionally handled by f. Each error is passed to f, which returns nil
// to drop it from the stream, the same error to keep it, or a different
// one to replace it. Values never reach f and are passed through as-is.
//
// The argument n bounds the number of concurrent calls to f.
// Items are written to the output in completion order.
// Use [OrderedCatch] to preserve the input order.
func Catch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
validateN(n)
validateNilFunc(f == nil)
return core.FilterMap(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 the ordered version of [Catch]:
// it writes items in input order rather than completion order.
func OrderedCatch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
validateN(n)
validateNilFunc(f == nil)
return core.OrderedFilterMap(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)
})
}