Skip to content

[Repo Assist] feat: add TaskSeq.unfold and TaskSeq.unfoldAsync (ref #289)#300

Merged
dsyme merged 2 commits intomainfrom
repo-assist/feat-unfold-2026-03-80824e6b5ff14272
Mar 7, 2026
Merged

[Repo Assist] feat: add TaskSeq.unfold and TaskSeq.unfoldAsync (ref #289)#300
dsyme merged 2 commits intomainfrom
repo-assist/feat-unfold-2026-03-80824e6b5ff14272

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

Adds TaskSeq.unfold and TaskSeq.unfoldAsync — lazy sequence generators that produce elements from an evolving state value, without requiring the element count to be known upfront. Matches the semantics of Seq.unfold and AsyncSeq.unfold.

Part of the work to align with FSharp.Control.AsyncSeq (ref #289). Please do not close #289 as this is one PR of several.


Implementation

Motivation

TaskSeq.init and TaskSeq.initAsync require a known count. unfold fills the gap for cases where the termination condition depends on state (e.g. parsing, pagination, Fibonacci, iterating a cursor).

Performance characteristics:

  • Zero intermediate collection allocations — elements are generated on demand
  • Generator called exactly once per element, plus one final None call
  • Works with take/truncate to consume infinite-like sequences lazily

Changes

File Change
TaskSeqInternal.fs Two new taskSeq {} generators: unfold (sync) and unfoldAsync (async)
TaskSeq.fs Expose both via TaskSeq.unfold / TaskSeq.unfoldAsync
TaskSeq.fsi XML-doc signatures
TaskSeq.Unfold.Tests.fs 14 new tests (new file)
FSharp.Control.TaskSeq.Test.fsproj Register test file

API

TaskSeq.unfold      : ('State -> ('T * 'State) option)         -> 'State -> TaskSeq<'T>
TaskSeq.unfoldAsync : ('State -> Task<('T * 'State) option>)   -> 'State -> TaskSeq<'T>

Example

// Fibonacci sequence (infinite), take first 10
TaskSeq.unfold (fun (a,b) -> Some (a, (b, a+b))) (1,1)
|> TaskSeq.take 10
// → 1, 1, 2, 3, 5, 8, 13, 21, 34, 55

Note on unfoldAsync signature

The generator is typed as Task<_> rather than #Task<_> (flexible type) because the taskSeq {} builder requires a type annotation to resolve the Bind overload. In practice, callers using task { return ... } expressions will satisfy this constraint directly.


Test Status

Build: ✅ succeeded (0 warnings, 0 errors)
Tests: ✅ 14/14 passed
Fantomas: ✅ clean after formatting

Test cases:

  • Empty sequence (generator returns None immediately)
  • Finite sequence (0..9)
  • Singleton (one element then None)
  • Fibonacci via state threading
  • String generation (A..Z)
  • Infinite sequences truncated with take 100
  • Generator call-count verification (n elements + 1 None call)
  • Re-iteration restarts from initial state

Generated by Repo Assist ·

To install this agentic workflow, run

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

Adds two new lazy sequence generators that produce elements from a
state value without requiring the element count to be known upfront.
Matches the semantics of Seq.unfold and AsyncSeq.unfold.

- TaskSeq.unfold     : synchronous generator ('State -> ('T * 'State) option)
- TaskSeq.unfoldAsync: async generator ('State -> Task<('T * 'State) option>)

Key properties:
- Zero allocations per element beyond the value tuple
- Works lazily: generator is only called as elements are consumed
- Infinite sequences supported (use take/truncate to limit)
- Re-iterating restarts from the original state

14 new tests covering: empty (None immediately), finite sequences,
singleton, Fibonacci via state threading, string generation, infinite
sequence truncation, generator call-count verification, and
re-iteration correctness.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions bot added automation enhancement New feature or request performance Performance questions, improvements and fixes repo-assist labels Mar 7, 2026
@dsyme dsyme marked this pull request as ready for review March 7, 2026 22:03
@dsyme dsyme merged commit 468e875 into main Mar 7, 2026
4 checks passed
@dsyme dsyme deleted the repo-assist/feat-unfold-2026-03-80824e6b5ff14272 branch March 7, 2026 22:10
github-actions bot pushed a commit that referenced this pull request Mar 7, 2026
…289)

- TaskSeq.chunkBySize: divides a task sequence into non-overlapping chunks of
  at most chunkSize elements. Uses a fixed-size array buffer (vs. ResizeArray)
  to avoid intermediate allocations and resizing.

- TaskSeq.windowed: returns overlapping sliding windows of a fixed size.
  Uses a ring buffer internally so that only a single allocation (per window)
  is needed; no redundant element copies on each step.

Both functions validate their size argument eagerly (before enumeration starts),
raise ArgumentException for non-positive sizes, and are fully documented in the
.fsi signature file.

Also:
- Update README.md to mark chunkBySize, windowed, pairwise, scan/scanAsync,
  reduce/reduceAsync, and unfold/unfoldAsync as implemented (these were merged
  in PRs #293, #296, #299, #300 respectively).
- Update release-notes.txt for 0.5.0.
- 171 new tests across TaskSeq.ChunkBySize.Tests.fs and TaskSeq.Windowed.Tests.fs.
  All 4021 existing tests continue to 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

Labels

automation enhancement New feature or request performance Performance questions, improvements and fixes repo-assist

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align set of supported functions with latest FSharp.Control.AsyncSeq

1 participant