Repository navigation
feat: FlatList module with the vocabulary of the FSharp.Core Array module - #26
Merged
Merged
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Default-value exception handling is inconsistent, and countBy introduces avoidable linear auxiliary storage.
3 open issues
What changed in this PR
Rewrites FlatList around FSharp.Core’s Array vocabulary, implementing ADR 0001 on the foundation from #25.
Changes:
- Adds expanded collection operations, Option/Tuple variants, builder helpers and
flatList { }. - Introduces shared contract tests and FlatList-specific coverage.
- Removes legacy APIs and documents breaking changes.
| File | Description |
|---|---|
| tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj | Registers new tests and contracts. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs | Tests reference-tuple overrides. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs | Tests defaults, storage sharing and extras. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs | Tests option overrides. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs | Connects FlatList to shared contracts. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListContractTests.fs | Instantiates collection contracts. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs | Tests computation-expression behavior. |
| tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs | Tests builder operations. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs | Defines contract operation records. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs | Tests transformations. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs | Tests sorting. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs | Tests searches. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs | Tests random operations. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs | Tests grouping and ranges. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs | Tests folds and aggregation. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs | Completes contracts with conversions. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs | Tests properties and access. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs | Tests collection creation. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs | Tests indexed operations. |
| tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs | Shares oracle and exception helpers. |
| src/FSharp.Collections.Immutable/Seq.fs | Removes cross-conversion module. |
| src/FSharp.Collections.Immutable/Helper.fs | Adds array algorithms and exception helpers. |
| src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj | Registers replacement modules. |
| src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi | Declares reference-tuple APIs. |
| src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs | Implements reference-tuple APIs. |
| src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi | Declares option APIs. |
| src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs | Implements option APIs. |
| src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs | Implements computation-expression builder. |
| src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs | Adds mutable-builder helpers. |
| src/FSharp.Collections.Immutable/FlatList.fs | Removes legacy implementation. |
| src/FSharp.Collections.Immutable/ComputationExpressions.fs | Exposes flatList. |
| README.md | Lists computation-expression support. |
| docsSrc/index.md | Updates collection overview. |
| docsSrc/Explanations/Background.md | Updates collection support table. |
| CHANGELOG.md | Records additions and breaking changes. |
Review effort: Balanced
Merged
4 of 6 tasks
xperiandri
added a commit
that referenced
this pull request
Oct 18, 2026
… `Array` module Review of #26: - `ofSeq` rejects a default flat list passed as a sequence with `ArgumentNullException`, as every function rejects a default flat list (D6); a valid flat list is still returned without a copy. - `collect` rejects a default flat list that the mapping returns with `ArgumentNullException`, as `concat` does. - `countBy` keeps one counter per key instead of every element: a dictionary over keys wrapped in a struct, which admits `null` keys, with F# structural equality and the order of first occurrence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 18, 2026
xperiandri
force-pushed
the
chore/foundation
branch
from
October 18, 2026 13:03
1c689c6 to
54f9dba
Compare
xperiandri
force-pushed
the
feat/flat-list
branch
2 times, most recently
from
October 18, 2026 15:35
2b5179a to
9d2526e
Compare
4 of 6 tasks
xperiandri
force-pushed
the
chore/foundation
branch
from
October 18, 2026 17:33
83e75cf to
e524b23
Compare
…` module
Rewrites `FlatList` after ADR 0001 in `FlatList/FlatList.fsi` and
`FlatList/FlatList.fs`: 144 functions in the canonical category order,
each documented in the signature file.
- Most functions unwrap the underlying array without a copy, call the
`Array` function and wrap its fresh result, so that they keep the
semantics and the exceptions of `Array`. The range functions repeat
the checks of `Array.take`, `skip` and `splitAt` and slice, which
copies nothing for an empty or a whole range.
- `try*` functions return `voption`, tuples are struct tuples, and the
`Option` and `Tuple` namespaces repeat those functions with options
and reference tuples; per-element tuple producers share one algorithm
that takes the tuple constructor.
- `FlatListBuilder` works on the builder, and `flatList { }` collects
its elements in a builder.
- `Helper.fs` gets the FSharp.Core exception messages and helpers that
the modules mirroring `List` will reuse.
- The legacy `FlatList.fs` and the `Seq` module are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `SeqOps`, `SeqCreationOps` and `IndexedOps` record the functions of a module; the `SeqLike` facet is a chain of abstract classes, one file per category, and `SeqCreation` and `Indexed` complete it. - Expected values come from the FSharp.Core `List` module over the enumerated elements, expected exceptions from the mirrored module. - `FlatList` derives one test class per facet and adds its own tests: default values, shared storage, the builder, the computation expression and the `Option` and `Tuple` namespaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `Array` module F# ignores `[<CompiledName>]` on a module: the modules compile as `FlatList`, `Option.FlatList`, `Tuple.FlatList` and `FlatListBuilder`, as the legacy modules of the released package did. The attributes are removed, and the documentation references use the real IDs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `Array` module Review of #26: - `ofSeq` rejects a default flat list passed as a sequence with `ArgumentNullException`, as every function rejects a default flat list (D6); a valid flat list is still returned without a copy. - `collect` rejects a default flat list that the mapping returns with `ArgumentNullException`, as `concat` does. - `countBy` keeps one counter per key instead of every element: a dictionary over keys wrapped in a struct, which admits `null` keys, with F# structural equality and the order of first occurrence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `Array` module Follows the reworked ADR 0001 (D2, D5): - `FlatList`, `Option.FlatList`, `Tuple.FlatList`, `FlatListBuilder` and `ComputationExpressions` are the root modules of their files. - The `FlatList<'T>` abbreviation moves to `FlatList/FlatListTypes.fs`, which needs no signature file. - `Helper.fs` caches the comparer of F# generic comparison per type (`DefaultComparer<'T>`); `binarySearch` and the builder sort use it, so a call no longer creates a comparer for a type that is not primitive. - The cref of `FastGenericComparer` in the remarks of `binarySearch` names the method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FlatList` Test methods carry no categories: the classes carry them (ADR 0001, D8). `FlatListOps` is the root module of its file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `Array` module
Two crefs name the right members: `ImmutableArray<T>.Empty` is a field, and the ID of
`ofBuilder` writes the nested generic builder as `ImmutableArray.Builder{``0}`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FlatList` The tests compare arrays and take their cases as data rows, so every case runs and reports as a test of its own (708 tests per target framework instead of 267): - The ops records observe a collection with its `toArray`, and results of another element type are arrays. `SeqOps.ToArray` is both the observation and the `toArray` under test; `toList` gets a field and a test of its own, because it no longer observes the collections. `Concat` and `Transpose` take the sequence that the module takes; `Average`, `Unzip` and `Unzip3` take arrays. - `SeqContractBase.ForEach` is gone: a test takes its input from `DynamicData (nameof Inputs.All, typeof<Inputs>)` and builds the collection and its elements with `Create`. Loops inside a test become rows from a source next to it (`InputsAndSizes`, `InputsAndCounts`, ...) or `DataRow` constants. - A row holds printable data, because MSTest names a row by its values: predicates are the cases of `ElementPredicate` and `PairPredicate`, whose `Matches` the test takes, and the tables of `FlatListTests` give their names as rows. - Expectations come from the FSharp.Core `Array` functions; exceptions still come from the `Array` or the `List` oracle, the latter on `List.ofArray`. Lists remain only as the oracles, as the input of `ofList`, the list case of `ofSeq` and the list inputs of the functions under test. - Tuples and value options that hold arrays are compared by their parts. - The empty collection of `lastIndexOf` and `lastIndexOfWith` is a test of its own instead of a check repeated by every row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `Array` module A `<summary>` and a `<remarks>` stay on one line each only when both are short (up to 60 characters of text); otherwise both span several lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FlatList` A `<summary>` and a `<remarks>` stay on one line each only when both are short; the results of the ops records that hold arrays of arrays are `int[][]`, so that a type has one postfix level at most. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `Array` module
- `FlatListBuilder.fsi`, `FlatListCE.fsi` and `ComputationExpressions.fsi` carry the
signatures and the XML documentation of their files, compiled right before them: every
public binding file has a signature file (ADR 0001, D2). `ComputationExpressions` groups
its instances under the `Creation` banner, so that `check-order.fsx` checks it as well.
- `flatList { yield! array }` rejects a default flat list with `ArgumentNullException`, as
the functions of `FlatList` do (D6), instead of the `NullReferenceException` of the
builder; a default flat list given as a sequence is rejected as `FlatList.ofSeq` rejects
it, instead of the `InvalidOperationException` of its enumeration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FlatList` `yield!` of a default flat list, given as a flat list or as a sequence, throws `ArgumentNullException` for the parameter `items`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 of 6 tasks
… `Array` module A default flat list given as a sequence is rejected with `ArgumentNullException`, as it is when it is given as a flat list (D6). It converts to a sequence without a check and fails with `InvalidOperationException` when a function enumerates it, so every function that takes a sequence calls the new `checkNotDefaultSeq` of `Helper.fs` next to its `null` check: - `concat` and `transpose` for the outer sequence; - `except` for the items to exclude, also when the input is empty, and before the input is checked, as FSharp.Core checks them; - `insertManyAt` for the values, after the check of the index, because FSharp.Core enumerates the values only then; - `FlatListBuilder.addRange`, and `for` of the `flatList` expression; `yield!` of a sequence uses the helper instead of its own copy of the check. The signatures document the exceptions. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FlatList` A default flat list given as a sequence throws `ArgumentNullException` that names the parameter: `concat`, `transpose`, `insertManyAt` and `except` are the rows of one test, and `addRange` and `for` have a test each. Rows and tests pin the order of the checks as well: `insertManyAt` checks its source first and its index before the values, and `except` checks the items before the array and also for an empty input, as the FSharp.Core functions do. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
commented
Oct 18, 2026
… `Array` module
ADR 0001, D1 promises every applicable function of the `Array` module and the wrappers of
the BCL members, so the functions that the rewrite had dropped come back:
- `sub list startIndex count` mirrors `Array.sub` (compiled `GetSubArray`): the same checks in
the same order and the same exceptions. It wraps `ImmutableArray.Slice`, which copies
unless the range is empty or the whole flat list.
- `sortRange`, `sortRangeWith` and `sortRangeWithComparer` wrap `Sort (index, count, comparer)`;
`sortRange` compares with F# generic comparison. They are not stable and throw what the BCL
member throws for a range out of range.
- `remove` and `removeWith` remove the first element that equals a value, `removeMany` and
`removeManyWith` one element for each of the given values (`Remove` and `RemoveRange`); the
input itself comes back when nothing matches. The remarks of `removeManyAt` and `filter`
say that they are `RemoveRange (index, length)` and, with a negated predicate, `RemoveAll`.
The check of a sequence is one call, `checkNotNullOrDefaultSeq` of `Helper.fs`, which throws
`ArgumentNullException` for a null sequence and for a default flat list given as one. It
replaces `checkNotDefaultSeq` in `concat`, `transpose`, `except`, `insertManyAt`,
`FlatListBuilder.addRange` and the `yield!` and `for` of `flatList { }`, and it is used by
`removeMany` and `removeManyWith`. A null sequence reports the parameter of the function:
`values` for `insertManyAt` (`Array.insertManyAt` says `source`) and `addRange` (the BCL says
`items`), and `for` over a null sequence throws `ArgumentNullException` instead of
`NullReferenceException`.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FlatList` Tests of the restored functions and of the checks of a sequence, as rows: - `sub` against `Array.sub` for every range of the contract inputs, and for the ranges that `Array.sub` rejects with the exception compared by `ExceptionAssert.Same` (its `array` is the `list` of `FlatList.sub`); a default flat list; the empty and the whole range. - The three range sorts against `Array.sub`, `Array.sortWith` and `Array.concat` over the sorted part and the parts that stay, for ranges out of range and for ranges that need no sorting. - The removals by value against oracles written by hand (the first equal element, one element for each value), in the default equality and with a case-insensitive comparer. - A null sequence and a default flat list given as a sequence, in the tables of the inputs that a function rejects; `concat`, `transpose`, `except` and `insertManyAt` compare a null sequence with the `Array` oracle, and the order of the checks of `insertManyAt` is pinned. - The pipelines of the tests that start with a flat list use LINQ `Select`, which reads the struct without a copy, and end with `Seq.toArray`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`remove`, `removeMany`, `removeManyWith` and `removeWith` are added. `sub` takes the meaning of `Array.sub`, which changes its arguments, and `sortRange`, `sortRangeWith` and `sortRangeWithComparer` stay. `removeAll`, `removeAllWith` and `removeRange` are removed with their replacements named. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… `Array` module The flat list parameter of the eight functions that came back has the name that the neighbours use, so that named arguments and the documentation read alike in the module: - `sub` and the range sorts (`sortRange`, `sortRangeWith`, `sortRangeWithComparer`) call it `array`, as the `Array` functions do. `Array.sub` names it `array`, so the check of a default flat list reports the same parameter, and the message of the check of the range names `array.Length`, word for word as `Array.sub` does. - `remove`, `removeWith`, `removeMany` and `removeManyWith` call it `source`, as `removeAt`, `removeManyAt`, `insertAt`, `insertManyAt` and `updateAt` do. The signature file, its parameter documentation and the names that the checks report follow. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FlatList` The tests follow the new parameter names: `array` for `sub` and the range sorts, `source` for the removals by value. `sub` is compared with `Array.sub` without a mapping of the parameter name now, so the `subParamName` helper goes away. The rows of `removeMany` and `removeManyWith` for a default flat list together with default items say "default source", as the rows of `insertManyAt` do. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The changelog writes `sub array startIndex count`, with the name that the flat list parameter has now. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… `Array` module The gap analysis found members of `ImmutableArray<'T>` and of its builder that no function wrapped, and the checks of a default flat list given as a sequence of a base type: - `FlatList` gets `indexOfRange`, `indexOfRangeWith`, `lastIndexOfRange` and `lastIndexOfRangeWith` (the overloads of `IndexOf` and `LastIndexOf` with a range, which the `indexFrom` and `indexRange` families of 1.0.0 were), `replace` and `replaceWith` (`Replace`), and `binarySearchRange`, `binarySearchRangeWithComparer` and `binarySearchWithComparer` (`ImmutableArray.BinarySearch`). The range search without a comparer uses the cached comparer of F# generic comparison, as `binarySearch` does. The BCL calls the count of a range `length`, so the two range searches check the index and the count first, with the type and the message of the BCL, and name the parameter `count`. - `FlatListBuilder` gets `indexOfRange`, `indexOfRangeWith`, `lastIndexOfRange`, `lastIndexOfRangeWith`, `insertManyAt`, `removeAll`, `removeMany`, `removeManyAt`, `removeManyWith`, `removeWith`, `replace`, `replaceWith`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer` and `blit`. They return the builder, except `removeWith`, which reports the removal as `remove` does. `replace` and `replaceWith` leave the builder as it is when no element equals the old value, as the BCL does, and the documentation says so. `removeManyAt` and `blit` name the count `count` and the target `target` and `targetIndex` where the BCL says `length`, `destination` and `destinationIndex`, and they keep the order of the checks, the exceptions and the messages of the BCL. `insertManyAt` checks the index before the values, as `InsertRange` does. - The parameter of a sequence is checked by `checkNotNullOrDefaultSeq`: a default `FlatList<string>` seen as a `seq<obj>`, by the covariance of `seq`, is an `ImmutableArray<string>`, and the type test for `ImmutableArray<obj>` did not see it, so it failed in the enumerator with `InvalidOperationException`. The type test stays the first step. For any other object the check asks for the generic type and reads `IsDefault` through reflection. The check is inline, because the public members of the computation expression call it, and an inline member cannot call an internal function. `ofSeq` takes the same path. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FlatList` Tests of the new functions, as rows: - `FlatListSearchTests` runs the searches of a range of the flat list and of its builder on every valid range of the inputs, against a search written by hand, with the comparer that ignores the case for the functions with a comparer. The ranges that the BCL rejects run against the BCL member (`ExceptionAssert.Same`), with the parameter names of the functions. The binary searches of a range run on every valid range of sorted integers, in ascending and in descending order, and on strings that a comparer sorts; the ordinal order of the range search without a comparer, `null` for the default comparer, and a comparer that throws are pinned. - `replace` and `replaceWith` against a first-occurrence oracle, the exception for a value that no element equals, and a new flat list for equal values. - The functions of the builder against `Array` and the oracles: `insertManyAt`, `removeManyAt`, `removeMany`, `removeManyWith`, `removeWith`, `removeAll`, `replace`, `replaceWith`, the range sorts and `blit`. The ranges out of range run against the BCL member, with the parameter names of the functions, and a test proves that every function that changes the builder returns it. - A default `FlatList<string>` seen as a `seq<obj>` runs through `ofSeq`, `insertManyAt`, `except`, `removeMany`, `removeManyWith`, the functions of the builder, `yield!` and `for`, with the parameter names, and a flat list of strings that is not default must pass. - `sub` also compares the second line of the message, which names the arguments and the length. The oracles move to `FlatListOracles` and the assertion of a rejected null to `FlatListAssert`, so that the tests of the flat list and of the builder share them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The changelog lists the new functions of `FlatList` and of `FlatListBuilder` and names the replacement of each removed function: `indexOf` and its counterparts for the `index` and `lastIndex` functions, `indexOfRange` and its counterparts for the `indexRange` and `indexFrom` functions, `removeMany` for `removeAll` and `removeManyAt` for `removeRange`, with the change of the default equality from F# structural equality to `EqualityComparer<'T>.Default`. The nested `FlatList.Builder` module is now `FlatListBuilder`. The entries were checked against the published 1.0.0. `Seq`, the module of conversions between the collections, was never released and the `ImmutableArray` module alias cannot be used from another assembly, so neither is listed as removed. `unfold` is new, so it is not listed as changed, `sortWithComparer` and `tryTail` are not new, and `take` and `splitAt` threw in 1.0.0: the entry says what changed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… `Array` module The documentation of the searches of a range said that the start index must be less than the length. That is how the BCL of .NET 8 and .NET 10 behaves: the forward search of .NET 11 accepts an empty range at a start index that equals the length. The remarks now say that the function throws what the member throws, and name the versions of the behavior that they describe. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…FlatList` The comments of the rows of the searches of a range say which BCL they describe: the rows with an empty range at a start index that equals the length throw on .NET 8 and .NET 10, the targets of the project, and not in the forward search of .NET 11. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri
added a commit
that referenced
this pull request
Oct 20, 2026
…` module (#26) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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
FlatList(ImmutableArray<'T>) after ADR 0001 (#23), on top of the foundation merged in #25.FlatList/FlatList.fsiandFlatList.fs: the root moduleFSharp.Collections.Immutable.FlatListwith 161 functions in the canonical category order, each documented in the signature file with its parameters, result, exceptions and complexity. The set is theFlatListcolumn of ADR Appendix A. It has every function of the FSharp.CoreArraymodule that applies to an immutable array. It has the BCL wrappers (add,asMemory,asSpan,binarySearch,binarySearchRange,binarySearchRangeWithComparer,binarySearchWithComparer,indexOf,indexOfRange,indexOfRangeWith,indexOfWith,lastIndexOf,lastIndexOfRange,lastIndexOfRangeWith,lastIndexOfWith,ofReadOnlySpan,ofSpan,remove,removeMany,removeManyWith,removeWith,replace,replaceWith,sortRange,sortRangeWith,sortRangeWithComparer,sortWithComparer, the builder functions,isDefault,isDefaultOrEmpty). It also has the extrasskipLast,takeLast,tryTail,buildandupdate.array, as inArray.insertAt,insertManyAt,removeAt,removeManyAtandupdateAtcall itsource, as their FSharp.Core 10 counterparts do. So do the removals by value:remove,removeWith,removeManyandremoveManyWith.sub array startIndex counthas the meaning, the exceptions and the messages ofArray.sub. It wrapsImmutableArray<'T>.Slice, so an empty or a whole range copies nothing. It compares the count with the length minus the start index. A huge count cannot overflow that check, as it does inArray.sub.remove item sourceandremoveWith comparer item sourcewrapRemove. They remove the first element that equals the item. They return the input itself when no element equals it.removeMany items sourceandremoveManyWith comparer items sourcewrapRemoveRange. Each value removes the first remaining element that equals it. A value without an equal element removes nothing.removeManychecks the flat list before the items, asRemoveRangedoes.removeandremoveManycompare inEqualityComparer<'T>.Default, asindexOfdoes. TheWithvariants take the equality comparer.containscompares in F# equality. The two differ fornan, whichremoveremoves, and for arrays, whichremovecompares by reference.sortRange index count array,sortRangeWith comparison index count arrayandsortRangeWithComparer comparer index count arraywrapSort(index, count, comparer). The elements outside the range keep their places. The sort is not stable.sortRangecompares in F# generic comparison, assortdoes. A range of at most one element, or a range that is sorted, returns the input itself. A range out of range throwsArgumentOutOfRangeExceptionforindexorcount, as the BCL does.indexOfRange startIndex count value array,indexOfRangeWith,lastIndexOfRangeandlastIndexOfRangeWithwrap the overloads ofIndexOfandLastIndexOfthat take a range. They return an index of the flat list, not of the range, and -1 when no element is equal. A search from the back starts atstartIndexand goes down overcountelements. They throw what the BCL members throw. On .NET 8 and .NET 10 that includes an empty range at a start index that equals the length, and an empty flat list accepts only the range 0, 0. They replace theindexFromandindexRangefamilies of 1.0.0, which the changelog now says.replace oldValue newValue arrayandreplaceWith comparer oldValue newValue arraywrapReplace. They replace the first element that equals the old value. They throwArgumentExceptionwhen no element does, and they return a new flat list also when the new value equals the old one.binarySearchWithComparer comparer value array,binarySearchRange index count value arrayandbinarySearchRangeWithComparer comparer index count value arraywrapImmutableArray.BinarySearch.binarySearchRangecompares asbinarySearchdoes, through the cachedDefaultComparer<'T>. The other two take the comparer, andnullstands forComparer<'T>.Default. A comparer that throws givesInvalidOperationExceptionwith the exception inside. The BCL calls the count of a rangelength. The functions checkindexandcountfirst, with the type and the message of the BCL, and name the parametercount. A range beyond the end stays with the BCL.FlatList/FlatListTypes.fsholds theFlatList<'T>abbreviation (D2): a root module cannot declare a type of the namespace, and a file of abbreviations needs no signature file (a signature file alone fails with FS0240). Every other file with public API has a signature file:FlatListBuilder.fsi,FlatListCE.fsiandComputationExpressions.fsiincluded.ImmutableCollectionsMarshal), call theArrayfunction and wrap its fresh result, so they keep the semantics and the exceptions ofArray(D5, D6). The range functions repeat the checks ofArray.take,skipandsplitAtand slice, which copies nothing for an empty or a whole range.binarySearchand every sort use F# generic comparison throughDefaultComparer<'T>inHelper.fs, one cached comparer per type: a comparer created per call breaks the same-comparer fast paths of the BCL for tuples.try*functions returnvoption, tuples are struct tuples (D3). The root modulesOption.FlatListandTuple.FlatListrepeat those functions in the namespacesFSharp.Collections.Immutable.Optionand.Tuplewith options and reference tuples; opening both,Optionlast, gives the signatures of theArraymodule. Per-element tuple producers share one algorithm that takes the tuple constructor, so neither variant pays a second pass.FlatListBuilderworks onImmutableArray<'T>.Builderwith 36 functions. The functions that change the builder return it.removeWithreturns whether it removed an element, asremovedoes.flatList { }collects into a builder and drains it without a copy when it is full (D7).insertManyAt,removeAll,removeMany,removeManyAt,removeManyWith,removeWith,replace,replaceWith, the range sortssortRange,sortRangeWithandsortRangeWithComparer, andblit. They wrap the members of the builder with the same arguments as the functions of the flat list, in the same words. The range sorts are not stable.replaceandreplaceWithleave the builder as it is when no element equals the old value, asImmutableArray<'T>.Builder.Replacedoes, whereFlatList.replacethrows.removeManyAtandblitname the parameterscount,targetandtargetIndex, where the BCL sayslength,destinationanddestinationIndex, and they check the ranges in the order and with the messages of the BCL.seq<'T>parameter has one check,checkNotNullOrDefaultSeq.nulland a default flat list given as a sequence throwArgumentNullExceptionfor that parameter inconcat,transpose,except,insertManyAt,ofSeq,removeMany,removeManyWith,addRange,yield!andfor. A default flat list given as a sequence is a boxed value, notnull. Without the check, it fails later with another exception.seq: a defaultFlatList<string>asseq<obj>is anImmutableArray<string>, which the type test forImmutableArray<obj>does not see. The type test stays the first step and allocates nothing. For any other object, the check asks for the generic type and readsIsDefaultthrough reflection. It is inline, because the public members of the computation expression call it, and an inline member cannot call an internal function.Helper.fsgets the FSharp.Core message texts, exact replicas of itsDetailedExceptionshelpers, the shared tuple-constructor algorithms,DefaultComparer<'T>andcheckNotNullOrDefaultSeq.FlatList.fsand theSeqmodule of cross conversions are removed.RemovedandChangedname only what 1.0.0 had: theSeqmodule andunfoldwere never released.Tests (2447 per target framework, 2442 of them new): the shared contract bases of the sequence-like modules (
SeqLikeas a chain of abstract classes, one file per category, plusSeqCreationandIndexed) compute their expectations with FSharp.CoreArrayover the enumerated elements. They compare every exception with the mirrored FSharp.Core function. Every case is a data row:DynamicDataover the sharedInputsand the sources of each contract,DataRowfor constants, so each of the 703 cases of the former loops runs and reports as a test of its own. The tests compare arrays withCollectionAssert.FlatListderives one class per facet and adds tests for default values, shared storage, the extras, the builder, the computation expression and both namespaces. The categories are on the test classes only (D8).sub, the range sorts and the removals have 214 tests. They compare the results withArray.sub, withArray.sortWithon the range and with a first-occurrence oracle, in default and in case-insensitive equality. They also check the exceptions, the parameter names and the cases that return the input itself. The test ofsubcompares the second line of the message too, which names the arguments and the length.FlatListSearchTestscover the searches of a range of the flat list and of its builder, and the binary searches. Each search runs on every valid range of its inputs, against a search written by hand. The ranges that the BCL rejects run against the BCL member, with the parameter names of the functions. The searches with a comparer run with the comparer that ignores the case.replace,replaceWithand the functions of the builder compare the results with the oracles ofFlatListOracles: the removal and the replacement of the first equal element,Array.insertManyAt,Array.removeManyAtandArray.blit. The ranges out of range run against the BCL member. The functions that change the builder are proved to return it.removeMany, the items to exclude before the array inexcept, the index before the values ininsertManyAtof the builder. A defaultFlatList<string>asseq<obj>runs throughofSeq,insertManyAt,except,removeMany,removeManyWith,addRange,yield!andfor, and a flat list of strings that is not default must pass.Types of changes
What types of changes does your code introduce to FSharp.Collections.Immutable?
Put an
xin the boxes that applytry*functions returnvoption, tuples are struct tuples,takeandsplitAtthrowInvalidOperationExceptionfor a count beyond the length, a default flat list throwsArgumentNullException,subtakes the flat list first and has the meaning ofArray.sub, and the functions listed under Removed in the changelog are gone.removeAll,removeAllWithandremoveRangeare among them:removeMany,removeManyWithandremoveManyAtdo their job, andfilterwith the negated predicate removes by a predicate. So are theindexandlastIndexfamilies:indexOf,indexOfRangeand their counterparts replace them, and they compare inEqualityComparer<'T>.Default, where the removed functions compared in F# structural equality. The nestedFlatList.Buildermodule is nowFlatListBuilder.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
Array.concatdereferences anullelement array without a check;FlatList.concatandtransposethrowArgumentNullExceptionfor a default element, as D6 requires for a defaultFlatList.F# ignores
[<CompiledName>]on modules, so the modules compile under their F# names (FlatList,Option.FlatList), as the legacy 1.0.0 modules did.🤖 Generated with Claude Code