Skip to content

Commit 8a72218

Browse files
perf: use while! in groupBy, countBy, partition, except, exceptOfSeq
Replaces the manual go-flag + initial-MoveNext pre-advance pattern with idiomatic while! in five functions: groupBy, countBy, partition, except, and exceptOfSeq. The pattern being replaced (groupBy example): let! step = e.MoveNextAsync() let mutable go = step while go do // body let! step = e.MoveNextAsync() go <- step After: while! e.MoveNextAsync() do // body For except/exceptOfSeq the first-element check is also cleaned up: let! hasFirst = e.MoveNextAsync() if hasFirst then if hashSet.Add e.Current then yield e.Current while! e.MoveNextAsync() do ... All changes are purely internal - no public API or behaviour change. Verified: 5093 tests passed, 0 failures, 0 warnings, formatting OK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f3f20ee commit 8a72218

File tree

2 files changed

+24
-50
lines changed

2 files changed

+24
-50
lines changed

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
1.0.0
5+
- perf: use while! in groupBy, countBy, partition, except, exceptOfSeq to eliminate redundant mutable 'go' variables and initial MoveNextAsync calls
56
- 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
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

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,35 +1462,29 @@ module internal TaskSeqInternal =
14621462

14631463
taskSeq {
14641464
use e = source.GetAsyncEnumerator CancellationToken.None
1465-
let mutable go = true
1466-
let! step = e.MoveNextAsync()
1467-
go <- step
1465+
let! hasFirst = e.MoveNextAsync()
14681466

1469-
if step then
1467+
if hasFirst then
14701468
// only create hashset by the time we actually start iterating;
14711469
// taskSeq enumerates sequentially, so a plain HashSet suffices — no locking needed.
14721470
let hashSet = HashSet<_>(HashIdentity.Structural)
14731471

14741472
use excl = itemsToExclude.GetAsyncEnumerator CancellationToken.None
1475-
let! exclStep = excl.MoveNextAsync()
1476-
let mutable exclGo = exclStep
14771473

1478-
while exclGo do
1474+
while! excl.MoveNextAsync() do
14791475
hashSet.Add excl.Current |> ignore
1480-
let! exclStep = excl.MoveNextAsync()
1481-
exclGo <- exclStep
14821476

1483-
while go do
1477+
// if true, it was added, and therefore unique, so we return it
1478+
// if false, it existed, and therefore a duplicate, and we skip
1479+
if hashSet.Add e.Current then
1480+
yield e.Current
1481+
1482+
while! e.MoveNextAsync() do
14841483
let current = e.Current
14851484

1486-
// if true, it was added, and therefore unique, so we return it
1487-
// if false, it existed, and therefore a duplicate, and we skip
14881485
if hashSet.Add current then
14891486
yield current
14901487

1491-
let! step = e.MoveNextAsync()
1492-
go <- step
1493-
14941488
}
14951489

14961490
let exceptOfSeq itemsToExclude (source: TaskSeq<_>) =
@@ -1499,26 +1493,24 @@ module internal TaskSeqInternal =
14991493

15001494
taskSeq {
15011495
use e = source.GetAsyncEnumerator CancellationToken.None
1502-
let mutable go = true
1503-
let! step = e.MoveNextAsync()
1504-
go <- step
1496+
let! hasFirst = e.MoveNextAsync()
15051497

1506-
if step then
1498+
if hasFirst then
15071499
// only create hashset by the time we actually start iterating;
15081500
// initialize directly from the seq — taskSeq is sequential so no locking needed.
15091501
let hashSet = HashSet<_>(itemsToExclude, HashIdentity.Structural)
15101502

1511-
while go do
1503+
// if true, it was added, and therefore unique, so we return it
1504+
// if false, it existed, and therefore a duplicate, and we skip
1505+
if hashSet.Add e.Current then
1506+
yield e.Current
1507+
1508+
while! e.MoveNextAsync() do
15121509
let current = e.Current
15131510

1514-
// if true, it was added, and therefore unique, so we return it
1515-
// if false, it existed, and therefore a duplicate, and we skip
15161511
if hashSet.Add current then
15171512
yield current
15181513

1519-
let! step = e.MoveNextAsync()
1520-
go <- step
1521-
15221514
}
15231515

