Skip to content

Commit b4f9aaf

Browse files
perf: replace ValueOption tracking with while! in pairwise and distinctUntilChanged family
pairwise, distinctUntilChanged, distinctUntilChangedWith, and distinctUntilChangedWithAsync previously used a ValueOption mutable and a for-in loop over the source to track the 'previous' element. used in other optimized functions (except, exceptOfSeq, skipWhile, etc.). This avoids the per-element struct match (ValueNone vs ValueSome branch) and is structurally consistent with the rest of the library. All 5251 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fc687a3 commit b4f9aaf

2 files changed

Lines changed: 41 additions & 39 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+
- perf: pairwise, distinctUntilChanged, distinctUntilChangedWith, distinctUntilChangedWithAsync now use explicit enumerator + while! instead of ValueOption tracking + for-in loop, eliminating per-element struct match overhead
56
- test: add SideEffects module and ImmTaskSeq variant tests to TaskSeq.ChunkBy.Tests.fs, improving coverage for chunkBy and chunkByAsync
67
- fixes: `Async.bind` signature corrected from `(Async<'T> -> Async<'U>)` to `('T -> Async<'U>)` to match standard monadic bind semantics (same as `Task.bind`); the previous signature made the function effectively equivalent to direct application
78
- refactor: simplify splitAt 'rest' taskSeq to use while!, removing redundant go2 mutable and manual MoveNextAsync pre-advance

src/FSharp.Control.TaskSeq/TaskSeqInternal.fs

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,73 +1488,74 @@ module internal TaskSeqInternal =
14881488
checkNonNull (nameof source) source
14891489

14901490
taskSeq {
1491-
let mutable maybePrevious = ValueNone
1491+
use e = source.GetAsyncEnumerator CancellationToken.None
1492+
let! hasFirst = e.MoveNextAsync()
14921493

1493-
for current in source do
1494-
match maybePrevious with
1495-
| ValueNone ->
1496-
yield current
1497-
maybePrevious <- ValueSome current
1498-
| ValueSome previous ->
1499-
if previous = current then
1500-
() // skip
1501-
else
1494+
if hasFirst then
1495+
let mutable previous = e.Current
1496+
yield previous
1497+
1498+
while! e.MoveNextAsync() do
1499+
let current = e.Current
1500+
1501+
if current <> previous then
15021502
yield current
1503-
maybePrevious <- ValueSome current
1503+
previous <- current
15041504
}
15051505

15061506
let distinctUntilChangedWith (comparer: 'T -> 'T -> bool) (source: TaskSeq<_>) =
15071507
checkNonNull (nameof source) source
15081508

15091509
taskSeq {
1510-
let mutable maybePrevious = ValueNone
1510+
use e = source.GetAsyncEnumerator CancellationToken.None
1511+
let! hasFirst = e.MoveNextAsync()
15111512

1512-
for current in source do
1513-
match maybePrevious with
1514-
| ValueNone ->
1515-
yield current
1516-
maybePrevious <- ValueSome current
1517-
| ValueSome previous ->
1518-
if comparer previous current then
1519-
() // skip
1520-
else
1513+
if hasFirst then
1514+
let mutable previous = e.Current
1515+
yield previous
1516+
1517+
while! e.MoveNextAsync() do
1518+
let current = e.Current
1519+
1520+
if not (comparer previous current) then
15211521
yield current
1522-
maybePrevious <- ValueSome current
1522+
previous <- current
15231523
}
15241524

15251525
let distinctUntilChangedWithAsync (comparer: 'T -> 'T -> #Task<bool>) (source: TaskSeq<_>) =
15261526
checkNonNull (nameof source) source
15271527

15281528
taskSeq {
1529-
let mutable maybePrevious = ValueNone
1529+
use e = source.GetAsyncEnumerator CancellationToken.None
1530+
let! hasFirst = e.MoveNextAsync()
15301531

1531-
for current in source do
1532-
match maybePrevious with
1533-
| ValueNone ->
1534-
yield current
1535-
maybePrevious <- ValueSome current
1536-
| ValueSome previous ->
1532+
if hasFirst then
1533+
let mutable previous = e.Current
1534+
yield previous
1535+
1536+
while! e.MoveNextAsync() do
1537+
let current = e.Current
15371538
let! areEqual = comparer previous current
15381539

1539-
if areEqual then
1540-
() // skip
1541-
else
1540+
if not areEqual then
15421541
yield current
1543-
maybePrevious <- ValueSome current
1542+
previous <- current
15441543
}
15451544

15461545
let pairwise (source: TaskSeq<_>) =
15471546
checkNonNull (nameof source) source
15481547

15491548
taskSeq {
1550-
let mutable maybePrevious = ValueNone
1549+
use e = source.GetAsyncEnumerator CancellationToken.None
1550+
let! hasFirst = e.MoveNextAsync()
15511551

1552-
for current in source do
1553-
match maybePrevious with
1554-
| ValueNone -> maybePrevious <- ValueSome current
1555-
| ValueSome previous ->
1552+
if hasFirst then
1553+
let mutable previous = e.Current
1554+
1555+
while! e.MoveNextAsync() do
1556+
let current = e.Current
15561557
yield previous, current
1557-
maybePrevious <- ValueSome current
1558+
previous <- current
15581559
}
15591560

15601561
let groupBy (projector: ProjectorAction<'T, 'Key, _>) (source: TaskSeq<_>) =

0 commit comments

Comments
 (0)