Repository navigation
feat: Queue and IQueue modules with the vocabulary of the FSharp.Core List module - #33
Open
xperiandri wants to merge 16 commits into
Open
xperiandri wants to merge 16 commits into
xperiandri wants to merge 16 commits into
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
The broad breaking API rewrite needs human compatibility and cross-framework validation, with exception parity still unresolved.
1 open issue
What changed in this PR
Rewrites the queue bindings around FSharp.Core List vocabulary, continuing ADR 0001’s per-collection module design.
Changes:
- Adds concrete and interface queue APIs with List-compatible semantics.
- Adds Option/Tuple variants, shared enumeration helpers, and
queue { }. - Expands contract tests and documents breaking changes.
| File | Description |
|---|---|
| tests/FSharp.Collections.Immutable.Tests/Queue/QueueTupleTests.fs | Tests reference-tuple operations. |
| tests/FSharp.Collections.Immutable.Tests/Queue/QueueTests.fs | Tests queue behavior and exceptions. |
| tests/FSharp.Collections.Immutable.Tests/Queue/QueueOptionTests.fs | Tests option-returning operations. |
| tests/FSharp.Collections.Immutable.Tests/Queue/QueueOps.fs | Connects Queue to shared contracts. |
| tests/FSharp.Collections.Immutable.Tests/Queue/QueueContractTests.fs | Instantiates queue contract suites. |
| tests/FSharp.Collections.Immutable.Tests/Queue/QueueComputationExpressionTests.fs | Tests computation expression behavior. |
| tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTupleTests.fs | Tests interface tuple variants. |
| tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTests.fs | Tests implementation preservation and enumeration. |
| tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOptionTests.fs | Tests interface option variants. |
| tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOps.fs | Connects IQueue to shared contracts. |
| tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj | Registers queue tests. |
| src/FSharp.Collections.Immutable/Queue/QueueTypes.fs | Defines queue type abbreviations. |
| src/FSharp.Collections.Immutable/Queue/QueueTuple.fsi | Declares tuple-variant API. |
| src/FSharp.Collections.Immutable/Queue/QueueTuple.fs | Implements tuple variants. |
| src/FSharp.Collections.Immutable/Queue/QueueOption.fsi | Declares option-variant API. |
| src/FSharp.Collections.Immutable/Queue/QueueOption.fs | Implements option variants. |
| src/FSharp.Collections.Immutable/Queue/QueueCE.fs | Implements the queue expression builder. |
| src/FSharp.Collections.Immutable/Queue/IQueueTuple.fsi | Declares interface tuple variants. |
| src/FSharp.Collections.Immutable/Queue/IQueueTuple.fs | Implements interface tuple variants. |
| src/FSharp.Collections.Immutable/Queue/IQueueOption.fsi | Declares interface option variants. |
| src/FSharp.Collections.Immutable/Queue/IQueueOption.fs | Implements interface option variants. |
| src/FSharp.Collections.Immutable/Queue/IQueue.fs | Implements interface queue operations. |
| src/FSharp.Collections.Immutable/Queue.fs | Removes the legacy module. |
| src/FSharp.Collections.Immutable/Helper.fs | Adds shared queue construction and traversal. |
| src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj | Registers the new queue files. |
| src/FSharp.Collections.Immutable/ComputationExpressions.fs | Exposes queue { }. |
| README.md | Updates supported collection table. |
| docsSrc/index.md | Lists interface and expression support. |
| docsSrc/Explanations/Background.md | Updates queue support documentation. |
| CHANGELOG.md | Records additions and breaking changes. |
Review effort: Balanced
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 18, 2026 18:49
56e9ddf to
e7a6257
Compare
Open
5 of 6 tasks
xperiandri
added a commit
that referenced
this pull request
Oct 18, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 18, 2026 19:03
ec1f68a to
4bc0412
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 18, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 18, 2026 19:39
4bc0412 to
2245874
Compare
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 18, 2026 20:12
430f3fb to
53a3555
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 18, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 18, 2026 20:17
2245874 to
379bddd
Compare
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 18, 2026 23:05
53a3555 to
5c8b1f5
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 19, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 10:36
379bddd to
a7e7964
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 19, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 11:34
a7e7964 to
f92710a
Compare
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 19, 2026 14:49
67c2c1c to
4a66e46
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 19, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 14:49
f92710a to
b9b20d3
Compare
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 19, 2026 15:55
4a66e46 to
aa81bb5
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 19, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 15:55
b9b20d3 to
b539f98
Compare
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 19, 2026 17:31
aa81bb5 to
6ce859c
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 19, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 17:31
b539f98 to
a9f0d19
Compare
xperiandri
force-pushed
the
feat/indexed-list
branch
from
October 19, 2026 17:40
6ce859c to
d416a34
Compare
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 17:44
a9f0d19 to
ccef883
Compare
xperiandri
added a commit
that referenced
this pull request
Oct 19, 2026
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…Core `List` module
`Queue` (`ImmutableQueue<'T>`) replaces the old module, which worked on `IImmutableQueue<'T>`. It has every function of
`List` that applies to an immutable queue, over the elements in the order in which they leave the queue, with the checks
and the exceptions of `List` and with stable sorts. Its parameters and exceptions name the queues `queue`, `queue1`,
`queue2`, `queue3` and `queues` where `List` names its lists. The extras of ADR 0001 are `enqueue`, `enqueueMany`,
`uncons`, `tryUncons`, the struct partial patterns `Cons` and `Nil`, `ofReadOnlySpan`, `ofSpan`, `skipLast`, `takeLast`,
`tryTail` and `sortWithComparer`; `enqueue`, `head`, `tail` and `uncons` compile as `Enqueue`, `Peek`, `Dequeue` and
`PeekDequeue`.
`IQueue` has the same functions for `IImmutableQueue<'T>` without the creation functions, plus `clear`. A result with the
element type of the input keeps the implementation of the input through `Clear` and `Enqueue` or through `Dequeue`; the
other results are immutable queues. The `Option` and `Tuple` namespaces repeat the functions that use value options or
struct tuples, and `queue { }` builds a queue.
A queue does not store its length, so `length` is O(n). `head`, `tail`, `skip`, `skipWhile`, `take`, `truncate` and
`splitAt` walk only the elements that they need, and the functions that check against the length take a snapshot first.
The `ImmutableQueue` module alias of the old module is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sequence-like, creation and stable sorting contracts run for `Queue`, and the sequence-like and stable sorting
contracts for `IQueue`, over immutable queues seen through the interface. `QueueOps.paramName` maps the names that the
`List` exceptions report to the names of the queue modules, also inside the names of several parameters and the
`list.[i]` names of `List.transpose`.
Further tests cover null inputs, the first-in, first-out order, `enqueue` and `enqueueMany`, `head`, `tail`, `uncons`
and `tryUncons`, the `Cons` and `Nil` patterns, queues whose elements lie in both stacks of `ImmutableQueue`, the results
that are their inputs, the order in which the functions of two queues call their callbacks before they throw, the
complete messages of the `List` exceptions with the names of the queues, the implementation that `IQueue` keeps,
`queue { }` and the `Option` and `Tuple` namespaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The changelog lists the new functions of `Queue`, the new `IQueue` module, the `Option` and `Tuple` modules and
`queue { }`, the breaking changes of the `List` semantics, the queue type of the module and the patterns, the fixed
`forall` and `averageBy`, and the removed `clear` and `ImmutableQueue` alias. The collection tables name `queue { }` for
`Queue` and add `IQueue`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FSharp.Core `List` module Follows the reworked ADR 0001 (D2, D5) and the answers of the maintainer: - `Queue`, `IQueue` and their `Option` and `Tuple` modules are the root modules of their files. The `Queue<'T>` and `IQueue<'T>` abbreviations move to `Queue/QueueTypes.fs`, which needs no signature file. `Queue.fsi` and `Queue.fs` open `FSharp.Collections.Immutable` after `System.Collections.Generic`, whose `Queue<T>` would shadow the abbreviation. - `tryHead`, `tryTail` and `tryUncons` compile as `TryPeek`, `TryDequeue` and `TryPeekDequeue` in all six modules, next to `Peek`, `Dequeue` and `PeekDequeue` for `head`, `tail` and `uncons` and `EnqueueMany` for `enqueueMany`. - The sorts of both modules compare with the cached `DefaultComparer<'T>.Instance`, so a call no longer creates a comparer. - The checks count a queue with `Lengths.queueUpTo` instead of the private `lengthUpToOne` and `lengthUpToTwo`. - `Helper.fs` gets the internal `Queues` module with the helpers that the modules repeated: `ofFreshArray`, `ofBuffer`, `lastMatch` and the walks `firstMatch`, `firstChoice` and `front`. A walk takes the interface and runs one generic loop over the struct enumerator of an `ImmutableQueue` or over the enumerator of another queue, which it disposes. The walks of `Queue` still allocate nothing, and the ones of `IQueue` no longer allocate an enumerator for an `ImmutableQueue`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, also when that queue is empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows the reworked ADR 0001 (D2, D8): - Test methods carry no categories: the classes carry them. The classes with the tests of the `Cons` and `Nil` patterns carry `PatternMatchingTestCategory`. - `QueueOps` and `IQueueOps` are the root modules of their own files. - The stable sorting contract runs on the 20 pairs of `Inputs.manyPairs` for both modules. - New tests, with arrays and data rows: the queue functions of all six modules compile with the names of the queue operations of the BCL; the sorts of both modules order strings in F# generic comparison; the searches, `take` and `truncate` of `IQueue` read a queue of another implementation only as far as they need and dispose its enumerator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FSharp.Core `List` module Follows the review of #33 and ADR 0001 (D6, D7, D14): - `sort`, `sortByDescending`, `sortDescending` and `sortWith` of `Queue` and `IQueue` sort with the `Seq` functions of FSharp.Core, which share the stable sort of `List`, so that a comparison or a projection that throws throws what `List` throws. LINQ already wrapped the exception of a comparison in an `InvalidOperationException`, as `Array.Sort` does. `OrderByDescending` differed for the projection, which `List.sortByDescending` calls inside the comparison. `sortBy` and `sortWithComparer` keep LINQ. - `sortDescending` and `sortByDescending` call `Seq.sortWith` with the cached comparer of F# generic comparison, through the new `Queues.sortDescending` and `Queues.sortByDescending`. `Seq.sortDescending` and `Seq.sortByDescending` are inline: inside a generic function they compare with the generic operator, which boxes the elements of a value type in every comparison. For 10 000 integers, they allocate 7.8 MB per call instead of 0.74 MB. - At 10 000 integers, `sortWith` takes about the same time as before and allocates 743 KB per call instead of 440 KB. - `QueueCE.fsi` carries the signatures and the documentation of `QueueCE`, compiled right before it. `queue` has its `val` and its example in `ComputationExpressions.fsi`, after `indexedList`, and only the binding stays in the `.fs`. - `queue { yield! ... }` rejects a null sequence, array or queue with `ArgumentNullException` for the parameter `items`. - The remarks of `IQueue.append` say that the result keeps the implementation of the first queue, which receives the elements (D14), also when that queue is empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The tests compare arrays and take their cases as data rows, as the converted contracts of `IndexedList` do (3751 tests per
target framework; 742 of them for `Queue` and 702 for `IQueue`, instead of 258 and 229):
- `QueueOps` and `IQueueOps` observe a collection with its `toArray`, and results of another element type are arrays;
`ToList` is the `toList` under test. The stable sorting contract runs for both modules, with the new tests of the
exceptions of the sorts against `List`.
- The case tables of `QueueTests` and `IQueueTests` are maps whose names are the rows: 114 and 115 functions that reject a
null queue, 5 that reject another null input, 12 and 10 results that are the input itself, 38 functions of `IQueue` that
keep the implementation, 6 sorts of one element, 4 functions of two queues that walk a common prefix and 14 complete
messages. The names tell apart the cases that repeated a function: the two parts of `partition` and `splitAt`, `append`
to an empty queue and the two ways to keep every element with `skip`.
- Expectations come from the `Array` functions, except where `List` behaves differently: the stable sorts, the walk of the
common prefix before a length check, `exists2` and `forall2`, `contains` of `NaN` and the exceptions keep their `List`
oracles. Tuples that hold arrays are compared by their parts.
- `queue { yield! ... }` rejects a null sequence, array or queue.
- The descending sorts of 10 000 integers allocate less than 3 MB. `Seq.sortDescending` and `Seq.sortByDescending` take
7.8 MB, because they box the integers in every comparison.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… table The changelog names the compiled names of the queue operations, as it does for `Stack`, and says that `IQueue.append` keeps the implementation of the first queue. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The tests call the assertion helpers of `main`, which are static classes: `ExceptionAssert.Same` replaces `ExceptionAssert.same`, and `OverrideAssert.OptionMatches`, `PairMatches` and `TripleMatches` replace the module functions. The calls are tupled and their messages come last (25 calls in 6 files). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FSharp.Core `List` module `sortDescending` and `sortByDescending` of `Queue` and `IQueue` call `DescendingSorts` of `Helper.fs`, which the sorts of `IndexedList` share, instead of copies in `Queues`. The module runs `Seq.sortWith` with the cached comparer of F# generic comparison, because the inline `Seq.sortDescending` and `Seq.sortByDescending` box the elements of a value type in every comparison. For 10 000 integers, the sorts allocate 0.74 MB per call instead of 7.6 MB and take about half the time. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`QueueOps` and `IQueueOps` give the stable sorting contract the two functions that sort integers, so that it checks for `Queue` and `IQueue` the allocation of the descending sorts and the order in which `sortByDescending` calls the projection. The allocation test of `QueueTests` and `IQueueTests` and its helper are removed, because the contract runs the same check (743 tests for `Queue` and 703 for `IQueue`). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… table The `Added`, `Changed` and `Removed` entries name only what the published 1.0.0 had or did, checked in the 1.0.0 assembly by reflection and by running its functions: its `Queue` module has `average`, `averageBy`, `clear`, `empty`, `enqueue`, `exists`, `filter`, `find`, `findIndex`, `fold`, `forall`, `head`, `isEmpty`, `iter`, `iter2`, `iteri`, `ofSeq`, `pick`, `reduce`, `singleton`, `sum`, `sumBy`, `tail`, `tryFind`, `tryFindIndex`, `tryPick` and the complete active pattern `Cons | Nil`. - `Removed` keeps `clear`, with its replacements. The `ImmutableQueue` module alias goes: an abbreviation of a module cannot be used from another assembly. - `Changed` compares with 1.0.0. The entry about `voption` names `tryFind`, `tryFindIndex` and `tryPick`, the only `try*` functions of 1.0.0, and the complete pattern `Cons | Nil` is described by what its `Cons` case matched, a reference tuple. `iter2` of queues of different lengths stopped at the shorter queue in 1.0.0 and now throws `ArgumentException`. A `null` queue threw `ArgumentNullException` in 1.0.0 too, so that is no longer listed. The compiled names are a change: 1.0.0 compiled the F# names, so `enqueue`, `head` and `tail` now compile to `Enqueue`, `Peek` and `Dequeue`, and the other functions to PascalCase names. - `Added` no longer names `enqueue` and the patterns `Cons` and `Nil`, which 1.0.0 had. Its sentence about compiled names names only the functions that 1.0.0 did not have: `enqueueMany`, `uncons`, `tryHead`, `tryTail` and `tryUncons`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… table The `Removed` entry of `Queue.clear` names the replacement for an interface value, as the entry of `ImmutableList.clear` does: `IQueue.clear` keeps the implementation of the queue that it clears, and `Queue.empty` creates the default one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FSharp.Core `List` module
A function that takes a sequence rejects a default flat list given as the sequence as it rejects a null
sequence: `ArgumentNullException` that names the parameter, from `checkNotNullOrDefaultSeq` as the first
statement (ADR 0001, D6). The default flat list is a boxed struct, not null, so it reached the BCL and
failed with `InvalidOperationException` while the call enumerated it, or was not read at all.
- `Queue.ofSeq` (`source`), `concat` and `transpose` (`queues`), `enqueueMany` (`values`) and `except`
(`itemsToExclude`), and the same four functions of `IQueue` (it has no `ofSeq`). `except` checks the
items before it looks at an empty queue, as `List.except` does, so a default flat list is rejected for
an empty queue too. The checks also see a default flat list of a derived reference type that arrives as
a sequence of its base type, such as strings as objects.
- `queue { }`: `yield!` of a sequence and `for` reject a default flat list. The checks of `yield!` of a
sequence, an array and a queue now run when `yield!` is evaluated, before the buffer is touched,
instead of inside the function that it returns, so that the builder members themselves reject their
input. The array check stays needed: `AddRange` of a null array converts it to an empty span and adds
nothing.
- The documentation of these functions says "null or a default flat list" the way `IndexedList` does.
The other parameters of the queue modules are not sequences: the queue parameters are classes or
interfaces that a flat list cannot be, and `ofArray` already checks its array.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The queue modules inherit the shared sequence-input tests: `ofSeq` (`Queue`), `concat`, `transpose` and `except` reject a default flat list given as the sequence as they reject a null sequence. This adds the rows that no shared contract has, as data rows with the names of the functions: - `QueueTests` and `IQueueTests`: each function that takes a sequence with a default flat list given as the sequence, with the parameter that names it (9 rows each): `ofSeq`, `concat`, `transpose`, `enqueueMany` and `except` for `Queue`; `concat`, `transpose`, `enqueueMany` and `except` for `IQueue`. Rows with a default flat list of strings as a sequence of objects, and with a default flat list of immutable queues as a sequence of the interface, check the covariant sequences that a type test for the flat list of the element type does not see. `except` is also checked on an empty queue. - A flat list of strings that is not default is still taken as a sequence of objects. - `QueueComputationExpressionTests`: `yield!` and `for` with a null sequence, a default flat list and a default flat list of strings as objects (6 rows), and the same flat list of strings that is not default. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… table
The changelog says what the sequence-input contract does for the queue modules: a function that takes a
sequence throws `ArgumentNullException` that names the parameter for a `null` sequence and for a default
`FlatList` given as a sequence, in `Queue`, `IQueue` and `queue { }`. Of these functions only `ofSeq`
exists in 1.0.0, where a default `FlatList` made it throw `InvalidOperationException` (checked in the
1.0.0 assembly), so `Changed` names `ofSeq` only; the other functions are new.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
feat/queue
branch
from
October 19, 2026 18:10
ccef883 to
d53b2ab
Compare
This branch has not been deployed
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.

