Skip to content

Commit f787ab3

Browse files
perf: use while! in iter/fold/reduce/mapFold/tryLast/Drop/Truncate
Replace the manual 'go' flag + initial MoveNextAsync pre-advance pattern with while! in iter, fold, reduce, mapFold, tryLast, and the Drop/Truncate cases in skipOrTake. The old pattern: let! step = e.MoveNextAsync() go <- step while go do ... let! step = e.MoveNextAsync() go <- step becomes: while! e.MoveNextAsync() do ... This matches the pattern already established by sum, sumBy, average, averageBy, lengthBy, etc. Benefits: - Removes one mutable bool (go) per function - Removes the initial redundant pre-advance call before the loop - Unifies style across the module Also simplifies the Drop and Truncate loop bodies to use explicit i/yielded counters instead of a pos variable with an inline if/else. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4873e88 commit f787ab3

File tree

2 files changed

+35
-79
lines changed

2 files changed

+35
-79
lines changed

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
1.0.0
55
- adds taskSeqDynamic computation expression and TaskSeqDynamic/TaskSeqDynamicInfo types for dynamic (FSI-compatible) resumable code, fixing issue where taskSeq would raise NotImplementedException in F# Interactive, #246
6+
- perf: simplify iter, fold, reduce, mapFold, tryLast, skipOrTake (Drop/Truncate) to use while! and remove manual go-flag and initial MoveNextAsync pre-advance, matching the pattern already used by sum/sumBy/average
67
- perf: TaskSeq.chunkBy and chunkByAsync reuse the ResizeArray buffer between chunks, reducing allocations on sequences with many chunk boundaries
78
- fixes: TaskSeq.insertAt, insertManyAt, removeAt, removeManyAt, updateAt now raise ArgumentNullException (not NullReferenceException) when given a null source; insertManyAt also validates the values argument
89
- refactor: simplify lengthBy and lengthBeforeMax to use while! and remove the redundant mutable 'go' and initial MoveNextAsync

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 34 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -357,68 +357,49 @@ module internal TaskSeqInternal =
357357

358358
task {
359359
use e = source.GetAsyncEnumerator CancellationToken.None
360-
let mutable go = true
361-
let! step = e.MoveNextAsync()
362-
go <- step
363360

364-
// this ensures that the inner loop is optimized for the closure
365-
// though perhaps we need to split into individual functions after all to use
366-
// InlineIfLambda?
361+
// Each branch keeps its own while! loop so the match dispatch is hoisted out and
362+
// the JIT sees a tight, single-case loop (same pattern as sum/sumBy etc.).
367363
match action with
368364
| CountableAction action ->
369365
let mutable i = 0
370366

371-
while go do
372-
do action i e.Current
373-
let! step = e.MoveNextAsync()
367+
while! e.MoveNextAsync() do
368+
action i e.Current
374369
i <- i + 1
375-
go <- step
376370

377371
| SimpleAction action ->
378-
while go do
379-
do action e.Current
380-
let! step = e.MoveNextAsync()
381-
go <- step
372+
while! e.MoveNextAsync() do
373+
action e.Current
382374

383375
| AsyncCountableAction action ->
384376
let mutable i = 0
385377

386-
while go do
378+
while! e.MoveNextAsync() do
387379
do! action i e.Current
388-
let! step = e.MoveNextAsync()
389380
i <- i + 1
390-
go <- step
391381

392382
| AsyncSimpleAction action ->
393-
while go do
383+
while! e.MoveNextAsync() do
394384
do! action e.Current
395-
let! step = e.MoveNextAsync()
396-
go <- step
397385
}
398386

399387
let fold folder initial (source: TaskSeq<_>) =
400388
checkNonNull (nameof source) source
401389

402390
task {
403391
use e = source.GetAsyncEnumerator CancellationToken.None
404-
let mutable go = true
405392
let mutable result = initial
406-
let! step = e.MoveNextAsync()
407-
go <- step
408393

409394
match folder with
410395
| FolderAction folder ->
411-
while go do
396+
while! e.MoveNextAsync() do
412397
result <- folder result e.Current
413-
let! step = e.MoveNextAsync()
414-
go <- step
415398

416399
| AsyncFolderAction folder ->
417-
while go do
400+
while! e.MoveNextAsync() do
418401
let! tempResult = folder result e.Current
419402
result <- tempResult
420-
let! step = e.MoveNextAsync()
421-
go <- step
422403

423404
return result
424405
}
@@ -457,22 +438,16 @@ module internal TaskSeqInternal =
457438
raiseEmptySeq ()
458439