15241516
let distinctUntilChanged (source: TaskSeq<_>) =
@@ -1601,12 +1593,10 @@ module internal TaskSeqInternal =
16011593
use e = source.GetAsyncEnumerator CancellationToken.None
16021594
let groups = Dictionary<'Key, ResizeArray<'T>>(HashIdentity.Structural)
16031595
let order = ResizeArray<'Key>()
1604-
let! step = e.MoveNextAsync()
1605-
let mutable go = step
16061596

16071597
match projector with
16081598
| ProjectorAction proj ->
1609-
while go do
1599+
while! e.MoveNextAsync() do
16101600
let key = proj e.Current
16111601
let mutable ra = Unchecked.defaultof<_>
16121602

@@ -1616,11 +1606,9 @@ module internal TaskSeqInternal =
16161606
order.Add key
16171607

16181608
ra.Add e.Current
1619-
let! step = e.MoveNextAsync()
1620-
go <- step
16211609

16221610
| AsyncProjectorAction proj ->
1623-
while go do
1611+
while! e.MoveNextAsync() do
16241612
let! key = proj e.Current
16251613
let mutable ra = Unchecked.defaultof<_>
16261614

@@ -1630,8 +1618,6 @@ module internal TaskSeqInternal =
16301618
order.Add key
16311619

16321620
ra.Add e.Current
1633-
let! step = e.MoveNextAsync()
1634-
go <- step
16351621

16361622
return
16371623
Array.init order.Count (fun i ->
@@ -1646,33 +1632,27 @@ module internal TaskSeqInternal =
16461632
use e = source.GetAsyncEnumerator CancellationToken.None
16471633
let counts = Dictionary<'Key, int>(HashIdentity.Structural)
16481634
let order = ResizeArray<'Key>()
1649-
let! step = e.MoveNextAsync()
1650-
let mutable go = step
16511635

16521636
match projector with
16531637
| ProjectorAction proj ->
1654-
while go do
1638+
while! e.MoveNextAsync() do
16551639
let key = proj e.Current
16561640
let mutable count = 0
16571641

16581642
if not (counts.TryGetValue(key, &count)) then
16591643
order.Add key
16601644

16611645
counts[key] <- count + 1
1662-
let! step = e.MoveNextAsync()
1663-
go <- step
16641646

16651647
| AsyncProjectorAction proj ->
1666-
while go do
1648+
while! e.MoveNextAsync() do
16671649
let! key = proj e.Current
16681650
let mutable count = 0
16691651

16701652
if not (counts.TryGetValue(key, &count)) then
16711653
order.Add key
16721654

16731655
counts[key] <- count + 1
1674-
let! step = e.MoveNextAsync()
1675-
go <- step
16761656

16771657
return Array.init order.Count (fun i -> let k = order[i] in k, counts[k])
16781658
}
@@ -1684,29 +1664,22 @@ module internal TaskSeqInternal =
16841664
use e = source.GetAsyncEnumerator CancellationToken.None
16851665
let trueItems = ResizeArray<'T>()
16861666
let falseItems = ResizeArray<'T>()
1687-
let! step = e.MoveNextAsync()
1688-
let mutable go = step
16891667

16901668
match predicate with
16911669
| Predicate pred ->
1692-
while go do
1670+
while! e.MoveNextAsync() do
16931671
let item = e.Current
16941672

16951673
if pred item then
16961674
trueItems.Add item
16971675
else
16981676
falseItems.Add item
16991677

1700-
let! step = e.MoveNextAsync()
1701-
go <- step
1702-
17031678
| PredicateAsync pred ->
1704-
while go do
1679+
while! e.MoveNextAsync() do
17051680
let item = e.Current
17061681
let! result = pred item
17071682
if result then trueItems.Add item else falseItems.Add item
1708-
let! step = e.MoveNextAsync()
1709-
go <- step
17101683

17111684
return trueItems.ToArray(), falseItems.ToArray()
17121685
}

0 commit comments

Comments
 (0)