Skip to content

[Repo Assist] perf: replace ValueOption tracking with while! in pairwise and distinctUntilChanged family#411

Draft
github-actions[bot] wants to merge 2 commits intomainfrom
repo-assist/perf-pairwise-distinct-until-changed-20260427-ca209e6aaefc5cbe
Draft

[Repo Assist] perf: replace ValueOption tracking with while! in pairwise and distinctUntilChanged family#411
github-actions[bot] wants to merge 2 commits intomainfrom
repo-assist/perf-pairwise-distinct-until-changed-20260427-ca209e6aaefc5cbe

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This is an automated pull request from Repo Assist, an AI assistant.

Summary

pairwise, distinctUntilChanged, distinctUntilChangedWith, and distinctUntilChangedWithAsync previously used a ValueOption mutable and a for current in source do loop to track the "previous" element:

// before
let mutable maybePrevious = ValueNone

for current in source do
    match maybePrevious with
    | ValueNone -> maybePrevious <- ValueSome current
    | ValueSome previous ->
        yield previous, current
        maybePrevious <- ValueSome current

This PR replaces each with an explicit enumerator + while!, consistent with the pattern already used in except, exceptOfSeq, skipWhile, and similar functions:

// after
use e = source.GetAsyncEnumerator CancellationToken.None
let! hasFirst = e.MoveNextAsync()

if hasFirst then
    let mutable previous = e.Current

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

Why this is better

  • Eliminates per-element struct match: Every element previously required a ValueNone / ValueSome branch. A direct mutable avoids this entirely.
  • Structural consistency: Uses the same while! + explicit enumerator idiom found throughout the rest of the optimised functions in TaskSeqInternal.fs.
  • Cleaner code: The first-element sentinel logic is made explicit via a single let! hasFirst = e.MoveNextAsync() check rather than inlined as a DU branch per element.

What changed

  • src/FSharp.Control.TaskSeq/TaskSeqInternal.fs: refactored pairwise, distinctUntilChanged, distinctUntilChangedWith, distinctUntilChangedWithAsync
  • release-notes.txt: entry under Unreleased

Test Status

  • dotnet build src/FSharp.Control.TaskSeq.sln -c Release — clean, 0 warnings, 0 errors
  • dotnet test (Release) — 5251 passed, 0 failed, 2 skipped
  • ✅ Targeted: Pairwise + DistinctUntilChanged suites — 94 passed, 0 failed
  • dotnet fantomas src/FSharp.Control.TaskSeq/TaskSeqInternal.fs --check — no formatting changes

Generated by 🤖 Repo Assist, see workflow run.

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@96b9d4c39aa22359c0b38265927eadb31dcf4e2a

…ctUntilChanged 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants