[Sequence] Creation: Collection::asSequence() and the constrainOnce flag - #40
Merged
Merged
Conversation
Return a lazy Sequence view over any Collection. A Collection is an IteratorAggregate, so GeneratorSequence already treats it as a replayable producer asked for a fresh iterator on every pass: nothing is copied, nothing is read before a terminal operation, and every pass sees the collection's current elements. Map needs nothing of its own: its keys, values and entries views are Collections and inherit the method. Add runtime coverage across every Collection implementation - order, emptiness, replay, short-circuiting, laziness and the live view under mutation - and a type assertion for the returned Sequence.
Force a sequence down to a single pass whatever its source: the second pass throws NonReplayableSourceException instead of replaying an array or re-invoking a producer. That is what a caller wants when replaying would repeat a side effect, a producer closure firing a SQL query being the case raised on noctud#23. The guard sits before the per-source-kind branches of resolveSourceForThisPass(): a constrained sequence has exactly one pass, so the identity check that protects a replay has nothing left to protect. Extract produceFromClosure() along the way, now shared by both paths. Give the exception its own named constructor - the existing message speaks of a non-replayable source, which would be untrue of a constrained array - and document the flag on the Sequence contract next to the source kinds.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Two creation APIs from the #23 checklist:
Collection::asSequence()(§5) and theconstrainOnceflag onsequenceOf()(§4). They travel together because they answer the same question — how a sequence comes into being — and the remaining factories (generateSequence,emptySequence) follow in the next one.Collection::asSequence()Eight lines, because the machinery was already there:
A
Collectionis anIteratorAggregate, whichGeneratorSequencealready treats as a producer asked for an iterable once per pass. Every element store returns a freshArrayIteratorfromgetIterator(), so the identity guard ofresolveProducedIterable()is satisfied without a special case. No copy, no wrapper class, no new branch in the replayability contract.What that buys is a live view: each pass re-reads the collection rather than a snapshot taken at call time.
asSequence_reads_the_collection_on_each_passpins it on the mutable side, and its immutable branch pins the counterpart —add()returns a new collection the existing sequence does not see.The laziness test is phrased as "no read of its own" rather than "no read at all", on purpose: view collections such as
MapKeySetorMapEntrySetread their source when they are built, well beforeasSequence()is called. What is under test is thatasSequence()and the operation chained onto it add nothing to that count.Both live in the shared
CollectionConvert/CollectionMutateWritetraits, so they run against everyCollectionimplementation — 14 of them.constrainOnceA constructor flag rather than a
constrainOnce()method, as agreed on #23: one class, one boolean, no wrapper sequence to explain.The guard sits before the per-source-kind branches of
resolveSourceForThisPass():That position is the point: the identity check and the
WeakReferenceexist to keep a replay honest, and a constrained sequence has no second pass to keep honest. The closure resolution was duplicated between the two paths, so it comes out asproduceFromClosure().The exception gets its own named constructor. The existing message speaks of a source that is non-replayable, which would be untrue of an array the caller chose to constrain, and a reader chasing that message would look in the wrong place.
A chained operation needs no propagation: the derived sequence does not carry the flag, but its factory re-reads the root on every pass, so the root is what throws —
constrained_once_throws_through_a_chained_operationpins that. And like the existing one-shot rule, it throws atgetIterator()rather than at the first advance, so a violation surfaces at the start of the offending pass instead of yielding a silent empty result.