@@ -16,28 +16,14 @@ open FSharp.Control
1616
1717[<AutoOpen>]
1818module With =
19- /// The only real difference in semantics between the base and the *Inclusive variant lies in whether the final item is returned.
20- /// NOTE the semantics are very clear on only propagating a single failing item in the inclusive case.
19+ /// The only real difference in semantics between the base and the *Inclusive variant lies in whether the final item is skipped.
2120 let getFunction inclusive isAsync =
2221 match inclusive, isAsync with
2322 | false , false -> TaskSeq.skipWhile
2423 | false , true -> fun pred -> TaskSeq.skipWhileAsync ( pred >> Task.fromResult)
2524 | true , false -> TaskSeq.skipWhileInclusive
2625 | true , true -> fun pred -> TaskSeq.skipWhileInclusiveAsync ( pred >> Task.fromResult)
2726
28- /// This is the base condition as one would expect in actual code
29- let inline cond x = x <> 6
30-
31- /// For each of the tests below, we add a guard that will trigger if the predicate is passed items known to be beyond the
32- /// first failing item in the known sequence (which is 1..10)
33- let inline condWithGuard x =
34- let res = cond x
35-
36- if x > 6 then
37- failwith " Test sequence should not be enumerated beyond the first item failing the predicate"
38-
39- res
40-
4127module EmptySeq =
4228
4329 // TaskSeq-skipWhile+A stands for:
@@ -76,6 +62,12 @@ module Immutable =
7662
7763 [<Theory; ClassData( typeof< TestImmTaskSeq>) >]
7864 let ``TaskSeq - skipWhile + A filters correctly`` variant = task {
65+ // truth table for f(x) = x < 5
66+ // 1 2 3 4 5 6 7 8 9 10
67+ // T T T T F F F F F F (stops at first F)
68+ // x x x x _ _ _ _ _ _ (skips exclusive)
69+ // A B C D E F G H I J
70+
7971 do !
8072 Gen.getSeqImmutable variant
8173 |> TaskSeq.skipWhile ((>) 5 ) // skip while less than 5
@@ -102,15 +94,21 @@ module Immutable =
10294
10395 [<Theory; ClassData( typeof< TestImmTaskSeq>) >]
10496 let ``TaskSeq - skipWhileInclusive + A filters correctly`` variant = task {
97+ // truth table for f(x) = x < 5
98+ // 1 2 3 4 5 6 7 8 9 10
99+ // T T T T F F F F F F (stops at first F)
100+ // x x x x x _ _ _ _ _ (skips inclusively)
101+ // A B C D E F G H I J
102+
105103 do !
106104 Gen.getSeqImmutable variant
107105 |> TaskSeq.skipWhileInclusive ((>) 5 )
108- |> verifyDigitsAsString " GHIJ " // last 4
106+ |> verifyDigitsAsString " FGHIJ " // last 4
109107
110108 do !
111109 Gen.getSeqImmutable variant
112110 |> TaskSeq.skipWhileInclusiveAsync ( fun x -> task { return x < 5 })
113- |> verifyDigitsAsString " GHIJ "
111+ |> verifyDigitsAsString " FGHIJ "
114112 }
115113
116114
@@ -142,16 +140,42 @@ module Immutable =
142140
143141module SideEffects =
144142 [<Theory; ClassData( typeof< TestSideEffectTaskSeq>) >]
145- let ``TaskSeq - skipWhile filters correctly`` variant =
146- Gen.getSeqWithSideEffect variant
147- |> TaskSeq.skipWhile condWithGuard
148- |> verifyDigitsAsString " ABCDE"
143+ let ``TaskSeq - skipWhile + A filters correctly`` variant = task {
144+ // truth table for f(x) = x < 6
145+ // 1 2 3 4 5 6 7 8 9 10
146+ // T T T T T F F F F F (stops at first F)
147+ // x x x x x _ _ _ _ _ (skips exclusively)
148+ // A B C D E F G H I J
149+
150+ do !
151+ Gen.getSeqWithSideEffect variant
152+ |> TaskSeq.skipWhile ((>) 6 )
153+ |> verifyDigitsAsString " FGHIJ"
154+
155+ do !
156+ Gen.getSeqWithSideEffect variant
157+ |> TaskSeq.skipWhileAsync ( fun x -> task { return x < 6 })
158+ |> verifyDigitsAsString " FGHIJ"
159+ }
149160
150161 [<Theory; ClassData( typeof< TestSideEffectTaskSeq>) >]
151- let ``TaskSeq - skipWhileAsync filters correctly`` variant =
152- Gen.getSeqWithSideEffect variant
153- |> TaskSeq.skipWhileAsync ( fun x -> task { return condWithGuard x })
154- |> verifyDigitsAsString " ABCDE"
162+ let ``TaskSeq - skipWhileInclusive + A filters correctly`` variant = task {
163+ // truth table for f(x) = x < 6
164+ // 1 2 3 4 5 6 7 8 9 10
165+ // T T T T T F F F F F (stops at first F)
166+ // x x x x x x _ _ _ _ (skips inclusively)
167+ // A B C D E F G H I J
168+
169+ do !
170+ Gen.getSeqWithSideEffect variant
171+ |> TaskSeq.skipWhileInclusive ((>) 6 )
172+ |> verifyDigitsAsString " GHIJ"
173+
174+ do !
175+ Gen.getSeqWithSideEffect variant
176+ |> TaskSeq.skipWhileInclusiveAsync ( fun x -> task { return x < 6 })
177+ |> verifyDigitsAsString " GHIJ"
178+ }
155179
156180 [<Theory>]
157181 [<InlineData( false , false ) >]
@@ -163,19 +187,23 @@ module SideEffects =
163187 let functionToTest = getFunction inclusive isAsync ((=) 42 )
164188
165189 let items = taskSeq {
166- yield x // Always passes the test; always returned
167- yield x * 2 // the failing item (which will also be yielded in the result when using *Inclusive)
190+ yield x // Always passes the test; always skipped
191+ yield x * 2 // Fails the test, skipped depending on "inclusive"
168192 x <- x + 1 // we are proving we never get here
169193 }
170194
171- let expected = if inclusive then [| 42 ; 84 |] else [| 42 |]
195+ // we skip one more if "inclusive"
196+ let expected = if inclusive then [||] else [| 84 |]
172197
198+ x |> should equal 42
173199 let! first = items |> functionToTest |> TaskSeq.toArrayAsync
200+ x |> should equal 42
174201 let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync
202+ x |> should equal 42
175203
176204 first |> should equal expected
177205 repeat |> should equal expected
178- x |> should equal 42
206+ x |> should equal 42 // if the var changed, we got too far
179207 }
180208
181209 [<Theory>]
@@ -195,9 +223,10 @@ module SideEffects =
195223 x <- x + 200 // as previously proven, we should not trigger this
196224 }
197225
198- let expectedFirst = if inclusive then [| 42 ; 44 * 2 |] else [| 42 |]
199- let expectedRepeat = if inclusive then [| 45 ; 47 * 2 |] else [| 45 |]
226+ let expectedFirst = if inclusive then [||] else [| 44 * 2 |]
227+ let expectedRepeat = if inclusive then [||] else [| 47 * 2 |]
200228
229+ x |> should equal 41
201230 let! first = items |> functionToTest |> TaskSeq.toArrayAsync
202231 x |> should equal 44
203232 let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync
@@ -215,10 +244,11 @@ module SideEffects =
215244 TaskSeq.skipWhile ( fun x -> x < 5 ) ts
216245 |> TaskSeq.toArrayAsync
217246
218- let expected = [| 1 .. 4 |]
247+ let expected = [| 5 .. 10 |]
219248 first |> should equal expected
220249
221250 // side effect, reiterating causes it to resume from where we left it (minus the failing item)
251+ // which means the original sequence has now changed due to the side effect
222252 let! repeat =
223253 TaskSeq.skipWhile ( fun x -> x < 5 ) ts
224254 |> TaskSeq.toArrayAsync
@@ -234,10 +264,11 @@ module SideEffects =
234264 TaskSeq.skipWhileInclusiveAsync ( fun x -> task { return x < 5 }) ts
235265 |> TaskSeq.toArrayAsync
236266
237- let expected = [| 1 .. 5 |]
267+ let expected = [| 6 .. 10 |]
238268 first |> should equal expected
239269
240270 // side effect, reiterating causes it to resume from where we left it (minus the failing item)
271+ // which means the original sequence has now changed due to the side effect
241272 let! repeat =
242273 TaskSeq.skipWhileInclusiveAsync ( fun x -> task { return x < 5 }) ts
243274 |> TaskSeq.toArrayAsync
@@ -251,26 +282,71 @@ module Other =
251282 [<InlineData( false , true ) >]
252283 [<InlineData( true , false ) >]
253284 [<InlineData( true , true ) >]
254- let ``TaskSeq - skipWhileXXX exclude all items after predicate fails`` ( inclusive , isAsync ) =
255- let functionToTest = With.getFunction inclusive isAsync
285+ let ``TaskSeq - skipWhileXXX should include all items after predicate fails`` ( inclusive , isAsync ) = task {
286+ do !
287+ [ 1 ; 2 ; 2 ; 3 ; 3 ; 2 ; 1 ]
288+ |> TaskSeq.ofSeq
289+ |> TaskSeq.skipWhile ( fun x -> x <= 2 )
290+ |> verifyDigitsAsString " CCBA"
291+
292+ do !
293+ [ 1 ; 2 ; 2 ; 3 ; 3 ; 2 ; 1 ]
294+ |> TaskSeq.ofSeq
295+ |> TaskSeq.skipWhileInclusive ( fun x -> x <= 2 )
296+ |> verifyDigitsAsString " CBA"
297+
298+ do !
299+ [ 1 ; 2 ; 2 ; 3 ; 3 ; 2 ; 1 ]
300+ |> TaskSeq.ofSeq
301+ |> TaskSeq.skipWhileAsync ( fun x -> Task.fromResult ( x <= 2 ))
302+ |> verifyDigitsAsString " CCBA"
256303
257- [ 1 ; 2 ; 2 ; 3 ; 3 ; 2 ; 1 ]
258- |> TaskSeq.ofSeq
259- |> functionToTest ( fun x -> x <= 2 )
260- |> verifyDigitsAsString ( if inclusive then " ABBC" else " ABB" )
304+ do !
305+ [ 1 ; 2 ; 2 ; 3 ; 3 ; 2 ; 1 ]
306+ |> TaskSeq.ofSeq
307+ |> TaskSeq.skipWhileInclusiveAsync ( fun x -> Task.fromResult ( x <= 2 ))
308+ |> verifyDigitsAsString " CBA"
309+ }
261310
262311 [<Theory>]
263312 [<InlineData( false , false ) >]
264313 [<InlineData( false , true ) >]
265314 [<InlineData( true , false ) >]
266315 [<InlineData( true , true ) >]
267- let ``TaskSeq - skipWhileXXX stops consuming after predicate fails`` ( inclusive , isAsync ) =
268- let functionToTest = With.getFunction inclusive isAsync
316+ let ``TaskSeq - skipWhileXXX stops consuming after predicate fails`` ( inclusive , isAsync ) = task {
317+ do !
318+ seq {
319+ yield ! [ 1 ; 2 ; 2 ; 3 ; 3 ]
320+ yield failwith " Too far"
321+ }
322+ |> TaskSeq.ofSeq
323+ |> TaskSeq.skipWhile ( fun x -> x <= 2 )
324+ |> verifyDigitsAsString " CC"
269325
270- seq {
271- yield ! [ 1 ; 2 ; 2 ; 3 ; 3 ]
272- yield failwith " Too far"
273- }
274- |> TaskSeq.ofSeq
275- |> functionToTest ( fun x -> x <= 2 )
276- |> verifyDigitsAsString ( if inclusive then " ABBC" else " ABB" )
326+ do !
327+ seq {
328+ yield ! [ 1 ; 2 ; 2 ; 3 ; 3 ]
329+ yield failwith " Too far"
330+ }
331+ |> TaskSeq.ofSeq
332+ |> TaskSeq.skipWhileInclusive ( fun x -> x <= 2 )
333+ |> verifyDigitsAsString " C"
334+
335+ do !
336+ seq {
337+ yield ! [ 1 ; 2 ; 2 ; 3 ; 3 ]
338+ yield failwith " Too far"
339+ }
340+ |> TaskSeq.ofSeq
341+ |> TaskSeq.skipWhileAsync ( fun x -> Task.fromResult ( x <= 2 ))
342+ |> verifyDigitsAsString " CC"
343+
344+ do !
345+ seq {
346+ yield ! [ 1 ; 2 ; 2 ; 3 ; 3 ]
347+ yield failwith " Too far"
348+ }
349+ |> TaskSeq.ofSeq
350+ |> TaskSeq.skipWhileInclusiveAsync ( fun x -> Task.fromResult ( x <= 2 ))
351+ |> verifyDigitsAsString " C"
352+ }
0 commit comments