@@ -158,3 +158,114 @@ module Functionality =
158158 first |> should equal second
159159 first |> should equal [| 0 .. 4 |]
160160 }
161+
162+ module SideEffects =
163+ [<Fact>]
164+ let ``TaskSeq - unfold generator side - effects accumulate across re - iterations`` () = task {
165+ // The generator closes over mutable external state. Each re-iteration starts fresh from
166+ // the initial seed (0), but the external counter keeps climbing — demonstrating that
167+ // the IAsyncEnumerable itself is stateless but the captured state is shared.
168+ let mutable totalCalls = 0
169+
170+ let ts =
171+ TaskSeq.unfold
172+ ( fun n ->
173+ totalCalls <- totalCalls + 1
174+ if n < 3 then Some( n, n + 1 ) else None)
175+ 0
176+
177+ let! first = ts |> TaskSeq.toArrayAsync
178+ first |> should equal [| 0 ; 1 ; 2 |]
179+ totalCalls |> should equal 4 // 3 Some + 1 None
180+
181+ let! second = ts |> TaskSeq.toArrayAsync
182+ second |> should equal [| 0 ; 1 ; 2 |]
183+ totalCalls |> should equal 8 // called 4 more times for the second iteration
184+ }
185+
186+ [<Fact>]
187+ let ``TaskSeq - unfoldAsync generator side - effects accumulate across re - iterations`` () = task {
188+ let mutable totalCalls = 0
189+
190+ let ts =
191+ TaskSeq.unfoldAsync
192+ ( fun n -> task {
193+ totalCalls <- totalCalls + 1
194+ return if n < 3 then Some( n, n + 1 ) else None
195+ })
196+ 0
197+
198+ let! first = ts |> TaskSeq.toArrayAsync
199+ first |> should equal [| 0 ; 1 ; 2 |]
200+ totalCalls |> should equal 4
201+
202+ let! second = ts |> TaskSeq.toArrayAsync
203+ second |> should equal [| 0 ; 1 ; 2 |]
204+ totalCalls |> should equal 8
205+ }
206+
207+ [<Fact>]
208+ let ``TaskSeq - unfold with take stops generator calls at the limit`` () = task {
209+ let mutable callCount = 0
210+
211+ // Infinite generator: always returns Some
212+ let ts =
213+ TaskSeq.unfold
214+ ( fun n ->
215+ callCount <- callCount + 1
216+ Some( n, n + 1 ))
217+ 0
218+
219+ let! result = ts |> TaskSeq.take 5 |> TaskSeq.toArrayAsync
220+ result |> should equal [| 0 ; 1 ; 2 ; 3 ; 4 |]
221+
222+ // take 5 pulls exactly 5 elements; with an always-Some generator no
223+ // extra sentinel call is needed, so callCount should be exactly 5.
224+ callCount |> should equal 5
225+ }
226+
227+ [<Fact>]
228+ let ``TaskSeq - unfoldAsync with take stops generator calls at the limit`` () = task {
229+ let mutable callCount = 0
230+
231+ let ts =
232+ TaskSeq.unfoldAsync
233+ ( fun n -> task {
234+ callCount <- callCount + 1
235+ return Some( n, n + 1 )
236+ })
237+ 0
238+
239+ let! result = ts |> TaskSeq.take 5 |> TaskSeq.toArrayAsync
240+ result |> should equal [| 0 ; 1 ; 2 ; 3 ; 4 |]
241+ callCount |> should equal 5
242+ }
243+
244+ [<Fact>]
245+ let ``TaskSeq - unfold propagates exception thrown inside the generator`` () =
246+ let ts =
247+ TaskSeq.unfold
248+ ( fun n ->
249+ if n = 3 then
250+ failwith " generator-boom"
251+
252+ Some( n, n + 1 ))
253+ 0
254+
255+ fun () -> ts |> consumeTaskSeq
256+ |> should throwAsyncExact typeof< System.Exception>
257+
258+ [<Fact>]
259+ let ``TaskSeq - unfoldAsync propagates exception thrown inside the async generator`` () =
260+ let ts =
261+ TaskSeq.unfoldAsync
262+ ( fun n -> task {
263+ if n = 3 then
264+ failwith " async-generator-boom"
265+
266+ return Some( n, n + 1 )
267+ })
268+ 0
269+
270+ fun () -> ts |> consumeTaskSeq
271+ |> should throwAsyncExact typeof< System.Exception>
0 commit comments