Skip to content

Commit 03ffc54

Browse files
authored
Merge pull request #407 from fsprojects/repo-assist/test-ofxxx-sideeffects-20260424-c044cc685e66a474
[Repo Assist] test: add SideEffects module to TaskSeq.OfXXX.Tests.fs
2 parents ee8e8c5 + 7cb072a commit 03ffc54

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

release-notes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Release notes:
33

44
Unreleased
5+
- test: add SideEffects module to TaskSeq.OfXXX.Tests.fs documenting re-iteration semantics (ofSeq re-evaluates source, ofTaskArray re-awaits cached tasks)
56
- adds TaskSeq.foldWhile and TaskSeq.foldWhileAsync: fold with early termination via a (state, element) -> bool predicate (takeWhile-style, exclusive). When the predicate returns false, iteration halts without folding that element; no further elements are enumerated.
67
- feat: add `TaskSeq.toChannelAsync` and `TaskSeq.ofChannel` for bidirectional `System.Threading.Channels` integration, closing #415
78
- eng: update PackageValidationBaselineVersion from 0.4.0 to 1.1.1 to enforce binary compatibility checks against the current stable release

src/FSharp.Control.TaskSeq.Test/TaskSeq.OfXXX.Tests.fs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,76 @@ module Immutable =
121121

122122
[<Fact>]
123123
let ``TaskSeq-ofSeq should succeed`` () = Seq.init 10 id |> TaskSeq.ofSeq |> validateSequence
124+
125+
module SideEffects =
126+
[<Fact>]
127+
let ``ofSeq re-evaluates the underlying source seq on each re-enumeration`` () = task {
128+
let mutable count = 0
129+
130+
// a lazy IEnumerable — each GetEnumerator() call re-executes the body
131+
let lazySeq = seq {
132+
for i in 1..3 do
133+
count <- count + 1
134+
yield i
135+
}
136+
137+
let ts = TaskSeq.ofSeq lazySeq
138+
let! arr1 = ts |> TaskSeq.toArrayAsync
139+
// each item triggered the side effect once
140+
count |> should equal 3
141+
142+
let! arr2 = ts |> TaskSeq.toArrayAsync
143+
// the underlying seq is re-traversed on the second GetAsyncEnumerator call
144+
count |> should equal 6
145+
arr1 |> should equal arr2
146+
}
147+
148+
[<Fact>]
149+
let ``ofTaskSeq with lazy seq of tasks re-creates tasks on each re-enumeration`` () = task {
150+
let mutable count = 0
151+
152+
// a lazy IEnumerable of Task objects — each seq iteration creates fresh Task objects
153+
let lazyTaskSeq = seq {
154+
for i in 1..3 do
155+
yield task {
156+
count <- count + 1
157+
return i
158+
}
159+
}
160+
161+
let ts = TaskSeq.ofTaskSeq lazyTaskSeq
162+
let! arr1 = ts |> TaskSeq.toArrayAsync
163+
count |> should equal 3
164+
165+
let! arr2 = ts |> TaskSeq.toArrayAsync
166+
// the underlying seq is re-iterated; new Task objects are created and run
167+
count |> should equal 6
168+
arr1 |> should equal arr2
169+
}
170+
171+
[<Fact>]
172+
let ``ofTaskArray does not re-run tasks on re-enumeration; task results are cached`` () = task {
173+
let mutable count = 0
174+
175+
// tasks are created upfront; they run synchronously to completion when constructed
176+
let tasks =
177+
Array.init 3 (fun i -> task {
178+
count <- count + 1
179+
return i + 1
180+
})
181+
182+
// all three tasks have already completed synchronously
183+
count |> should equal 3
184+
185+
let ts = TaskSeq.ofTaskArray tasks
186+
let! arr1 = ts |> TaskSeq.toArrayAsync
187+
188+
// awaiting already-completed tasks does not re-run them
189+
count |> should equal 3
190+
arr1 |> should equal [| 1; 2; 3 |]
191+
192+
let! arr2 = ts |> TaskSeq.toArrayAsync
193+
// the second enumeration re-awaits the same cached task results
194+
count |> should equal 3
195+
arr2 |> should equal arr1
196+
}

0 commit comments

Comments
 (0)