Skip to content

[Repo Assist] perf+feat: optimize exists/contains; add TaskSeq.distinct/distinctBy/distinctByAsync#305

Merged
dsyme merged 3 commits intomainfrom
repo-assist/perf-exists-distinct-2026-03-ae5d2085b24d632f
Mar 8, 2026
Merged

[Repo Assist] perf+feat: optimize exists/contains; add TaskSeq.distinct/distinctBy/distinctByAsync#305
dsyme merged 3 commits intomainfrom
repo-assist/perf-exists-distinct-2026-03-ae5d2085b24d632f

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Mar 7, 2026

🤖 This PR was created by Repo Assist, an automated AI assistant.

Summary

This PR bundles two improvements from the selected runs (Task 8: Performance, Task 5: Coding Improvements):


Performance: optimize exists / existsAsync / contains

Before: all three functions went through tryFind, which boxes the found element into a heap-allocated Some 'T before the caller immediately discards it via Option.isSome. contains additionally created a (=) value partial-application closure.

After: a dedicated exists function (mirroring the existing forall implementation) returns bool directly, with an early-exit loop that never touches Option. contains uses its own tight loop with an inline equality check.

This eliminates one heap allocation per successful call and avoids a closure allocation for contains.


Coding improvement: TaskSeq.distinct / distinctBy / distinctByAsync

Adds three new combinators that complement the existing distinctUntilChanged, except, and exceptOfSeq:

Function Description
TaskSeq.distinct Removes all duplicates (keeps first occurrence), equality on the element
TaskSeq.distinctBy projection Removes elements whose projected key has already been seen
TaskSeq.distinctByAsync projection Async variant of distinctBy

All three use a HashSet and iterate the source once. Like except, they are not suitable for infinite sequences.

Difference from distinctUntilChanged: distinctUntilChanged only removes consecutive duplicates (like uniq). distinct removes all duplicates regardless of position (like sort | uniq -u).

taskSeq { yield! [1; 2; 1; 3; 2] }
|> TaskSeq.distinct
// → [1; 2; 3]

taskSeq { yield! [1; 2; 1; 3; 2] }
|> TaskSeq.distinctUntilChanged
// → [1; 2; 1; 3; 2]  (only consecutive removed)

Files changed

  • TaskSeqInternal.fs — new exists, contains, distinct, distinctBy, distinctByAsync implementations
  • TaskSeq.fsi — public API signatures with full XML doc comments
  • TaskSeq.fs — wire-up
  • TaskSeq.Distinct.Tests.fs (new) — 75 tests
  • FSharp.Control.TaskSeq.Test.fsproj — test file registration
  • release-notes.txt — updated

Test Status

Build: ✅ succeeded (Release)
Tests: ✅ 3893 passed, 0 failed, 2 skipped (pre-existing infrastructure skips)

Generated by Repo Assist ·

To install this agentic workflow, run

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

…ByAsync

Performance (Task 8):
- Add dedicated 'exists' implementation returning bool directly, avoiding
  the intermediate Option<'T> allocation that the previous tryFind+isSome
  approach incurred. Both exists/existsAsync and contains now use this path.
- 'contains' also avoids the (=) closure allocation from the old impl.

Coding improvement (Task 5):
- Add TaskSeq.distinct: removes all duplicate elements (keeps first occurrence)
  using a HashSet, complementing the existing distinctUntilChanged.
- Add TaskSeq.distinctBy: de-duplicates by a key projection function.
- Add TaskSeq.distinctByAsync: async variant of distinctBy.
- 75 new tests covering empty sequences, functionality, side effects, and
  comparison with Seq/distinctUntilChanged semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review March 7, 2026 23:27
@dsyme dsyme merged commit 78a1d50 into main Mar 8, 2026
4 checks passed
@dsyme dsyme deleted the repo-assist/perf-exists-distinct-2026-03-ae5d2085b24d632f branch March 8, 2026 01:55
github-actions bot added a commit that referenced this pull request Mar 8, 2026
Mark as implemented (✅) following the wave of PRs merged for v0.6.0:
- TaskSeq.average, averageBy, averageByAsync (#304)
- TaskSeq.sum, sumBy, sumByAsync (#304)
- TaskSeq.distinct, distinctBy, distinctByAsync, distinctUntilChanged (#305)
- TaskSeq.mapFold, mapFoldAsync (#306)
- TaskSeq.groupBy, groupByAsync (#307)
- TaskSeq.countBy, countByAsync — add missing table row (#307)
- TaskSeq.partition, partitionAsync — add missing table row (#307)

Also:
- Fix typo 'dictinctBy' → 'distinctBy'
- Update 'Status & planning' checklist to reflect completed work
- Add PR link definitions (#304#307) at the bottom

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.

1 participant