Proposed Changes
Rewrites the legacy
Queuemodule forImmutableQueue<'T>and adds the interface moduleIQueue(IImmutableQueue<'T>) after ADR 0001 (#23). This pull request is stacked on #31 (feat/indexed-list).Queue/Queue.fsiandQueue.fs: 124 functions, in the canonical category order. They are theQueuecolumn of ADR Appendix A, over the elements in the order in which they leave the queue. WhereListnames a list, the parameters name the queue:queue,queue1,queue2,queue3,queues.ImmutableQueuecome first (D5):Peek,Dequeue,Enqueue,IsEmpty.head,tail,uncons,skip,skipWhile,splitAt,takeandtruncateread only the elements that they need.Arrayfunction or a shared algorithm and creates the queue withImmutableQueue.Create. The elements go to the front stack, so the queue dequeues them without a reversal.List.sort*is.sort,sortDescending,sortWithandsortByDescendinguse theSeqsorts of FSharp.Core, which share the stable sort ofList, so a comparison or a projection that throws throws whatListthrows.sortByandsortWithCompareruse LINQ, which computes the keys first, asList.sortBydoes. A queue of one element is returned without a call of the projection or the comparer.lengthis O(n). A function that checks a count against the length reads the elements first.ofSeqreturns anImmutableQueuewithout a copy.enqueueMany,uncons,tryUncons,tryTail,skipLast,takeLast,sortWithComparer,ofReadOnlySpanandofSpan. The struct partial patternsConsandNilreplace the complete patternCons | Nilof 1.0.0.IQueue/IQueue.fsiandIQueue.fs: 115 functions overIImmutableQueue<'T>: the functions ofQueuewithout the creation functions, plusclear.Clear ()plusEnqueue, orDequeue.appendenqueues the second queue into the first one, so the collection that receives the elements keeps its implementation (D14), also when the first queue is empty.Queue.appendreturns the second queue then, because both areImmutableQueues.Queue. So areconcatandtranspose, because their inputs can have different implementations.Queue/QueueTypes.fsholds theQueue<'T>andIQueue<'T>abbreviations, because the modules are root modules (D2).Queue.fsiandQueue.fsopenFSharp.Collections.ImmutableafterSystem.Collections.Generic, whoseQueue<T>would shadow the abbreviation.Listfunction throws: the type, the message and the parameter name of this module. This includes the quirks ofList:exists2andforall2can return before they check the lengths.exceptdoes not read the items to exclude for an empty queue.take,skipandsplitAtthrow for a count beyond the length.InvalidOperationException, and so does the projection ofsortByDescending, whichListcalls inside the comparison. The projection ofsortBythrows as it is.nullinput throwsArgumentNullException.seqparameter rejectsnulland a default flat list given as a sequence, a boxed struct that the BCL would fail on while it enumerates it, withArgumentNullExceptionthat names the parameter.checkNotNullOrDefaultSeqdoes it as the first statement ofofSeq,concat,transpose,enqueueManyandexceptofQueueandIQueue(which has noofSeq) and ofyield!andforinqueue { }. The shared contracts of feat:IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31 coverofSeq,concat,transposeandexcept; the tests here addenqueueMany, the covariant sequences (a default flat list of strings as objects) and the expression.Option.Queue(14 functions) andOption.IQueue(13) repeat the functions withvoption.Tuple.Queue(18) andTuple.IQueue(17) repeat the functions with struct tuples. The per-element tuple producers share one algorithm with the default module; the other functions delegate and repack.queue { }builds a queue. An immutable queue has no builder, so the expression collects its elements in aResizeArrayand creates the queue at the end. The first element of the expression leaves the queue first.yield!rejects anullsequence, array or queue and a default flat list given as a sequence withArgumentNullExceptionfor the parameteritems, andforrejects anullsequence and a default flat list for the parametersource; the checks run whenyield!orforis evaluated.QueueCE.fsicarries the signatures and the documentation, andqueuehas itsvalinComputationExpressions.fsi.Queuecompile under PascalCase names, where 1.0.0 compiled the F# names, such asheadandisEmpty. The queue operations use the .NET vocabulary in compiled code, in all six modules.head,tailandunconscompile asPeek,DequeueandPeekDequeue;tryHead,tryTailandtryUnconsasTryPeek,TryDequeueandTryPeekDequeue;enqueueandenqueueManyasEnqueueandEnqueueMany. The other functions use the compiled names of FSharp.Core.Helper/Queues.fs:Queuesholds the helpers that the queue modules share:ofFreshArray,ofBuffer,lastMatchand the walksfirstMatch,firstChoiceandfront. It is the only addition of this pull request to theHelperfolder.ImmutableQueuewith its struct enumerator, also when anIQueuefunction receives it, and any other queue with its own enumerator, which it disposes.IEnumerator<'T>type argument. An internal struct puts the struct enumerator of the queue behind that interface, and the loop calls it without boxing.IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31:Snapshot,ListChecks,ListArrays,Arrays,RaiseandErrorStrings, as the list modules do.sortDescendingandsortByDescendingof both modules callDescendingSortsof feat:IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31, which avoids the boxes of the inlineSeqsorts.Lengths.queueUpToof feat:IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31, only as far as a check needs. feat:IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31 adds it without a caller, so the tests ofQueueandIQueueare the first to cover it.net10.0, bytes per call):Queue.tryFind,tryPickandfindIndex: 0 B.IQueue.tryFind,tryPickandfindIndexof anImmutableQueue: 0 B. The interface enumerator ofImmutableQueuewould allocate 48 B.Seq.sortDescendingandSeq.sortByDescending, and then withDescendingSortsof feat:IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31, which the modules call (median of 200 calls, five alternating processes, one process for each sort):Queue.sortDescending: 7.6 MB and 2.2 ms, then 0.74 MB and 0.90 ms.Queue.sortByDescending: 7.6 MB and 2.3 ms, then 0.74 MB and 1.2 ms.IQueue.sortDescending: 7.8 MB and 1.9 ms, then 1.0 MB and 1.0 ms.IQueue.sortByDescending: 7.8 MB and 2.2 ms, then 1.0 MB and 1.2 ms.Queue.sortWith, is the same in both builds: 0.74 MB, 0.91 and 0.89 ms.Queue.fs.README.md,docsSrc/index.mdanddocsSrc/Explanations/Background.md:queue { }forQueueand a new row forIQueue.Tests: 9320 per target framework, 1488 of them new (768 for
Queue, 720 forIQueue; #31 has 7832).Queuederives the sequence-like (461 tests), creation (31) and stable-sort (15) contracts.IQueuederives the sequence-like (461) and stable-sort (15) contracts, over immutable queues upcast to the interface. The sequence-like and creation contracts of feat:IndexedListandIIndexedListmodules with the vocabulary of the FSharp.CoreListmodule #31 include the sequence inputs: a null sequence and a default flat list given as a sequence toofSeq,concat,transposeandexcept. The stable-sort contract also compares the exceptions of the sorts withList, checks the order in whichsortByDescendingcalls the projection, and checks that the descending sorts of 10 000 integers allocate less than 3 MB, which the inlineSeqfunctions do not meet.QueueOps.paramNamemaps the parameter names of theListexceptions to the names of the queue modules, also in"list1, list2"and in thelist.[i]names ofList.transpose.Queue(199) andIQueue(206) have one row for each case of a table: 114 and 115 functions that reject anullqueue, 12 and 10 results that are the input itself, 38 functions ofIQueuethat keep the implementation, and 9 and 9 functions that reject a default flat list given as a sequence (also a default flat list of strings as objects, which a type test misses, and an empty queue forexcept). The tests ofQueuealso have 6 sorts of one element, 4 functions of two queues that walk a common prefix and 14 complete messages. They also cover:ImmutableQueue;queue { }(20 tests, withyield!andforover a null sequence and a default flat list) and both namespaces (19, 17, 23 and 21 tests).IImmutableQueueimplementation proves thatIQueuekeeps it. A second one counts its enumerators: the searches,takeandtruncateread it only as far as they need and dispose the enumerator.Types of changes
What types of changes does your code introduce to FSharp.Collections.Immutable?
Put an
xin the boxes that applyThese changes break existing code:
Queueworks onQueue<'T>(ImmutableQueue<'T>) instead ofIQueue<'T>, andenqueue,tailandfilterreturn aQueue<'T>. The functions for anyIImmutableQueue<'T>are in the newIQueuemodule.tryFind,tryFindIndexandtryPickreturnvoption, andpickandtryPicktake functions that returnvoption.Cons | Nil, whoseConscase matched a reference tuple of the head and the tail, is replaced by the struct partial patternsCons, which matches a struct tuple, andNil.headandtailof an empty queue throwArgumentException, asList.headandList.taildo, where they threwInvalidOperationException.iter2of queues of different lengths throwsArgumentException, asList.iter2does, where it stopped at the shorter queue.forallandaverageBytake the queue. They returned a function of a sequence before.Queue.clearis removed: useQueue.empty, orIQueue.clearto keep the implementation of an interface value.Enqueue,PeekandDequeueand the other PascalCase compiled names, where it saw the F# names of 1.0.0, such asenqueue,headandtail.Checklist
Put an
xin the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.Further comments
Queues: they need an internal struct over the struct enumerator ofImmutableQueue. An inline loop with member constraints cannot use it: a constraint resolves only to members that are public everywhere, andIEnumerator<'T>has twoCurrentproperties. One loop over the interface enumerator would be simpler, at 48 B per call.exists,forall,takeWhile,skipand a few others look alike in both modules.Queuewalks the struct enumerator and builds anImmutableQueue;IQueuekeeps the implementation of its input.takeandtruncateread one element beyond the count. It tells them whether to return the input itself.List: the modules report the unusual names ofListexactly:"source"forexactlyOne,average,averageBy,containsand therandom*functions;"count"forinit.🤖 Generated with Claude Code