Skip to content

feat: Queue and IQueue modules with the vocabulary of the FSharp.Core List module - #33

Open
xperiandri wants to merge 4 commits into
feat/indexed-listfrom
feat/queue
Open

xperiandri wants to merge 4 commits into
feat/indexed-listfrom
feat/queue

Conversation

@xperiandri

@xperiandri xperiandri commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Proposed Changes

Rewrites the legacy Queue module for ImmutableQueue<'T> and adds the interface module IQueue (IImmutableQueue<'T>) after ADR 0001 (#23). This pull request is stacked on #31 (feat/indexed-list).

  • Queue/Queue.fsi and Queue.fs: 124 functions, in the canonical category order. They are the Queue column of ADR Appendix A, over the elements in the order in which they leave the queue. Where List names a list, the parameters name the queue: queue, queue1, queue2, queue3, queues.
    • The members of ImmutableQueue come first (D5): Peek, Dequeue, Enqueue, IsEmpty. head, tail, uncons, skip, skipWhile, splitAt, take and truncate read only the elements that they need.
    • The searches from the front, the folds and the iterations walk the struct enumerator of the queue.
    • Everything else copies to an array, calls the Array function or a shared algorithm and creates the queue with ImmutableQueue.Create. The elements go to the front stack, so the queue dequeues them without a reversal.
    • Every sort is stable, as List.sort* is. sort, sortDescending, sortWith and sortByDescending use the Seq sorts of FSharp.Core, which share the stable sort of List, so a comparison or a projection that throws throws what List throws. sortBy and sortWithComparer use LINQ, which computes the keys first, as List.sortBy does. A queue of one element is returned without a call of the projection or the comparer.
    • The queue does not store its length, so length is O(n). A function that checks a count against the length reads the elements first.
    • Results that keep every element return the input itself. ofSeq returns an ImmutableQueue without a copy.
    • The extras that 1.0.0 did not have are enqueueMany, uncons, tryUncons, tryTail, skipLast, takeLast, sortWithComparer, ofReadOnlySpan and ofSpan. The struct partial patterns Cons and Nil replace the complete pattern Cons | Nil of 1.0.0.
  • IQueue/IQueue.fsi and IQueue.fs: 115 functions over IImmutableQueue<'T>: the functions of Queue without the creation functions, plus clear.
    • Results of the same element type keep the implementation of the input: Clear () plus Enqueue, or Dequeue.
    • append enqueues 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.append returns the second queue then, because both are ImmutableQueues.
    • Results of another element type are a Queue. So are concat and transpose, because their inputs can have different implementations.
  • Queue/QueueTypes.fs holds the Queue<'T> and IQueue<'T> abbreviations, because the modules are root modules (D2). Queue.fsi and Queue.fs open FSharp.Collections.Immutable after System.Collections.Generic, whose Queue<T> would shadow the abbreviation.
  • Exceptions (D6): every function throws what the FSharp.Core List function throws: the type, the message and the parameter name of this module. This includes the quirks of List:
    • The functions of two and three queues call their callback on the common prefix before they throw.
    • exists2 and forall2 can return before they check the lengths.
    • except does not read the items to exclude for an empty queue.
    • take, skip and splitAt throw for a count beyond the length.
    • A comparison that throws in a sort gives an InvalidOperationException, and so does the projection of sortByDescending, which List calls inside the comparison. The projection of sortBy throws as it is.
    • A null input throws ArgumentNullException.
  • Sequence inputs (D6): every seq parameter rejects null and a default flat list given as a sequence, a boxed struct that the BCL would fail on while it enumerates it, with ArgumentNullException that names the parameter. checkNotNullOrDefaultSeq does it as the first statement of ofSeq, concat, transpose, enqueueMany and except of Queue and IQueue (which has no ofSeq) and of yield! and for in queue { }. The shared contracts of feat: IndexedList and IIndexedList modules with the vocabulary of the FSharp.Core List module #31 cover ofSeq, concat, transpose and except; the tests here add enqueueMany, the covariant sequences (a default flat list of strings as objects) and the expression.
  • Option and Tuple namespaces (D3): Option.Queue (14 functions) and Option.IQueue (13) repeat the functions with voption. Tuple.Queue (18) and Tuple.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.
  • Computation expression (D7): queue { } builds a queue. An immutable queue has no builder, so the expression collects its elements in a ResizeArray and creates the queue at the end. The first element of the expression leaves the queue first. yield! rejects a null sequence, array or queue and a default flat list given as a sequence with ArgumentNullException for the parameter items, and for rejects a null sequence and a default flat list for the parameter source; the checks run when yield! or for is evaluated. QueueCE.fsi carries the signatures and the documentation, and queue has its val in ComputationExpressions.fsi.
  • Compiled names: the functions of Queue compile under PascalCase names, where 1.0.0 compiled the F# names, such as head and isEmpty. The queue operations use the .NET vocabulary in compiled code, in all six modules. head, tail and uncons compile as Peek, Dequeue and PeekDequeue; tryHead, tryTail and tryUncons as TryPeek, TryDequeue and TryPeekDequeue; enqueue and enqueueMany as Enqueue and EnqueueMany. The other functions use the compiled names of FSharp.Core.
  • Helper/Queues.fs:
  • Measured allocations (Release, net10.0, bytes per call):
    • Queue.tryFind, tryPick and findIndex: 0 B.
    • IQueue.tryFind, tryPick and findIndex of an ImmutableQueue: 0 B. The interface enumerator of ImmutableQueue would allocate 48 B.
    • The descending sorts of 10 000 integers with the inline Seq.sortDescending and Seq.sortByDescending, and then with DescendingSorts of feat: IndexedList and IIndexedList modules with the vocabulary of the FSharp.Core List module #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.
      • The control, Queue.sortWith, is the same in both builds: 0.74 MB, 0.91 and 0.89 ms.
  • Removed files: the legacy Queue.fs.
  • Documentation: the changelog, and the collection tables of README.md, docsSrc/index.md and docsSrc/Explanations/Background.md: queue { } for Queue and a new row for IQueue.

Tests: 9320 per target framework, 1488 of them new (768 for Queue, 720 for IQueue; #31 has 7832).

  • The tests compare arrays and take their cases as data rows. A row is a name or a value that prints in the name of the test, so a failing case does not stop the others.
  • Queue derives the sequence-like (461 tests), creation (31) and stable-sort (15) contracts. IQueue derives the sequence-like (461) and stable-sort (15) contracts, over immutable queues upcast to the interface. The sequence-like and creation contracts of feat: IndexedList and IIndexedList modules with the vocabulary of the FSharp.Core List module #31 include the sequence inputs: a null sequence and a default flat list given as a sequence to ofSeq, concat, transpose and except. The stable-sort contract also compares the exceptions of the sorts with List, checks the order in which sortByDescending calls the projection, and checks that the descending sorts of 10 000 integers allocate less than 3 MB, which the inline Seq functions do not meet.
  • QueueOps.paramName maps the parameter names of the List exceptions to the names of the queue modules, also in "list1, list2" and in the list.[i] names of List.transpose.
  • The tests of Queue (199) and IQueue (206) have one row for each case of a table: 114 and 115 functions that reject a null queue, 12 and 10 results that are the input itself, 38 functions of IQueue that 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 for except). The tests of Queue also have 6 sorts of one element, 4 functions of two queues that walk a common prefix and 14 complete messages. They also cover:
    • the first-in, first-out order and queues whose elements lie in both stacks of ImmutableQueue;
    • the order of the callbacks before an exception;
    • the patterns, queue { } (20 tests, with yield! and for over a null sequence and a default flat list) and both namespaces (19, 17, 23 and 21 tests).
  • A custom IImmutableQueue implementation proves that IQueue keeps it. A second one counts its enumerators: the searches, take and truncate read it only as far as they need and dispose the enumerator.
  • Reflection proves the compiled names of all six modules. A test with strings proves that the sorts use F# generic comparison, which is ordinal.

Types of changes

What types of changes does your code introduce to FSharp.Collections.Immutable?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

These changes break existing code:

  • Queue works on Queue<'T> (ImmutableQueue<'T>) instead of IQueue<'T>, and enqueue, tail and filter return a Queue<'T>. The functions for any IImmutableQueue<'T> are in the new IQueue module.
  • tryFind, tryFindIndex and tryPick return voption, and pick and tryPick take functions that return voption.
  • The complete active pattern Cons | Nil, whose Cons case matched a reference tuple of the head and the tail, is replaced by the struct partial patterns Cons, which matches a struct tuple, and Nil.
  • head and tail of an empty queue throw ArgumentException, as List.head and List.tail do, where they threw InvalidOperationException. iter2 of queues of different lengths throws ArgumentException, as List.iter2 does, where it stopped at the shorter queue.
  • forall and averageBy take the queue. They returned a function of a sequence before.
  • Queue.clear is removed: use Queue.empty, or IQueue.clear to keep the implementation of an interface value.
  • C# sees Enqueue, Peek and Dequeue and the other PascalCase compiled names, where it saw the F# names of 1.0.0, such as enqueue, head and tail.