459440
let mutable result = e.Current
460-
let! step = e.MoveNextAsync()
461-
let mutable go = step
462441

463442
match folder with
464443
| FolderAction folder ->
465-
while go do
444+
while! e.MoveNextAsync() do
466445
result <- folder result e.Current
467-
let! step = e.MoveNextAsync()
468-
go <- step
469446

470447
| AsyncFolderAction folder ->
471-
while go do
448+
while! e.MoveNextAsync() do
472449
let! tempResult = folder result e.Current
473450
result <- tempResult
474-
let! step = e.MoveNextAsync()
475-
go <- step
476451

477452
return result
478453
}
@@ -482,28 +457,21 @@ module internal TaskSeqInternal =
482457

483458
task {
484459
use e = source.GetAsyncEnumerator CancellationToken.None
485-
let mutable go = true
486460
let mutable state = initial
487461
let results = ResizeArray()
488-
let! step = e.MoveNextAsync()
489-
go <- step
490462

491463
match folder with
492464
| MapFolderAction folder ->
493-
while go do
465+
while! e.MoveNextAsync() do
494466
let result, newState = folder state e.Current
495467
results.Add result
496468
state <- newState
497-
let! step = e.MoveNextAsync()
498-
go <- step
499469

500470
| AsyncMapFolderAction folder ->
501-
while go do
471+
while! e.MoveNextAsync() do
502472
let! (result, newState) = folder state e.Current
503473
results.Add result
504474
state <- newState
505-
let! step = e.MoveNextAsync()
506-
go <- step
507475

508476
return results.ToArray(), state
509477
}
@@ -804,15 +772,10 @@ module internal TaskSeqInternal =
804772

805773
task {
806774
use e = source.GetAsyncEnumerator CancellationToken.None
807-
let mutable go = true
808775
let mutable last = ValueNone
809-
let! step = e.MoveNextAsync()
810-
go <- step
811776

812-
while go do
777+
while! e.MoveNextAsync() do
813778
last <- ValueSome e.Current
814-
let! step = e.MoveNextAsync()
815-
go <- step
816779

817780
match last with
818781
| ValueSome value -> return Some value
@@ -1233,24 +1196,19 @@ module internal TaskSeqInternal =
12331196
else
12341197
taskSeq {
12351198
use e = source.GetAsyncEnumerator CancellationToken.None
1199+
let mutable i = 0
1200+
let mutable cont = true
12361201

1237-
let! step = e.MoveNextAsync()
1238-
let mutable cont = step
1239-
let mutable pos = 0
1240-
1241-
// skip, or stop looping if we reached the end
1242-
while cont do
1243-
pos <- pos + 1
1244-
1245-
if pos < count then
1246-
let! moveNext = e.MoveNextAsync()
1247-
cont <- moveNext
1248-
else
1249-
cont <- false
1202+
// advance past 'count' elements; stop early if the source is shorter
1203+
while cont && i < count do
1204+
let! hasMore = e.MoveNextAsync()
1205+
if hasMore then i <- i + 1 else cont <- false
12501206

1251-
// return the rest
1252-
while! e.MoveNextAsync() do
1253-
yield e.Current
1207+
// return remaining elements; enumerator is at element (count-1) so one
1208+
// more MoveNext is needed to reach element (count)
1209+
if cont then
1210+
while! e.MoveNextAsync() do
1211+
yield e.Current
12541212

12551213
}
12561214
| Take ->
@@ -1277,19 +1235,16 @@ module internal TaskSeqInternal =
12771235
else
12781236
taskSeq {
12791237
use e = source.GetAsyncEnumerator CancellationToken.None
1238+
let mutable yielded = 0
1239+
let mutable cont = true
12801240

1281-
let! step = e.MoveNextAsync()
1282-
let mutable cont = step
1283-
let mutable pos = 0
1284-
1285-
// return items until we've exhausted the seq
1286-
while cont do
1287-
yield e.Current
1288-
pos <- pos + 1
1241+
// yield up to 'count' elements; stop when exhausted or limit reached
1242+
while cont && yielded < count do
1243+
let! hasMore = e.MoveNextAsync()
12891244

1290-
if pos < count then
1291-
let! moveNext = e.MoveNextAsync()
1292-
cont <- moveNext
1245+
if hasMore then
1246+
yield e.Current
1247+
yielded <- yielded + 1
12931248
else
12941249
cont <- false
12951250

0 commit comments

Comments
 (0)