Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Release notes:

Unreleased
- perf: pairwise, distinctUntilChanged, distinctUntilChangedWith, distinctUntilChangedWithAsync now use explicit enumerator + while! instead of ValueOption tracking + for-in loop, eliminating per-element struct match overhead
- test: add SideEffects module to TaskSeq.Unfold.Tests.fs, verifying generator call counts, re-iteration behaviour, early-termination via take, and exception propagation
- test: add SideEffects module to TaskSeq.OfXXX.Tests.fs documenting re-iteration semantics (ofSeq re-evaluates source, ofTaskArray re-awaits cached tasks)
- 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.
Expand Down
79 changes: 40 additions & 39 deletions src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1535,73 +1535,74 @@ module internal TaskSeqInternal =
checkNonNull (nameof source) source

taskSeq {
let mutable maybePrevious = ValueNone
use e = source.GetAsyncEnumerator CancellationToken.None
let! hasFirst = e.MoveNextAsync()

for current in source do
match maybePrevious with
| ValueNone ->
yield current
maybePrevious <- ValueSome current
| ValueSome previous ->
if previous = current then
() // skip
else
if hasFirst then
let mutable previous = e.Current
yield previous

while! e.MoveNextAsync() do
let current = e.Current

if current <> previous then
yield current
maybePrevious <- ValueSome current
previous <- current
}

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

taskSeq {
let mutable maybePrevious = ValueNone
use e = source.GetAsyncEnumerator CancellationToken.None
let! hasFirst = e.MoveNextAsync()

for current in source do
match maybePrevious with
| ValueNone ->
yield current
maybePrevious <- ValueSome current
| ValueSome previous ->
if comparer previous current then
() // skip
else
if hasFirst then
let mutable previous = e.Current
yield previous

while! e.MoveNextAsync() do
let current = e.Current

if not (comparer previous current) then
yield current
maybePrevious <- ValueSome current
previous <- current
}

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

taskSeq {
let mutable maybePrevious = ValueNone
use e = source.GetAsyncEnumerator CancellationToken.None
let! hasFirst = e.MoveNextAsync()

for current in source do
match maybePrevious with
| ValueNone ->
yield current
maybePrevious <- ValueSome current
| ValueSome previous ->
if hasFirst then
let mutable previous = e.Current
yield previous

while! e.MoveNextAsync() do
let current = e.Current
let! areEqual = comparer previous current

if areEqual then
() // skip
else
if not areEqual then
yield current
maybePrevious <- ValueSome current
previous <- current
}

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

taskSeq {
let mutable maybePrevious = ValueNone
use e = source.GetAsyncEnumerator CancellationToken.None
let! hasFirst = e.MoveNextAsync()

for current in source do
match maybePrevious with
| ValueNone -> maybePrevious <- ValueSome current
| ValueSome previous ->
if hasFirst then
let mutable previous = e.Current

while! e.MoveNextAsync() do
let current = e.Current
yield previous, current
maybePrevious <- ValueSome current
previous <- current
}

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