Checklist

Put an x in 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.

  • Build and tests pass locally
  • I have added tests that prove my fix is effective or that my feature works (if appropriate)
  • I have added necessary documentation (if appropriate)

Further comments

  • The walks of Queues: they need an internal struct over the struct enumerator of ImmutableQueue. An inline loop with member constraints cannot use it: a constraint resolves only to members that are public everywhere, and IEnumerator<'T> has two Current properties. One loop over the interface enumerator would be simpler, at 48 B per call.
  • Shared code: only the helpers are shared. The bodies of exists, forall, takeWhile, skip and a few others look alike in both modules. Queue walks the struct enumerator and builds an ImmutableQueue; IQueue keeps the implementation of its input.
  • take and truncate read one element beyond the count. It tells them whether to return the input itself.
  • Parameter names of List: the modules report the unusual names of List exactly: "source" for exactlyOne, average, averageBy, contains and the random* functions; "count" for init.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 18, 2026 15:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

Comment thread src/FSharp.Collections.Immutable/Queue/IQueue.fs Outdated
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 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
xperiandri requested a balanced review from Copilot October 18, 2026 19:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The broad breaking API rewrite and specialized traversal paths warrant human validation of behavior and test results.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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
xperiandri requested a balanced review from Copilot October 18, 2026 20:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The broad breaking API rewrite and strict FSharp.Core compatibility guarantees require final maintainer validation across target frameworks.

No open issues 🎉

Review effort: Balanced

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
xperiandri requested a balanced review from Copilot October 19, 2026 10:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The broad breaking API rewrite, exception contracts, and custom enumerator paths warrant human review and build/test validation.

No open issues 🎉

Review effort: Balanced

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 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 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 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 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 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 and others added 3 commits October 20, 2026 18:13
…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>
…ll the default modules by their short names

Each of these files opens `FSharp.Collections.Immutable`, and inside `FSharp.Collections.Immutable.Tuple.IQueue` the name
`IQueue` reaches the default module, also for the functions that both modules define, so the full namespace in front
of it added nothing. The compiled code is the same: the opcodes and the called methods of every method of both builds
match one to one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants