Skip to content

Commit f492d35

Browse files
authored
Merge branch 'main' into repo-assist/test-unfold-sideeffects-20260423-240f686bd62cbcbc
2 parents 0a2a9b8 + 03ffc54 commit f492d35

3 files changed

Lines changed: 75 additions & 1 deletion

File tree

.github/workflows/publish.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
run: ./build.cmd
3838
- name: Obtain NuGet key
3939
# this hash is v1.1.0
40-
uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544
40+
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841
4141
id: login
4242
with:
4343
user: dsyme

release-notes.txt

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

44
Unreleased
55
- test: add SideEffects module to TaskSeq.Unfold.Tests.fs, verifying generator call counts, re-iteration behaviour, early-termination via take, and exception propagation
6+
- test: add SideEffects module to TaskSeq.OfXXX.Tests.fs documenting re-iteration semantics (ofSeq re-evaluates source, ofTaskArray re-awaits cached tasks)
67
- 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.
78
- feat: add `TaskSeq.toChannelAsync` and `TaskSeq.ofChannel` for bidirectional `System.Threading.Channels` integration, closing #415
89
- 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)