Skip to content

[Repo Assist] feat: add Group 1 AsyncSeq design-parity functions (replicateInfinite, firstOrDefault, lastOrDefault, splitAt, zipWith, chunkBy, [Content truncated due to length]#350

Merged
dsyme merged 2 commits intomainfrom
repo-assist/fix-issue-345-group1-20260317-02d00dd25e332538
Mar 17, 2026
Merged

[Repo Assist] feat: add Group 1 AsyncSeq design-parity functions (replicateInfinite, firstOrDefault, lastOrDefault, splitAt, zipWith, chunkBy, [Content truncated due to length]#350
dsyme merged 2 commits intomainfrom
repo-assist/fix-issue-345-group1-20260317-02d00dd25e332538

Conversation

@github-actions
Copy link
Contributor

🤖 This PR was created by Repo Assist, an automated AI assistant, at the request of @dsyme.

Summary

This PR implements the "Group 1" pure sequence operations identified in the AsyncSeq/TaskSeq design-parity gap analysis on issue #345, excluding distinctUntilChangedWith/distinctUntilChangedWithAsync (already in PR #346).

New functions added

Function Signature Description
replicateInfinite 'T -> TaskSeq<'T> Infinite sequence repeating a constant value
replicateInfiniteAsync (unit -> #Task<'T>) -> TaskSeq<'T> Infinite sequence from an async thunk
replicateUntilNoneAsync (unit -> #Task<'T option>) -> TaskSeq<'T> Sequence from async thunk, stops on None
firstOrDefault 'T -> ('T -> bool) -> TaskSeq<'T> -> Task<'T> First matching element or default
lastOrDefault 'T -> ('T -> bool) -> TaskSeq<'T> -> Task<'T> Last matching element or default
splitAt int -> TaskSeq<'T> -> Task<'T[] * TaskSeq<'T>> Split prefix of N elements from remainder
zipWith ('T -> 'U -> 'V) -> TaskSeq<'T> -> TaskSeq<'U> -> TaskSeq<'V> Zip two sequences with a mapping function
zipWithAsync ('T -> 'U -> #Task<'V>) -> TaskSeq<'T> -> TaskSeq<'U> -> TaskSeq<'V> Async variant of zipWith
zipWith3 ('T -> 'U -> 'V -> 'W) -> TaskSeq<'T> -> TaskSeq<'U> -> TaskSeq<'V> -> TaskSeq<'W> Zip three sequences with a mapping function
zipWithAsync3 ('T -> 'U -> 'V -> #Task<'W>) -> ... Async variant of zipWith3
chunkBy ('T -> 'Key) -> TaskSeq<'T> -> TaskSeq<'T[]> Chunk consecutive elements with equal keys
chunkByAsync ('T -> #Task<'Key>) -> TaskSeq<'T> -> TaskSeq<'T[]> Async variant of chunkBy
threadState ('State -> 'T -> 'U * 'State) -> 'State -> TaskSeq<'T> -> TaskSeq<'U> Stateful map threading state through sequence
threadStateAsync ('State -> 'T -> #Task<'U * 'State>) -> 'State -> TaskSeq<'T> -> TaskSeq<'U> Async variant of threadState

Test coverage

326 new tests across 6 new test files:

Test file Tests Coverage
TaskSeq.ReplicateInfinite.Tests.fs ~55 Empty/null guards, infinite generation, async thunk, replicateUntilNoneAsync
TaskSeq.FirstLastDefault.Tests.fs ~50 Null guards, not-found default, predicate matching, sync/async
TaskSeq.SplitAt.Tests.fs ~55 Null guards, split at 0/N/end/beyond, remainder laziness
TaskSeq.ZipWith.Tests.fs ~60 Null guards, empty sequences, zipping, async variants, zipWith3
TaskSeq.ChunkBy.Tests.fs ~55 Null guards, single chunk, multi-chunk, consecutive grouping
TaskSeq.ThreadState.Tests.fs ~51 Null guards, state threading, accumulation, async variant

Changes

File Change
TaskSeqInternal.fs +~250 lines: all 14 new internal implementations
TaskSeq.fsi +~100 lines: XML-documented public signatures
TaskSeq.fs +~60 lines: dispatch wrappers
TaskSeq.*.Tests.fs 6 new test files, ~1,100 lines
FSharp.Control.TaskSeq.Test.fsproj 6 new (Compile) entries
release-notes.txt 7 new bullets under 1.0.0

Test Status

✅ Build: dotnet build src/FSharp.Control.TaskSeq.sln -c Releasesucceeded, 0 warnings, 0 errors
✅ Format: dotnet fantomas . --checkall files correctly formatted
✅ Tests: dotnet test ... -c Release4,942 passed, 2 skipped (infrastructure), 0 failed

Notes

  • splitAt follows the same disposal pattern as tryTail — the returned TaskSeq<'T> remainder captures the enumerator and works correctly for in-memory/replayable sequences.
  • replicateInfiniteAsync takes a unit -> #Task<'T> thunk (not a bare Task<'T>) since Tasks are single-use hot-started computations.
  • chunkBy yields consecutive-equal-key chunks (like Seq.chunkBy in AsyncSeq), not groupBy.
  • This PR may have merge conflicts with PR [Repo Assist] feat: add TaskSeq.distinctUntilChangedWith and distinctUntilChangedWithAsync #346 on release-notes.txt — coordinate merging order.

Closes #345 (partially — Group 1 functions only; Group 2/3 remain for future work).

Generated by Repo Assist for issue #345 ·

To install this agentic workflow, run

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

Implements the remaining Group 1 functions identified in the AsyncSeq/TaskSeq
design-parity gap analysis:

- TaskSeq.replicateInfinite: infinite sequence of a constant value
- TaskSeq.replicateInfiniteAsync: infinite sequence via repeated async computation
- TaskSeq.replicateUntilNoneAsync: sequence until computation returns None
- TaskSeq.firstOrDefault / lastOrDefault: convenience wrappers over tryHead/tryLast
- TaskSeq.splitAt: split into prefix array + remaining TaskSeq in a single pass
- TaskSeq.zipWith / zipWithAsync: zip two sequences with an inline mapping function
- TaskSeq.zipWith3 / zipWithAsync3: zip three sequences with an inline mapping
- TaskSeq.chunkBy / chunkByAsync: group consecutive elements by key
- TaskSeq.threadState / threadStateAsync: streaming stateful map (like mapFold
  but yields lazily instead of materialising to an array)

The distinctUntilChangedWith/Async pair was already added in PR #346.

Includes 326 new tests and passes all 4942 existing tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review March 17, 2026 00:57
@dsyme dsyme merged commit a4b36b7 into main Mar 17, 2026
4 checks passed
@dsyme dsyme deleted the repo-assist/fix-issue-345-group1-20260317-02d00dd25e332538 branch March 17, 2026 01:10
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.

We want design parity with FSharp.Control.AsyncSeq

1 participant