feat: add TaskSeq.foldWhile and foldWhileAsync#401
Open
OnurGumus wants to merge 4 commits intofsprojects:mainfrom
Open
feat: add TaskSeq.foldWhile and foldWhileAsync#401OnurGumus wants to merge 4 commits intofsprojects:mainfrom
OnurGumus wants to merge 4 commits intofsprojects:mainfrom
Conversation
Fold over a task sequence with early termination. The folder returns
`Continue newState` to keep consuming, or `Halt newState` to stop
immediately; in either case the state is updated. No elements past the
one that caused Halt are enumerated from the input.
Motivation: reaching for `fold`/`foldAsync` often ends with a mutable
flag or a `match state with Decided -> state | _ -> ...` guard at the
top of the folder when the caller wants to short-circuit on some
condition discovered mid-stream. `tryPickAsync` handles the "find and
halt" subcase but cannot also thread an accumulator. `foldUntil` fills
the gap and removes the impedance mismatch.
Includes:
- FoldStep<'State> DU (Continue / Halt) in public API
- Internal FoldUntilAction DU unifying sync/async folder dispatch
- Public TaskSeq.foldUntil and TaskSeq.foldUntilAsync
- XML docs on both overloads mirroring fold/foldAsync style
- Release-notes entry
- 22 tests covering: null source, empty sequence (no folder call,
initial state preserved), all-Continue (equals fold), Halt-on-first
(1 folder call, no further pulls), Halt mid-sequence (correct count,
source not pulled past Halt), Halt-on-last (equals fold)
Contributor
|
@OnurGumus I'm wondering about the new type. Why not a boolean flag to indicate when to stop? |
Reworks the early-termination fold added in 7bde226 to match the conventions of FSharp.Control.TaskSeq: bool-returning predicate + plain folder, and no new public DU. Every other early-termination API in the library (takeWhile, skipWhile, tryFind, forall, exists) uses this shape; every DU in the source is internal. FoldStep<'State> would have been the only public DU, which is the inconsistency @dsyme flagged on the PR. API: - TaskSeq.foldWhile : ('State -> 'T -> bool) -> ('State -> 'T -> 'State) -> 'State -> TaskSeq<'T> -> Task<'State> - TaskSeq.foldWhileAsync : ('State -> 'T -> #Task<bool>) -> ('State -> 'T -> #Task<'State>) -> 'State -> TaskSeq<'T> -> Task<'State> Semantics (match takeWhile, exclusive): the predicate is evaluated against (currentState, nextElement) before that element is folded. If false, iteration halts without folding that element and the source is not enumerated further. Removes: - public FoldStep<'State> DU - internal FoldUntilAction DU - TaskSeq.foldUntil / foldUntilAsync Tests: rewritten for exclusive semantics (halting element not folded, predicate-call / folder-call counts asserted separately). Full test suite green (5277 passed).
5c79074 to
bbf9125
Compare
Author
Hi @dsyme , I kinda favor types over bools, but you are right. Looking at other signatures using bool would be more consistent. I refactored accordingly: foldWhile : ('State -> 'T -> bool) -> ('State -> 'T -> 'State) -> 'State -> TaskSeq<'T> -> Task<'State> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
TaskSeq.foldWhileandTaskSeq.foldWhileAsync— a fold variant with early termination. A boolean predicate decides, against the current state and next element, whether that element should be folded in. Once the predicate returnsfalse, iteration halts without folding that element and no further elements are pulled from the input (matchestakeWhilesemantics exactly).Motivation
Reaching for
fold/foldAsyncoften ends with a mutable flag or amatch state with Decided -> state | _ -> ...guard at the top of the folder when the caller wants to short-circuit on some condition discovered mid-stream.tryPickAsynchandles the "find and halt" subcase but cannot also thread an accumulator.foldWhilefills the gap.Why not existing operators
tryPickAsync— short-circuits but returns'U option, no accumulator.fold+ mutable flag — works but requires mutation at the call site and drains the whole sequence even after deciding.takeWhileInclusive+toListAsync— nice shape but allocates a list and requires a second pass to inspect.scan+tryFind— loses the final state when no element halts.foldWhileis the shape the language naturally wants for stateful early-exit folds, and matches the existingtakeWhile/skipWhile/tryFindconvention: plainboolpredicate separated from the data function, no new public types.What's in the PR
TaskSeq.foldWhileandTaskSeq.foldWhileAsyncinTaskSeq.fs+ signatures and XML docs inTaskSeq.fsiUnreleasedTaskSeq.FoldWhile.Tests.fs:ArgumentNullExceptionTestEmptyVariants)fold(including left-associativity)foldTest plan
dotnet test ...— full suite: 5277 passed, 0 failed, 2 skippeddotnet fantomas . --check— cleannetstandard2.1Originally proposed as
foldUntilwith aFoldStep<'State>DU (Continue/Halt). Per @dsyme's review, refactored to thebool-predicate form above to match the library's existing early-termination API style.