Repository navigation
docs: add ADR 0001 for the per-collection module rewrite - #23
Open
xperiandri wants to merge 12 commits into
Open
xperiandri wants to merge 12 commits into
xperiandri wants to merge 12 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Several documented contracts conflict with underlying collection behavior, and the ADR is future-dated.
4 open findings
What changed in this PR
Adds ADR 0001 to define the planned per-collection module rewrite.
Changes:
- Defines API, layout, implementation, and testing decisions.
- Introduces Frozen collection support and value-type defaults.
- Documents per-collection support matrices and delivery plan.
| File | Description |
|---|---|
docsSrc/Explanations/ADR_0001_Collection_Modules.md |
Adds the architectural decision record and API matrices. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
xperiandri
force-pushed
the
docs/adr-collection-modules
branch
from
October 11, 2026 15:06
2d34b91 to
abd1c71
Compare
xperiandri
force-pushed
the
docs/adr-collection-modules
branch
from
October 17, 2026 18:32
abd1c71 to
f86b3bd
Compare
xperiandri
force-pushed
the
docs/adr-collection-modules
branch
from
October 17, 2026 22:58
10b17c5 to
75c0c24
Compare
1 of 6 tasks
xperiandri
force-pushed
the
docs/adr-collection-modules
branch
from
October 18, 2026 00:51
e72f4c1 to
845174b
Compare
This was referenced Oct 18, 2026
Merged
Records the decisions behind the rewrite of the collection modules: one folder and signature file per collection, `FrozenMap`/`FrozenSet` support, `voption` and struct-tuple defaults with `Option`/`Tuple` override namespaces, category-based function order, BCL/LINQ-first implementation, shared contract tests, one pull request per collection, and the function-by-collection support matrices. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…0001 Moves both items from follow-ups into the decision: a nested `FlatList.Parallel` module mirroring `Array.Parallel` through the zero-copy bridge, delivered as its own pull request after `FlatList`, and `alternateLookup`/`tryAlternateLookup` on `FrozenMap`/`FrozenSet` under `#if NET10_0_OR_GREATER`, delivered inside the Frozen pull requests. The appendices gain the matching matrix rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- `IndexedList`, `Queue`, `Stack` and their interface modules get the struct partial patterns `Cons` and `Nil` instead of the multi-case `(|Cons|Nil|)`, which allocates a `Choice` per match (ADR 0002 §5). - Sorts avoid culture-sensitive comparers such as `Comparer<string>.Default`; `Comparer<'T>.Default` stays usable where it agrees with F# comparison, as ADR 0002 §3 uses it. - Operation test categories come from `nameof`, never from string literals; a contract base takes the name from any module. - `EquatableArray` is named in D1, in the computation expressions, in the pull request chain as `feat/equatable-array`, and in Appendix A; step 1 of ADR 0002 may ship from `main` before the chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in ADR 0001 The review of the HashMap pull request (#30) changes three parts of the design: - D2: a file with one module declares it as the root module of the file; the type abbreviations of a folder move to `XTypes.fsi`/`XTypes.fs`, because a root module cannot declare a type of its namespace. Modules carry no `[<CompiledName>]`. - D12 (new): the comparers that a map or a set keeps come only through one `Comparable*` struct per collection, whose members are the creation functions with the comparers applied. It replaces the `*WithComparer(s)`, `*WithKeyComparer` and `with*Comparer(s)` functions. - D13 (new): `ofArray`, `ofList` and `ofSeq` of the maps are static extension members in an `[<AutoOpen>]` `XExtensions` module, overloaded for `KeyValuePair`, reference tuples and struct tuples. `ofKeyValuePairs` and the `Tuple` creation functions go; `setItems` takes struct tuples like `addRange`. The ADR also records what the implementation found: the explicit type parameters of the alternate lookups (FS0687), the `comparison` constraint and the cached default comparer of the sorted collections, the O(n) `maxKeyValue` of `SortedMap`, and why `keys`/`values` return different types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D8 put an operation category on every test method through `TestCategory (nameof …)`. The test category rules forbid categories written as strings and put the attribute of the examined component on the class (#32), so the categories of the contract and collection tests are the `TestCategories.fs` attributes on the test classes, and test methods carry none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aluePair` map functions in ADR 0001 - D12: the structs that create collections with comparers are named after what they hold, `CustomComparers*` for maps and `CustomComparer*` for sets. The modules create them with `forComparers`/`forKeyComparer` (maps) and `forComparer` (sets), in the Creation category. - D13: `addRange` and `setItems` take `KeyValuePair`s, the shape of every dictionary. The nested submodules `Tuple` and `ValueTuple` of the map modules hold them for reference and struct tuples; after `open FSharp.Collections.Immutable.Tuple` the submodules still resolve. They leave the `Tuple` namespace. - D2: the types file of a folder is `XTypes.fs` without a signature file, because a signature file without an implementation file fails with FS0240. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
docs/adr-collection-modules
branch
from
October 18, 2026 13:03
d3f830a to
4c822eb
Compare
… `rev` in ADR 0001 - D2: `tryHead`/`tryTail`/`tryUncons` compile as `TryPeek`/`TryDequeue`/`TryPeekDequeue` for `Queue` and as `TryPeek`/`TryPop`/`TryPeekPop` for `Stack`; `enqueueMany` as `EnqueueMany` and `consMany` as `PushMany`, so compiled code has one .NET vocabulary. - D5: a member gives way to a measured faster path that keeps the contract. `IndexedList.rev` copies, reverses and builds with `CreateRange`, about 2.5 times faster than `ImmutableList.Reverse` at the same allocation; Appendix A notes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D2 listed `CustomComparersX`/`CustomComparerX` after the module, but `forComparers`, `forKeyComparer` and `forComparer` of the module return the struct, so the struct compiles first; the extension module follows both, because its members are those of the struct with the default comparers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 18, 2026
…computation expressions, and D14 in ADR 0001 D2: the comparer structs and the extension modules live in `X.CustomComparers.fs` (`X.CustomComparer.fs` for sets) and `X.Extensions.fs`, with `DependentUpon` on the files of the module, so that Visual Studio shows them under it. Every file with public API has a signature file, the builders and the computation expressions included; only `XTypes.fs` has none. D14: where FSharp.Core and the BCL disagree in an edge case, the modules follow .NET: which of two equal elements `intersect` and `union` keep, the implementation that `append` keeps, repeated keys in `ofSeqBy`, values that a value comparer finds equal, comparer identity of the frozen collections, and the default value of a comparer struct. `IsProperSupersetOf` of the immutable sets counts repeated elements, against its documentation; the modules answer with set semantics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The relations of the set modules take two sets, so the defect of `IsProperSupersetOf` shows when the second set holds elements that the first set's comparer finds equal; a sequence with repetitions is the BCL's own example of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A probe of `ImmutableDictionary` and `ImmutableSortedDictionary` with case-insensitive comparers shows that both keep the stored key and value when the value comparer finds the new value equal, in `SetItem`, `SetItems`, `AddRange` and the builder indexers, and that both replace the key and the value for another value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ImmutableHashSet.Union` always keeps the element of the first set. `ImmutableSortedSet.Union` keeps the element of the larger set when the other operand is a sorted set with the same comparer, because it then adds the smaller set to the larger one; `SortedSet.union` follows the member that it wraps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
main
branch
2 times, most recently
from
October 20, 2026 16:16
5d56ba0 to
9d7316e
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
Adds
docsSrc/Explanations/ADR_0001_Collection_Modules.md, an architecture decision record for rewriting the collection modules before any code lands.It decides:
.fsisignature files;FrozenMap/FrozenSetmodules, with the alternate lookup onnet10.0;FlatList.Parallelmodule;voptionresults and struct tuples by default, withOption/Tupleoverride namespaces;CustomComparers*) and per set (CustomComparer*), which the modules create withforComparers/forKeyComparer/forComparer;KeyValuePairs, reference tuples and struct tuples;addRange/setItemsoverKeyValuePairs withTuple/ValueTuplesubmodules;It also fixes:
System.Linqfirst and keeps the FSharp.Core semantics of exceptions and sort stability;The appendices are the function-by-collection support matrices for sequence-like collections, maps and sets. fsdocs publishes the page next to
Background.Types of changes
What types of changes does your code introduce to FSharp.Collections.Immutable?
Put an
xin the boxes that applyDocumentation only: the ADR describes breaking changes that the following pull requests will make, but this one changes no code.
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
The matrices are the contract every following pull request implements, so wrong cells are cheapest to fix here.
The fixes for both Copilot reviews are folded into the commits that introduced the text:
Optionnamespace is defined by representation, with the exact FSharp.Core signature only where FSharp.Core has the function; theTuplenamespace holds every function whose signature mentions a struct tuple;FlatListranges copy, becauseImmutableArray.Sliceallocates unless the range is empty or whole;IndexedListranges cost time and memory in proportion to their length, becauseImmutableList.GetRangebuilds a new tree;Stackconstructor pushes from the end, because the BCLCreate/CreateRangeleave the last element on top;FlatListunstable likeArray.sort*, the others stable through LINQOrderBy), all sorts using F# structural comparison;mapandchoosegive the result the default comparer of the new type;ReadOnlySpan<char>(F# consumes, but cannot declare,allows ref struct).Each point was checked at runtime against
System.Collections.Immutable10.0.8 and FSharp.Core 10.Three later commits redesign parts after the review of #30.
mainnow say.CustomComparers*(maps) andCustomComparer*(sets), and the modules create them withforComparers/forKeyComparer/forComparer.addRange/setItemstakeKeyValuePairs, withTuple/ValueTuplesubmodules for reference and struct tuples.XTypes.fswithout a signature file. A signature file alone fails with FS0240.A prototype with F# 10 checked these points:
Tuple/ValueTuplesubmodules resolve afteropen FSharp.Collections.Immutable.Tuple;🤖 Generated with Claude Code