diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8373ad55..029a7817 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
semantics and exceptions. Its `tryFind`, `tryFindIndex` and `tryPick` return `voption`, `choose` and `tryPick` take
functions that return `voption`, and `groupBy`, `partition`, `partitionWith` and `zip` return struct tuples; the
`Parallel` modules of the `Option` and `Tuple` namespaces repeat them with options and reference tuples.
+* `IndexedList` has every function of the FSharp.Core `List` module that applies to an immutable list, with its semantics,
+ parameter names and exceptions and with stable sorts, plus `binarySearch`, `binarySearchRange`,
+ `binarySearchRangeWithComparer`, `binarySearchWithComparer`, `blit`, `indexOf`, `indexOfRange`, `indexOfRangeWith`,
+ `indexOfWith`, `lastIndexOf`, `lastIndexOfRange`, `lastIndexOfRangeWith`, `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`,
+ `removeMany`, `removeManyWith`, `revRange`, `skipLast`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer`,
+ `sortWithComparer`, `sub`, `takeLast`, `tryFindIndexBackRange`, `tryFindIndexRange`, `tryUncons`, `uncons` and the struct
+ partial active patterns `Cons` and `Nil`.
+* The functions of `IndexedList` that wrap a member of `ImmutableList<'T>` throw what the member throws, with the names of
+ their own parameters. `removeMany` removes one element for each value, in `EqualityComparer<'T>.Default`. The functions
+ that take a range check it before they call the member: the members accept a range whose end overflows `Int32.MaxValue`,
+ `BinarySearch` accepts a range beyond the end, and `LastIndexOf` and `FindLastIndex` accept a last element at or after
+ the end and answer with the index of another element.
+ 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, which the members would reject with `InvalidOperationException` while they
+ enumerate it; the `IIndexedList` and `IndexedListBuilder` modules and the `indexedList { }` expression do the same.
+* `sortRange`, `sortRangeWith` and `sortRangeWithComparer` are stable, as the other sorts of `IndexedList` are, and do not
+ wrap the unstable `ImmutableList<'T>.Sort(index, count, comparer)`.
+* The `IIndexedList` module has the functions of `IndexedList` for `IImmutableList<'T>`, without the creation and builder
+ functions, `binarySearch*`, `blit`, `revRange`, `sortRange*`, `sub` and `tryFindIndex*Range`, plus `clear`. Its functions
+ keep the implementation of the input list where the result has the element type of the input, and return an
+ `IndexedList` otherwise. `IIndexedList<'T>` is the abbreviation of `IImmutableList<'T>`, as `IndexedList<'T>` is the
+ abbreviation of `ImmutableList<'T>`.
+* `max`, `maxBy`, `min` and `minBy` of `IndexedList` and `IIndexedList` are inline, as the ones of `List` are, so that the
+ comparison is compiled for the type of the elements and boxes nothing. `exists2`, `forall2` and `compareWith` stop at the
+ first pair that decides. `concat` and `collect` share the nodes of an indexed list of at least 16 elements instead of
+ copying its elements, and `skip`, `take`, `sub`, `splitAt`, `chunkBySize`, `windowed` and the range sorts read a range in
+ O(log n + count).
+* The `FSharp.Collections.Immutable.Option` and `FSharp.Collections.Immutable.Tuple` namespaces have `IndexedList` and
+ `IIndexedList` modules that repeat the functions that use value options or struct tuples with options and reference
+ tuples.
+* The `IndexedListBuilder` module works on the builder of an indexed list, and `indexedList { }` builds an indexed list.
+ The module removes and replaces values (`remove`, `removeWith`, `removeAll`, `removeMany`, `removeManyAt`,
+ `removeManyWith`, `replace`, `replaceWith`), inserts values (`insertManyAt`), reverses, sorts and searches ranges
+ (`revRange`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer`, `indexOfRange`, `indexOfRangeWith`,
+ `lastIndexOfRange`, `lastIndexOfRangeWith`, `binarySearch`, `binarySearchWithComparer`,
+ `binarySearchRangeWithComparer`) and copies elements into an array (`blit`). `replace` throws `ArgumentException` for a
+ value that no element equals, as the builder of an indexed list does.
### Changed
@@ -44,6 +81,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `FlatList.sub` has the meaning of `Array.sub`: `sub array startIndex count` returns `count` elements from `startIndex`.
It was `sub start stop list`.
* The nested `FlatList.Builder` module is now the `FlatListBuilder` module.
+* The `ImmutableList` module is `IndexedList`. It works on `ImmutableList<'T>` (`IndexedList<'T>`), where most functions of
+ 1.0.0 took any `IImmutableList<'T>` and the ones that change a list returned an `IImmutableList<'T>`; the functions for
+ `IImmutableList<'T>` are in the new `IIndexedList` module. 1.0.0 compiled the module as `ImmutableListModule`, with the
+ F# names of its functions, such as `isEmpty`, and `IndexedList` compiles them to PascalCase names, such as `IsEmpty`.
+* `IndexedList.ofList` takes an F# list, where `ofList` took any sequence. The mapping of `collect` returns an indexed
+ list and `concat` takes indexed lists, where they took any sequences. `append` takes two indexed lists, where it took a
+ list and a sequence: `append list (ofSeq items)` or `insertManyAt (length list) items list` appends a sequence.
+* `IndexedList`: `tryFind`, `tryFindIndex`, `tryHead`, `tryItem`, `tryLast` and `tryTail` return `voption`; `choose` and
+ `pick` take choosers that return `voption`; `splitAt` returns a struct tuple; and the generator of `unfold` returns a
+ `voption` of a struct tuple with the element before the state, as in `List.unfold`, where it returned an `option` of a
+ tuple with the state before the element.
+* `IndexedList`: every function throws what the FSharp.Core `List` function of the same name throws, and names the
+ parameters of `List`, such as `source` in `ofSeq`, `itemsToExclude` in `except` and `list1` in `append`. `head`, `last`
+ and `tail` of an empty list, and `item` and `removeAt` for an index outside the list, throw `ArgumentException`, where
+ they threw `ArgumentOutOfRangeException`. `take` and `splitAt` throw `InvalidOperationException` for a count beyond the
+ length and `ArgumentException` for a negative count, and `skip` and `truncate` return the list and an empty list for a
+ negative count, where all four threw `ArgumentOutOfRangeException`. `forall2` throws `ArgumentException` for lists of
+ different lengths when every pair of the shorter list satisfies the predicate, where it returned `true`. `contains` and
+ `ofBuilder` throw `ArgumentNullException` for a `null` input, where they threw `NullReferenceException`. A default
+ `FlatList` given as a sequence to `ofSeq`, `concat` and `except` throws `ArgumentNullException`, where it threw
+ `InvalidOperationException` (`except` of an empty list returned).
+* `IndexedList.skipWhile` and `takeWhile` do what their names say, as the functions of `List` do. In 1.0.0, `skipWhile`
+ returned the elements before the first one that fails the predicate, and `takeWhile` returned the elements from it on.
+* `IndexedList.except` removes every element equal to an excluded item and the duplicates of the other elements, as
+ `List.except` does, where the 1.0.0 `except` removed one element for each item, as `removeMany` does now.
+* `IndexedList.contains` compares in F# structural equality, where 1.0.0 compared in `EqualityComparer<'T>.Default`:
+ arrays with equal elements are equal, and `nan` is not equal to itself. `remove` and `replace`, and `indexOf`,
+ `lastIndexOf`, `indexOfRange` and `lastIndexOfRange`, which replace `index`, `lastIndex`, `indexRange` and
+ `lastIndexRange`, compare in `EqualityComparer<'T>.Default`, where 1.0.0 compared in F# structural equality
+ (`HashIdentity.Structural`), so elements that are arrays, or whose type defines `Equals` differently from F# equality,
+ compare differently.
+* The functions that replace renamed functions of 1.0.0 differ from them in these cases. `insertAt`, `insertManyAt` and
+ `updateAt` throw `ArgumentException` for an index outside the list, where `insert`, `insertRange` and `withItem` threw
+ `ArgumentOutOfRangeException`. `removeManyAt` throws `ArgumentException` for a range that is not inside the list and
+ removes nothing for a negative count or a range whose end overflows `Int32.MaxValue`, where `removeRange` threw
+ `ArgumentOutOfRangeException`. `lastIndexOf` returns -1 for an empty list, where `lastIndex` threw
+ `ArgumentOutOfRangeException`. `lastIndexOfRange` throws `ArgumentOutOfRangeException` for a start index at or after the
+ end of the list, where `lastIndexRange` accepted it and could answer with an index that the list does not have. `rev`
+ and `revRange` throw `ArgumentNullException` for a `null` list, where `reverse` and `reverseRange` threw
+ `NullReferenceException`.
+* `IndexedList.rev` builds the reversed list from a reversed copy of the elements. At 100 000 integers it takes about 3 ms
+ where `reverse`, which called `ImmutableList.Reverse`, took about 23 ms, and it allocates 5.2 MB where `reverse`
+ allocated 4.8 MB.
### Removed
@@ -61,6 +141,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
compared in F# structural equality. The removed functions did not remove the elements that satisfy a predicate: use
`filter` with the negated predicate for that.
* `FlatList.removeRange`: use `removeManyAt`.
+* The functions of the `ImmutableList` module that `IndexedList` has under another name and with the same arguments:
+ `lastIndex` to `lastIndexOf`, `lastIndexWith` to `lastIndexOfWith`, `indexRange` to `indexOfRange`, `indexRangeWith` to
+ `indexOfRangeWith`, `lastIndexRange` to `lastIndexOfRange`, `lastIndexRangeWith` to `lastIndexOfRangeWith`, `insert` to
+ `insertAt`, `insertRange` to `insertManyAt`, `removeRange` to `removeManyAt`, `exceptWith` to `removeManyWith`, `reverse`
+ to `rev`, `reverseRange` to `revRange` and `withItem` to `updateAt`. Only `revRange index count list` differs: it takes
+ the index and the count as two arguments, where `reverseRange` took them as a tuple.
+* `ImmutableList.index` and `indexWith`: use `IndexedList.indexOf` and `indexOfWith`. `ImmutableList.indexFrom`,
+ `indexFromWith`, `lastIndexFrom` and `lastIndexFromWith`: use `IndexedList.indexOfRange`, `indexOfRangeWith`,
+ `lastIndexOfRange` and `lastIndexOfRangeWith`. `indexFrom index item list` is
+ `indexOfRange index (length list - index) item list`, and `lastIndexFrom index item list` is
+ `lastIndexOfRange index (index + 1) item list`. `index`, `indexWith`, `indexFrom` and `indexFromWith` returned a function
+ that took the list a second time, as in `index item list list`; the functions that replace them return the index.
+* `ImmutableList.clear`: use `IndexedList.empty`, or `IIndexedList.clear` to keep the implementation of an interface value.
+* `ImmutableList.skipUntil` and `takeUntil`: use `IndexedList.skipWhile` and `takeWhile` with the negated predicate. They
+ returned each other's result in 1.0.0: `skipUntil p list` returned what `takeWhile (not << p) list` returns, and
+ `takeUntil p list` returned what `skipWhile (not << p) list` returns.
+* `ImmutableList.filterFold`: it has no replacement.
+* The `IndexedSeq` module and the `IIndexedSeq<'T>` abbreviation of `IReadOnlyList<'T>`: use `IReadOnlyList<'T>`, whose
+ `Count` and indexer replace `IndexedSeq.length` and `IndexedSeq.item`. `IndexedSeq.check` needs no replacement, because
+ every function of the list modules throws `ArgumentNullException` for a `null` list.
## [1.0.0] - 2020-02-23
diff --git a/README.md b/README.md
index 950057e1..239ef746 100644
--- a/README.md
+++ b/README.md
@@ -13,14 +13,14 @@ Please also join the [F# Open Source Group](http://fsharp.github.com)
| Module | .NET type | Computation expression |
|---|---|---|
| `FlatList` | `ImmutableArray<'T>` | `flatList { }` |
-| `ImmutableList` | `ImmutableList<'T>` | – |
+| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` |
+| `IIndexedList` | `IImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
| `SortedSet` | `ImmutableSortedSet<'T>` | – |
-| `IIndexedSeq` | `IReadOnlyList<'T>` | – |
[Documentation](https://fsprojects.github.io/FSharp.Collections.Immutable/)
diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md
index 822820fa..21f275db 100644
--- a/docsSrc/Explanations/Background.md
+++ b/docsSrc/Explanations/Background.md
@@ -27,14 +27,14 @@ While F# has its own built-in immutable collections, System.Collections.Immutabl
| Module | .NET type | Computation expression |
|---|---|---|
| `FlatList` | `ImmutableArray<'T>` | `flatList { }` |
-| `ImmutableList` | `ImmutableList<'T>` | – |
+| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` |
+| `IIndexedList` | `IImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
| `SortedSet` | `ImmutableSortedSet<'T>` | – |
-| `IIndexedSeq` | `IReadOnlyList<'T>` | – |
---
diff --git a/docsSrc/Tutorials/Getting_Started.md b/docsSrc/Tutorials/Getting_Started.md
index eea423e8..4d82dde2 100644
--- a/docsSrc/Tutorials/Getting_Started.md
+++ b/docsSrc/Tutorials/Getting_Started.md
@@ -22,7 +22,7 @@ open FSharp.Collections.Immutable
``` F#
// Create an immutable list
-let list1 = ImmutableList.ofSeq [1; 2; 3]
+let list1 = IndexedList.ofSeq [1; 2; 3]
// Create an immutable hash set
let set1 = HashSet.ofSeq ["a"; "b"]
diff --git a/docsSrc/index.md b/docsSrc/index.md
index 276ad8fe..8fd0c476 100644
--- a/docsSrc/index.md
+++ b/docsSrc/index.md
@@ -9,14 +9,14 @@ F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us
| Module | .NET type | Computation expression |
|---|---|---|
| `FlatList` | `ImmutableArray<'T>` | `flatList { }` |
-| `ImmutableList` | `ImmutableList<'T>` | – |
+| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` |
+| `IIndexedList` | `IImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
| `SortedSet` | `ImmutableSortedSet<'T>` | – |
-| `IIndexedSeq` | `IReadOnlyList<'T>` | – |
---
diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs
index 6db0b059..972f77ec 100644
--- a/src/FSharp.Collections.Immutable/ComputationExpressions.fs
+++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs
@@ -4,3 +4,5 @@ module FSharp.Collections.Immutable.ComputationExpressions
// ---- Creation ----
let flatList = FlatListCE ()
+
+let indexedList = IndexedListCE ()
diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi
index c0086e56..e588c81b 100644
--- a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi
+++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi
@@ -19,3 +19,17 @@ module FSharp.Collections.Immutable.ComputationExpressions
/// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200.
///
val flatList : FlatListCE
+
+/// Builds an indexed list.
+///
+///
+/// let squares = indexedList {
+/// yield 0
+/// for i in 1..3 do
+/// yield i * i
+/// yield! [ 100; 200 ]
+/// }
+///
+/// Evaluates to an indexed list of 0, 1, 4, 9, 100 and 200.
+///
+val indexedList : IndexedListCE
diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj
index 293de773..f66316ea 100644
--- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj
+++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj
@@ -33,6 +33,13 @@
+
+
+
+
+
+
+
@@ -44,10 +51,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/src/FSharp.Collections.Immutable/Helper/Collectors.fs b/src/FSharp.Collections.Immutable/Helper/Collectors.fs
new file mode 100644
index 00000000..2e544de7
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/Collectors.fs
@@ -0,0 +1,93 @@
+///
+/// The accumulators that build an from many collections, for
+/// the functions that concatenate them (ADR 0001, D5).
+///
+///
+///
+/// An indexed list is a balanced tree. Appending another indexed list to a builder joins the tree of the appended list to
+/// the tree of the builder: it takes O(log n) and allocates a few nodes, whatever the length of the appended list, and the
+/// result shares the nodes of the appended list, as any immutable tree shares its nodes. Appending any other collection
+/// builds a subtree from its elements.
+///
+///
+/// The join costs about a microsecond, so it pays only for an indexed list of some length. The functions of this module
+/// measure how long: with 100 000 elements in all and fresh collections of 1, 3, 10, 30, 100 and 1000 elements, copying
+/// the elements into a buffer and building the tree once, as
+///
+/// does, is 1.2 to 1.8 times as fast as joining for collections of 1, 3 and 10 elements, and joining is 1.4 times as fast
+/// for 30 and 2.4 times for 100 and 1000. Concatenating 100 indexed lists of 1000 elements takes 0.1 to 0.4 ms with joins
+/// and 3 to 7 ms with copies, and allocates 76 KB instead of 5.8 MB; one indexed list of 100 000 elements takes 4 µs
+/// instead of 3 to 7 ms.
+///
+///
+module internal FSharp.Collections.Immutable.Collectors
+
+open System.Collections.Immutable
+
+///
+/// The least length of an indexed list that the accumulator joins to its result instead of copying it.
+///
+///
+/// The break-even of the measurements in the remarks of the module lies between 10 and 30 elements.
+///
+[]
+let private JoinedLength = 16
+
+///
+/// Appends collections to an indexed list under construction: it copies the short ones, and joins the long indexed lists as
+/// they are.
+///
+/// The type of the elements.
+///
+///
+/// The copied elements wait in a buffer until a long indexed list comes or the result is asked for, and then the builder
+/// takes them in one piece, so that a run of short collections costs what
+///
+/// costs, and a long indexed list costs a join. The elements keep the order in which the collections were added.
+///
+///
+/// It exists because a builder alone is 1.6 to 1.8 times slower than the buffer for collections of one element, and the buffer
+/// alone copies every element of a long indexed list that the join would have shared (see the remarks of
+/// ). Functions that add the elements one at a time, such as
+/// ,
+/// need neither: the buffer is 2.3 times as fast as
+/// for 100 000 elements.
+///
+///
+[]
+type ListCollector<'T> () =
+
+ let builder = ImmutableList.CreateBuilder<'T>()
+ let pending = ResizeArray<'T>()
+
+ /// Moves the copied elements into the builder.
+ member private _.Flush () =
+ if pending.Count > 0 then
+ builder.AddRange pending
+ pending.Clear ()
+
+ ///
+ /// Appends the elements of a collection to the result.
+ ///
+ /// The collection, which can be an indexed list.
+ ///
+ /// is .
+ ///
+ ///
+ /// An indexed list of at least 16 elements is joined to the result, which then shares its nodes; any other collection
+ /// is copied.
+ ///
+ member this.Add (items : seq<'T>) =
+ match items with
+ | :? ImmutableList<'T> as list when list.Count >= JoinedLength ->
+ this.Flush ()
+ builder.AddRange list
+ | _ -> pending.AddRange items
+
+ ///
+ /// The indexed list of the elements that were added.
+ ///
+ /// The elements of the collections, in the order in which the collections were added.
+ member this.ToImmutable () =
+ this.Flush ()
+ builder.ToImmutable ()
diff --git a/src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs b/src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs
new file mode 100644
index 00000000..8f1182ed
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs
@@ -0,0 +1,77 @@
+///
+/// The descending sorts of the modules that mirror : stable sorts
+/// in the order of F# generic comparison, with the comparer that the type caches, and without the boxes of the inline
+/// sorts of (ADR 0001, D5).
+///
+///
+///
+/// and
+///
+/// are inline. Compiled into a generic function, they compare with the generic operator, which boxes the elements, or the
+/// keys, of a value type in every comparison: at 10 000 integers, a sort allocates 7.5 MB, eleven times as much as a sort
+/// with a comparison function.
+///
+///
+/// The sorts here call
+/// ,
+/// which is not inline and runs the stable sort of , with the comparer that
+/// caches for the type. The comparer compares
+/// as the generic operator does, so the comparisons, their order and the exceptions that they throw are those of
+///
+/// and
+///
+/// (ADR 0001, D6): a comparison or a projection that throws makes the sort throw an
+/// that holds the exception.
+///
+///
+module internal FSharp.Collections.Immutable.DescendingSorts
+
+///
+/// The elements of a sequence in descending order of F# generic comparison. The sequence sorts the elements stably
+/// when it is read.
+///
+/// The elements, which the caller has checked for .
+/// The elements, equal elements in their order.
+///
+///
+/// The comparer compares the second element with the first, as
+///
+/// does. The sort allocates the arrays
+/// of FSharp.Core and nothing per comparison, for a type that has a primitive comparer.
+///
+///
+/// It exists instead of
+/// ,
+/// which boxes the elements of a value type in every comparison (see the
+/// remarks of the module).
+///
+///
+let sortDescending (source : seq<'T>) =
+ let comparer = DefaultComparer<'T>.Instance
+ // The annotations keep the arguments non-nullable: IComparer<'T>.Compare takes nullable ones, and the lambda
+ // would get them, and the sequence of the result an element type that is nullable too
+ Seq.sortWith (fun (left : 'T) (right : 'T) -> comparer.Compare (right, left)) source
+
+///
+/// The elements of a sequence in descending order of a projection, in F# generic comparison. The sequence sorts the
+/// elements stably when it is read.
+///
+/// The function that returns the key of an element.
+/// The elements, which the caller has checked for .
+/// The elements, those with equal keys in their order.
+///
+///
+/// The projection runs in the comparison, twice for each comparison and for the second element first, as it does in
+/// ;
+/// the keys are not computed beforehand.
+///
+///
+/// It exists instead of
+/// ,
+/// which boxes the keys of a value type in every comparison (see the remarks
+/// of the module).
+///
+///
+let sortByDescending (projection : 'T -> 'Key) (source : seq<'T>) =
+ let comparer = DefaultComparer<'Key>.Instance
+ Seq.sortWith (fun (left : 'T) (right : 'T) -> comparer.Compare (projection right, projection left)) source
diff --git a/src/FSharp.Collections.Immutable/Helper/Lengths.fs b/src/FSharp.Collections.Immutable/Helper/Lengths.fs
new file mode 100644
index 00000000..342d2eb2
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/Lengths.fs
@@ -0,0 +1,88 @@
+///
+/// The lengths of the queues and the stacks, and
+/// , which do not store their length, counted only as far as
+/// a caller needs them (ADR 0001, D5 and D6).
+///
+///
+///
+/// The checks of take a length, but most of them only tell an empty
+/// input from one with elements, or an input of one element from a longer one, and the sorts return an input of at most one
+/// element unchanged. A count of a whole queue or stack would make each of them O(n).
+///
+///
+/// The modules of the queues and the stacks pass a limit instead: 1 to tell an empty input from one with elements, 2 to
+/// tell an input of one element from a longer one. The functions take the interfaces, which the concrete queue and stack
+/// implement, so that one function serves a module and its interface module.
+///
+///
+module internal FSharp.Collections.Immutable.Lengths
+
+open System.Collections.Immutable
+
+///
+/// Counts the elements of a collection up to a limit, by stepping from the collection to the collection without its first
+/// element.
+///
+/// Tells whether a collection has no element.
+/// Returns the collection without its first element; the collection is not empty.
+/// The largest number to count.
+/// The collection to count.
+/// The smaller of the number of elements and the limit, and 0 for a limit that is not positive.
+///
+///
+/// The step runs at most limit - 1 times, so a limit of 1 only reads whether the collection is empty.
+///
+///
+/// The queue and the stack differ only in how they tell that no element is left and how they step, so the loop exists
+/// once. It is inline and inlines its two functions, so that a count contains the calls of the interface members
+/// themselves, not calls of closures.
+///
+///
+let inline private countUpTo
+ ([] isEmpty : 'Collection -> bool)
+ ([] rest : 'Collection -> 'Collection)
+ (limit : int)
+ (collection : 'Collection)
+ =
+ let mutable remaining = collection
+ let mutable count = 0
+
+ while count < limit && not (isEmpty remaining) do
+ count <- count + 1
+
+ if count < limit then
+ remaining <- rest remaining
+
+ count
+
+///
+/// The number of elements of a queue, counted up to a limit: the length, or the limit for a longer queue.
+///
+/// The largest number to count; a limit that is not positive gives 0.
+/// The queue, which the caller has checked for .
+/// The smaller of the length of the queue and the limit, and 0 for a limit that is not positive.
+///
+/// O(1) for a limit of 1, which only reads . A
+/// larger limit calls at most limit - 1 times.
+/// Each call allocates a queue, and the call that takes the last element of the front of an
+/// reverses its back once, so the function suits small
+/// limits.
+///
+let queueUpTo (limit : int) (queue : IImmutableQueue<'T>) =
+ countUpTo (fun (queue : IImmutableQueue<'T>) -> queue.IsEmpty) (fun queue -> queue.Dequeue ()) limit queue
+
+///
+/// The number of elements of a stack, counted up to a limit: the length, or the limit for a longer stack.
+///
+///
+/// The largest number to count; a limit that is not positive gives 0, and counts
+/// the whole stack.
+///
+/// The stack, which the caller has checked for .
+/// The smaller of the length of the stack and the limit, and 0 for a limit that is not positive.
+///
+/// O(min(n, limit)), without an allocation: returns
+/// the stack below the top element, which exists already.
+///
+let stackUpTo (limit : int) (stack : IImmutableStack<'T>) =
+ countUpTo (fun (stack : IImmutableStack<'T>) -> stack.IsEmpty) (fun stack -> stack.Pop ()) limit stack
diff --git a/src/FSharp.Collections.Immutable/Helper/ListArrays.fs b/src/FSharp.Collections.Immutable/Helper/ListArrays.fs
new file mode 100644
index 00000000..d2caa831
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/ListArrays.fs
@@ -0,0 +1,344 @@
+///
+/// The functions of the FSharp.Core module that walk two or three
+/// lists together, over arrays, for the modules that mirror it and work on snapshots of their collections (ADR 0001, D5
+/// and D6).
+///
+///
+///
+/// The FSharp.Core module has these functions too, but it handles
+/// arrays of different lengths differently. Its functions compare the lengths before they call the callback once, and
+/// report arrays. The functions of walk the lists together, call
+/// the callback for the common prefix, compare the lengths when the shorter list ends, and report lists. A callback with
+/// a side effect shows the difference, and so does a predicate that decides before the end.
+///
+///
+/// Each function here applies its callback to the elements of the common prefix in order and then checks the lengths with
+/// the names that the caller passes (see ), so that the callback
+/// sees what it sees in a list before the exception. A predicate of
+///
+/// or
+///
+/// that decides early ends the walk before the check, as in a list.
+///
+///
+/// The summary of each function, the descriptions of its parameters and of its result are those of the function of
+/// (FSharp.Core 10.1), with arrays where the original says lists or
+/// collections. The remarks say what differs and why the function exists.
+///
+///
+module internal FSharp.Collections.Immutable.ListArrays
+
+///
+/// Tests if any pair of corresponding elements of the arrays satisfies the given predicate.
+///
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to test the input elements.
+/// The first input array.
+/// The second input array.
+/// True if any pair of elements satisfy the predicate.
+///
+/// No pair of the common prefix satisfies the predicate, and the arrays differ in length. The exception is the one of
+/// .
+///
+///
+///
+/// The predicate is applied to matching elements in the two arrays up to the lesser of the two lengths. If any application
+/// returns true then the overall result is true and no further elements are tested. Otherwise, if one array is longer than
+/// the other then the exception is raised. Otherwise, false is returned. This is
+/// an O(n) operation in the worst case, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the predicate and reports arrays, where
+///
+/// calls the predicate for the common prefix first and reports lists.
+///
+///
+let exists2 (name2 : string) (predicate : 'T1 -> 'T2 -> bool) (array1 : 'T1 array) (array2 : 'T2 array) =
+ let common = min array1.Length array2.Length
+ let mutable found = false
+ let mutable index = 0
+
+ while not found && index < common do
+ found <- predicate array1[index] array2[index]
+ index <- index + 1
+
+ if not found then
+ ListChecks.exists2 name2 array1.Length array2.Length
+
+ found
+
+///
+/// Applies a function to corresponding elements of two collections, threading an accumulator argument through the
+/// computation. The collections must have identical sizes. If the input function is f and the elements are
+/// i0...iN and j0...jN then computes f (... (f s i0 j0)...) iN jN.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to update the state given the input elements.
+/// The initial state.
+/// The first input array.
+/// The second input array.
+/// The final state value.
+///
+/// The arrays differ in length. The folder has folded the pairs of the common prefix by then. The exception is the one of
+/// .
+///
+///
+///
+/// This is an O(n) operation, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the folder and reports arrays, where
+///
+/// folds the common prefix first and reports lists.
+///
+///
+let fold2
+ (name1 : string)
+ (name2 : string)
+ (folder : 'State -> 'T1 -> 'T2 -> 'State)
+ (state : 'State)
+ (array1 : 'T1 array)
+ (array2 : 'T2 array)
+ =
+ let mutable state = state
+
+ for index = 0 to min array1.Length array2.Length - 1 do
+ state <- folder state array1[index] array2[index]
+
+ ListChecks.sameLengths name1 array1.Length name2 array2.Length
+ state
+
+///
+/// Tests if all corresponding elements of the arrays satisfy the given predicate pairwise.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to test the input elements.
+/// The first input array.
+/// The second input array.
+/// True if all of the pairs of elements satisfy the predicate.
+///
+/// Every pair of the common prefix satisfies the predicate, and the arrays differ in length. The exception is the one of
+/// .
+///
+///
+///
+/// The predicate is applied to matching elements in the two arrays up to the lesser of the two lengths. If any application
+/// returns false then the overall result is false and no further elements are tested. Otherwise, if one array is longer
+/// than the other then the exception is raised. Otherwise, true is returned. This
+/// is an O(n) operation in the worst case, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the predicate and reports arrays, where
+///
+/// calls the predicate for the common prefix first and reports lists.
+///
+///
+let forall2 (name1 : string) (name2 : string) (predicate : 'T1 -> 'T2 -> bool) (array1 : 'T1 array) (array2 : 'T2 array) =
+ let common = min array1.Length array2.Length
+ let mutable holds = true
+ let mutable index = 0
+
+ while holds && index < common do
+ holds <- predicate array1[index] array2[index]
+ index <- index + 1
+
+ if holds then
+ ListChecks.sameLengths name1 array1.Length name2 array2.Length
+
+ holds
+
+///
+/// Applies the given function to two collections simultaneously. The collections must have identical sizes.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to apply to pairs of elements from the input arrays.
+/// The first input array.
+/// The second input array.
+///
+/// The arrays differ in length. The action has been applied to the pairs of the common prefix by then. The exception is the
+/// one of
+/// .
+///
+///
+///
+/// This is an O(n) operation, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the action and reports arrays, where
+///
+/// applies the action to the common prefix first and reports lists.
+///
+///
+let iter2 (name1 : string) (name2 : string) (action : 'T1 -> 'T2 -> unit) (array1 : 'T1 array) (array2 : 'T2 array) =
+ for index = 0 to min array1.Length array2.Length - 1 do
+ action array1[index] array2[index]
+
+ ListChecks.sameLengths name1 array1.Length name2 array2.Length
+
+///
+/// Applies the given function to two collections simultaneously. The collections must have identical sizes. The integer
+/// passed to the function indicates the index of the element.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to apply to a pair of elements from the input arrays along with their index.
+/// The first input array.
+/// The second input array.
+///
+/// The arrays differ in length. The action has been applied to the pairs of the common prefix by then. The exception is the
+/// one of
+/// .
+///
+///
+///
+/// This is an O(n) operation, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the action and reports arrays, where
+///
+/// applies the action to the common prefix first and reports lists.
+///
+///
+let iteri2 (name1 : string) (name2 : string) (action : int -> 'T1 -> 'T2 -> unit) (array1 : 'T1 array) (array2 : 'T2 array) =
+ for index = 0 to min array1.Length array2.Length - 1 do
+ action index array1[index] array2[index]
+
+ ListChecks.sameLengths name1 array1.Length name2 array2.Length
+
+///
+/// Builds a new array whose elements are the results of applying the given function to the corresponding elements of the
+/// two arrays pairwise.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to transform pairs of elements from the input arrays.
+/// The first input array.
+/// The second input array.
+/// The array of transformed elements.
+///
+/// The arrays differ in length. The mapping has been applied to the pairs of the common prefix by then. The exception is
+/// the one of
+/// .
+///
+///
+///
+/// This is an O(n) operation, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the mapping and reports arrays, where
+///
+/// applies the mapping to the common prefix first and reports lists.
+///
+///
+let map2 (name1 : string) (name2 : string) (mapping : 'T1 -> 'T2 -> 'U) (array1 : 'T1 array) (array2 : 'T2 array) : 'U array =
+ let result = Array.zeroCreate (min array1.Length array2.Length)
+
+ for index = 0 to result.Length - 1 do
+ result[index] <- mapping array1[index] array2[index]
+
+ ListChecks.sameLengths name1 array1.Length name2 array2.Length
+ result
+
+///
+/// Builds a new array whose elements are the results of applying the given function to the corresponding elements of the
+/// three arrays simultaneously.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The name to report for the third array; FSharp.Core uses "list3".
+/// The function to transform triples of elements from the input arrays.
+/// The first input array.
+/// The second input array.
+/// The third input array.
+/// The array of transformed elements.
+///
+/// The arrays differ in length. The mapping has been applied to the triples of the common prefix by then. The exception is
+/// the one of
+/// .
+///
+///
+///
+/// This is an O(n) operation, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the mapping and reports arrays, where
+///
+/// applies the mapping to the common prefix first and reports lists.
+///
+///
+let map3
+ (name1 : string)
+ (name2 : string)
+ (name3 : string)
+ (mapping : 'T1 -> 'T2 -> 'T3 -> 'U)
+ (array1 : 'T1 array)
+ (array2 : 'T2 array)
+ (array3 : 'T3 array)
+ : 'U array =
+ let result = Array.zeroCreate (min array1.Length (min array2.Length array3.Length))
+
+ for index = 0 to result.Length - 1 do
+ result[index] <- mapping array1[index] array2[index] array3[index]
+
+ ListChecks.map3 name1 array1.Length name2 array2.Length name3 array3.Length
+ result
+
+///
+/// Like
+/// ,
+/// but mapping corresponding elements from two arrays of equal length.
+///
+/// The name to report for the first array; FSharp.Core uses "list1".
+/// The name to report for the second array; FSharp.Core uses "list2".
+/// The function to transform pairs of elements from the two arrays and their index.
+/// The first input array.
+/// The second input array.
+/// The array of transformed elements.
+///
+/// The arrays differ in length. The mapping has been applied to the pairs of the common prefix by then. The exception is
+/// the one of
+/// .
+///
+///
+///
+/// This is an O(n) operation, where n is the length of the arrays.
+///
+///
+/// It exists because
+///
+/// compares the lengths before it calls the mapping and reports arrays, where
+///
+/// applies the mapping to the common prefix first and reports lists.
+///
+///
+let mapi2
+ (name1 : string)
+ (name2 : string)
+ (mapping : int -> 'T1 -> 'T2 -> 'U)
+ (array1 : 'T1 array)
+ (array2 : 'T2 array)
+ : 'U array =
+ let result = Array.zeroCreate (min array1.Length array2.Length)
+
+ for index = 0 to result.Length - 1 do
+ result[index] <- mapping index array1[index] array2[index]
+
+ ListChecks.sameLengths name1 array1.Length name2 array2.Length
+ result
diff --git a/src/FSharp.Collections.Immutable/Helper/ListChecks.fs b/src/FSharp.Collections.Immutable/Helper/ListChecks.fs
new file mode 100644
index 00000000..cd390d48
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/ListChecks.fs
@@ -0,0 +1,581 @@
+///
+/// The argument checks of the functions of the FSharp.Core module,
+/// for the modules that mirror it: ,
+/// , the modules of the queues and the stacks, and their interface
+/// modules (ADR 0001, D6).
+///
+///
+///
+/// A module that mirrors throws what its functions throw for the
+/// same input: the same type and the same message, with the name of the parameter of the mirroring function (ADR 0001, D6).
+/// Each check takes the lengths or the indices to test and the names to report, and throws what the functions that it names
+/// throw. The checks are here, once, so that no module spells the type, the message and the parameter name of every
+/// exception again.
+///
+///
+/// The documentation of each check gives the name that the FSharp.Core function reports for the parameter.
+/// and
+/// pass those names; a module with other parameter names passes
+/// its own.
+///
+///
+/// The FSharp.Core functions of two or three lists compare the lengths when the shorter list ends, so a function with a
+/// callback applies it to the common prefix before it calls the check of the lengths.
+/// does that over arrays.
+///
+///
+module internal FSharp.Collections.Immutable.ListChecks
+
+open System
+
+///
+/// The message that
+/// ,
+/// ,
+/// ,
+/// and
+/// write as a literal.
+///
+///
+/// It is not a constant of , whose every constant is the text of
+/// one resource: no resource of FSharp.Core holds this text.
+///
+[]
+let private IndexOutsideListBounds = "index must be within bounds of the list"
+
+///
+/// Rejects an empty list, as
+/// ,
+/// ,
+/// ,
+/// and
+/// do.
+///
+/// The name of the parameter to report: "list" for these functions.
+/// The length of the list.
+///
+/// The length is 0. The message is , and the
+/// parameter name is .
+///
+///
+/// It is a check of its own, besides
+/// , because the
+/// functions of the two groups give an empty input different messages.
+///
+let notEmpty (name : string) (length : int) =
+ if length = 0 then
+ invalidArg name ErrorStrings.InputListWasEmpty
+
+///
+/// Rejects an empty input with the message of an empty sequence, as
+/// ,
+/// ,
+/// and
+/// ("list") and
+/// ,
+/// and
+/// ("source") do.
+///
+/// The name of the parameter to report: "list" or "source", as above.
+/// The length of the input.
+///
+/// The length is 0. The message is , and the
+/// parameter name is .
+///
+///
+/// It is a check of its own, besides
+/// , because the messages
+/// differ: these functions say "sequence" where the functions of the other check say "list".
+///
+let notEmptySequence (name : string) (length : int) =
+ if length = 0 then
+ invalidArg name ErrorStrings.InputSequenceEmpty
+
+///
+/// Rejects an input without exactly one element, as
+/// does.
+///
+/// The name of the parameter to report: "source" for this function.
+/// The length of the input.
+///
+/// The length is not 1. The message is for no
+/// element and for more than one, and the
+/// parameter name is .
+///
+let exactlyOne (name : string) (length : int) =
+ if length = 0 then
+ invalidArg name ErrorStrings.InputSequenceEmpty
+ elif length > 1 then
+ invalidArg name ErrorStrings.InputSequenceTooLong
+
+///
+/// Rejects an index outside the list, as
+/// does.
+///
+/// The name of the parameter to report: "index" for this function.
+/// The index to test.
+/// The length of the list.
+///
+/// The index is negative or not less than the length. The message is
+/// , and the parameter name is
+/// .
+///
+///
+/// It is a check of its own, besides
+/// , because the
+/// message differs, although the indices that both reject are the same.
+///
+let item (name : string) (index : int) (length : int) =
+ if index < 0 || index >= length then
+ invalidArg name ErrorStrings.IndexOutOfBounds
+
+///
+/// Rejects an index outside the range from 0 to the length, as
+/// and
+/// do.
+///
+/// The name of the parameter to report: "index" for these functions.
+/// The index to test.
+/// The length of the list.
+///
+/// The index is negative or greater than the length. The message is
+/// , and the parameter name is
+/// .
+///
+///
+/// The length itself is a valid index, because an element can be inserted after the last one.
+///
+let insertAt (name : string) (index : int) (length : int) =
+ if index < 0 || index > length then
+ invalidArg name IndexOutsideListBounds
+
+///
+/// Rejects an index outside the list, as
+/// and
+/// do.
+///
+/// The name of the parameter to report: "index" for these functions.
+/// The index to test.
+/// The length of the list.
+///
+/// The index is negative or not less than the length. The message is
+/// , and the parameter name is
+/// .
+///
+///
+/// Unlike for
+/// , the length
+/// itself is not a valid index: no element is there to remove or to replace.
+///
+let removeAt (name : string) (index : int) (length : int) =
+ if index < 0 || index >= length then
+ invalidArg name IndexOutsideListBounds
+
+///
+/// Rejects a negative index and a range that ends beyond the list, as
+/// does.
+///
+/// The name of the parameter to report: "index" for this function.
+/// The index of the first element of the range.
+/// The number of elements of the range.
+/// The length of the list.
+///
+/// The index is negative, or the index plus the count is greater than the length. The message is
+/// , and the parameter name is
+/// .
+///
+///
+///
+/// walks the first index + count elements and keeps the ones before the index, so a count that is not positive
+/// removes nothing, even with an index beyond the list, as long as index + count does not exceed the length. The
+/// check tests the same sum, which wraps around on overflow there as it does here.
+///
+let removeManyAt (name : string) (index : int) (count : int) (length : int) =
+ if index < 0 || index + count > length then
+ invalidArg name IndexOutsideListBounds
+
+///
+/// Rejects a negative count, as
+/// ,
+/// and
+/// do.
+///
+/// The name of the parameter to report: "count" for these functions.
+/// The count to test.
+///
+/// The count is negative. The message is the one of
+/// ,
+/// which gives the name and the value of the count, and the parameter name is .
+///
+///
+///
+/// gives the message without the value, so it has a check of its own,
+/// .
+///
+let nonNegative (name : string) (count : int) =
+ if count < 0 then
+ Raise.invalidArgInputMustBeNonNegative name count
+
+///
+/// Rejects a negative count, as
+/// does.
+///
+/// The name of the parameter to report: "count" for this function.
+/// The count to test.
+///
+/// The count is negative. The message is
+/// , and the parameter name is
+/// .
+///
+///
+/// It is a check of its own, besides
+/// , because unlike
+///
+/// it reports the message without the value.
+///
+let replicate (name : string) (count : int) =
+ if count < 0 then
+ invalidArg name ErrorStrings.InputMustBeNonNegative
+
+///
+/// Rejects a size that is not positive, as
+/// ("chunkSize"),
+///
+/// ("windowSize") and
+/// ("count") do.
+///
+/// The name of the parameter to report: the one in the brackets above.
+/// The size to test.
+///
+/// The size is 0 or negative. The message is the one of
+/// , which
+/// gives the name and the value of the size, and the parameter name is .
+///
+let positive (name : string) (size : int) =
+ if size <= 0 then
+ Raise.invalidArgInputMustBePositive name size
+
+///
+/// Rejects a negative count and a count beyond the length, as
+/// does.
+///
+/// The name of the parameter to report: "count" for this function.
+/// The count to test.
+/// The length of the list.
+///
+/// The count is negative. The message is the one of
+/// , and
+/// the parameter name is .
+///
+///
+/// The count is greater than the length. The message is the one of
+/// , which says how many
+/// elements are missing.
+///
+let take (name : string) (count : int) (length : int) =
+ if count < 0 then
+ Raise.invalidArgInputMustBeNonNegative name count
+ elif count > length then
+ Raise.invalidOpListNotEnoughElements (count - length)
+
+///
+/// Rejects a count beyond the length, as
+/// does.
+///
+/// The name of the parameter to report: "count" for this function.
+/// The count to test.
+/// The length of the list.
+///
+/// The count is greater than the length. The message is the one of
+/// ,
+/// which gives the count and the distance past the list, and the parameter name is .
+///
+///
+/// A negative count skips nothing, so the function does not reject it, as
+/// does not.
+///
+let skip (name : string) (count : int) (length : int) =
+ if count > length then
+ Raise.invalidArgOutOfRange name count "distance past the list" (count - length)
+
+///
+/// Rejects a negative index and an index beyond the length, as
+/// does.
+///
+/// The name of the parameter to report: "index" for this function.
+/// The index to test.
+/// The length of the list.
+///
+/// The index is negative. The message is the one of
+/// , and
+/// the parameter name is .
+///
+///
+/// The index is greater than the length. The message is the one of
+/// for an empty list and the one of
+/// for a longer one.
+///
+///
+/// An empty list has a message of its own because
+/// gives it one.
+///
+let splitAt (name : string) (index : int) (length : int) =
+ if index < 0 then
+ Raise.invalidArgInputMustBeNonNegative name index
+ elif index > length then
+ if length = 0 then
+ Raise.invalidOpListEmpty ()
+ else
+ Raise.invalidOpListNotEnoughElements (index - length)
+
+///
+/// Rejects lists of different lengths, as
+/// ,
+/// ,
+/// ,
+/// ,
+/// ,
+/// and
+///
+/// do ("list1", "list2").
+///
+/// The name of the parameter of the first list.
+/// The length of the first list.
+/// The name of the parameter of the second list.
+/// The length of the second list.
+///
+/// The lengths differ. The exception is the one of
+/// :
+/// the parameter name is the name of the shorter list, and the message says how much shorter it is.
+///
+let sameLengths (name1 : string) (length1 : int) (name2 : string) (length2 : int) =
+ if length1 < length2 then
+ Raise.invalidArgDifferentListLength name1 name2 (length2 - length1)
+ elif length2 < length1 then
+ Raise.invalidArgDifferentListLength name2 name1 (length1 - length2)
+
+///
+/// Rejects lists of different lengths with a message without lengths, as
+/// does.
+///
+/// The name of the parameter of the second list: "list2" for this function.
+/// The length of the first list.
+/// The length of the second list.
+///
+/// The lengths differ. The message is ,
+/// and the parameter name is always the name of the second list, .
+///
+///
+/// It is a check of its own, besides
+/// ,
+/// because the exception of
+///
+/// neither names the shorter list nor says how much shorter it is.
+///
+let exists2 (name2 : string) (length1 : int) (length2 : int) =
+ if length1 <> length2 then
+ invalidArg name2 ErrorStrings.ListsHadDifferentLengths
+
+///
+/// Rejects lists of different lengths before the folder is called, as
+/// does
+/// ("list1", "list2").
+///
+/// The name of the parameter of the first list.
+/// The length of the first list.
+/// The name of the parameter of the second list.
+/// The length of the second list.
+///
+/// The lengths differ. When one of the lists is empty, the exception is the one of
+/// .
+/// Otherwise it is the one of
+/// ,
+/// which names both lists and gives both lengths.
+///
+///
+///
+/// reports the same mistake in two ways. An empty list against a list with elements is reported as the other functions
+/// of two lists report it. Two lists with elements reach the fallback of the function, which copies them into arrays to
+/// fold them from the end, and it reports their lengths. Hence the two branches of the check.
+///
+let foldBack2 (name1 : string) (length1 : int) (name2 : string) (length2 : int) =
+ if length1 <> length2 then
+ if length1 = 0 || length2 = 0 then
+ sameLengths name1 length1 name2 length2
+ else
+ Raise.invalidArg2ListsDifferent name1 length1 name2 length2
+
+///
+/// Rejects lists of different lengths and reports their lengths, as
+/// does
+/// ("list1", "list2", "list3").
+///
+/// The name of the parameter of the first list.
+/// The length of the first list.
+/// The name of the parameter of the second list.
+/// The length of the second list.
+/// The name of the parameter of the third list.
+/// The length of the third list.
+///
+/// The lengths differ. The exception is the one of
+/// :
+/// it names the three lists and gives their lengths.
+///
+let map3 (name1 : string) (length1 : int) (name2 : string) (length2 : int) (name3 : string) (length3 : int) =
+ if length1 <> length2 || length1 <> length3 then
+ Raise.invalidArg3ListsDifferent name1 name2 name3 length1 length2 length3
+
+///
+/// Rejects lists of different lengths and reports the lengths that remain after their common prefix, as
+///
+/// does ("list1", "list2", "list3").
+///
+/// The name of the parameter of the first list.
+/// The length of the first list.
+/// The name of the parameter of the second list.
+/// The length of the second list.
+/// The name of the parameter of the third list.
+/// The length of the third list.
+///
+/// The lengths differ. The exception is the one of
+/// ,
+/// with each length reduced by the length of the shortest list.
+///
+///
+/// It is a check of its own, besides
+/// ,
+/// because
+///
+/// walks the three lists together and reports what is left of each when the shortest one ends.
+///
+let zip3 (name1 : string) (length1 : int) (name2 : string) (length2 : int) (name3 : string) (length3 : int) =
+ if length1 <> length2 || length1 <> length3 then
+ let common = min length1 (min length2 length3)
+ Raise.invalidArg3ListsDifferent name1 name2 name3 (length1 - common) (length2 - common) (length3 - common)
+
+///
+/// Rejects lists of different lengths, as
+/// does.
+///
+/// The name of the parameter of the list of lists: "list" for this function.
+/// The lengths of the lists, in the order of the list of lists.
+///
+/// The lengths differ. The exception is the one of
+/// :
+/// the parameter name is the name of the shorter list of a pair that includes the first list, and the message says how
+/// much shorter it is. The lists are named by their positions: for the name "list", "list.[0]",
+/// "list.[1]" and so on.
+///
+///
+///
+///
+/// takes the first element of every list in each round, and fails in the first round in which a list has none, which is
+/// the round of the length of the shortest list. It then compares each list with the first one. When the first list is
+/// among the shortest, the pair is the first list and the first list that goes on, which is longer. Otherwise the pair is
+/// the first list that has run out, which is shorter, and the first list. The check takes the lengths at once and finds
+/// the same pair, so the caller does not take the lists apart.
+///
+///
+/// Fewer than two lists cannot differ, so they pass.
+///
+///
+let transpose (name : string) (lengths : int array) =
+ if lengths.Length > 1 then
+ let shortest = Array.min lengths
+ let first = lengths[0]
+ let element (index : int) = String.Format ("{0}.[{1}]", name, index)
+
+ if first = shortest then
+ // The first list ends first, so the first list that goes on is longer
+ let mutable index = 1
+
+ while index < lengths.Length && lengths[index] = shortest do
+ index <- index + 1
+
+ if index < lengths.Length then
+ Raise.invalidArgDifferentListLength (element 0) (element index) (lengths[index] - shortest)
+ else
+ // The first list goes on, so the first list that ends is shorter
+ let mutable index = 1
+
+ while lengths[index] <> shortest do
+ index <- index + 1
+
+ Raise.invalidArgDifferentListLength (element index) (element 0) (first - shortest)
+
+///
+/// Rejects a negative count and, for a positive count, an empty input, as
+/// ,
+/// and
+/// do.
+///
+/// The name of the parameter of the count: "count" for these functions.
+/// The count to test.
+/// The name of the parameter of the input: "source" for these functions.
+/// The length of the input.
+///
+/// The count is negative (the exception of
+/// ), or the count is
+/// positive and the input is empty (the message is
+/// , and the parameter name is
+/// ).
+///
+///
+/// A count of 0 draws nothing, so it is valid for an empty input.
+///
+let randomChoices (countName : string) (count : int) (sourceName : string) (length : int) =
+ nonNegative countName count
+
+ if length = 0 && count > 0 then
+ invalidArg sourceName ErrorStrings.InputSequenceEmpty
+
+///
+/// Rejects what
+///
+/// rejects and a count beyond the length, as
+/// ,
+/// and
+/// do.
+///
+/// The name of the parameter of the count: "count" for these functions.
+/// The count to test.
+/// The name of the parameter of the input: "source" for these functions.
+/// The length of the input.
+///
+/// The input is not empty and shorter than the count. The message is
+/// , and the parameter name is
+/// .
+///
+///
+/// A sample draws each element at most once, so unlike the choices it cannot be larger than the input.
+///
+let randomSample (countName : string) (count : int) (sourceName : string) (length : int) =
+ randomChoices countName count sourceName length
+
+ if length > 0 && count > length then
+ invalidArg countName ErrorStrings.NotEnoughElements
+
+///
+/// The index in a list of the given length that the functions with a randomizer draw, as
+/// does.
+///
+/// The name of the parameter of the randomizer: "randomizer" for these functions.
+/// The function that returns a number from 0 up to, but not including, 1.
+/// The length of the list.
+/// The number that the randomizer returned, times the length, without its fraction.
+///
+/// The randomizer returned a number outside [0, 1), or not a number. The message is the one of
+/// , and
+/// the parameter name is .
+///
+///
+/// The check and the index are one function because the FSharp.Core functions with a randomizer test every number that it
+/// returns before they use it.
+///
+let randomIndex (name : string) (randomizer : unit -> float) (length : int) =
+ let value = randomizer ()
+
+ if value >= 0.0 && value < 1.0 then
+ int (value * float length)
+ else
+ Raise.invalidArgRandomizerOutOfRange name value
diff --git a/src/FSharp.Collections.Immutable/Helper/ListRanges.fs b/src/FSharp.Collections.Immutable/Helper/ListRanges.fs
new file mode 100644
index 00000000..9af79ed3
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/ListRanges.fs
@@ -0,0 +1,191 @@
+///
+/// The range checks of the functions that wrap the members of
+/// and of its builder, with the parameter names of the callers (ADR 0001, D6 and D14).
+///
+///
+///
+/// The members add an index and a count and compare the sum with the length. The sum wraps around at
+/// , so a range that starts near the end of the numbers passes the check of some
+/// members, which then do nothing, or answer with an index that is not the index of an element. The checks here compare
+/// the count with the difference between the length and the index, which cannot overflow once the index is not negative.
+///
+///
+/// A range that the members of reject is rejected with the
+/// same exception and the name that the member gives the parameter, except that the caller passes the names of its own
+/// parameters. The checks add the ranges that the members accept and that the documentation calls invalid.
+///
+///
+/// The functions of ,
+/// and
+/// that search, reverse, sort or copy a range call the
+/// checks of this module before they call the member, so that the three modules reject the same ranges in the same way.
+///
+///
+module internal FSharp.Collections.Immutable.ListRanges
+
+open System
+
+///
+/// The message of the for a range that does not fit in the list.
+///
+///
+/// The runtime throws this exception from
+/// ,
+/// with a message that says "array" and "source collection". The text here says "list". It is not a constant of
+/// , because no resource holds it.
+///
+[]
+let private OffsetAndLengthOutOfBounds =
+ "Offset and length were out of bounds for the list or count is greater than the number of elements from index to the end of the list."
+
+///
+/// Checks a range of elements that starts at in a list.
+///
+/// The name of the parameter of the index.
+/// The index of the first element of the range.
+/// The name of the parameter of the count.
+/// The number of elements of the range.
+///
+/// The name that the exception for a range beyond the end of the list gives: the members name the count, except
+/// , which names the
+/// index.
+///
+/// The length of the list.
+///
+/// The index or the count is negative, or the range ends beyond the list: the checks of
+/// ,
+/// ,
+/// ,
+/// ,
+///
+/// and .
+/// A range of no elements may start at the end of the list.
+///
+///
+/// It serves the functions that look at a range from its first element, such as the ones that search forward for an
+/// item or for an element that satisfies a predicate, reverse a range or sort it, and, through
+/// ,
+/// copy it. The members compare the sum of the index and the count with the length, which wraps around (see the remarks of
+/// the module).
+///
+let forward (indexName : string) (index : int) (countName : string) (count : int) (beyondName : string) (length : int) =
+ if index < 0 then
+ raise (ArgumentOutOfRangeException indexName)
+
+ if count < 0 then
+ raise (ArgumentOutOfRangeException countName)
+
+ // The index and the length are not negative, so the difference cannot overflow
+ if count > length - index then
+ raise (ArgumentOutOfRangeException beyondName)
+
+///
+/// Checks a range of elements that ends at in a list, which a
+/// backward search walks from its end to its start.
+///
+/// The name of the parameter of the index of the last element of the range.
+/// The index of the first element that the search looks at.
+/// The name of the parameter of the count.
+/// The number of elements of the range.
+/// The length of the list.
+///
+/// The start index or the count is negative, the count exceeds the length, or a range of elements does not lie inside
+/// the list: the checks of
+///
+/// and ,
+/// which also accept a last element at or beyond the end of the list, and answer with the index of another element.
+///
+///
+///
+/// A range of no elements lies anywhere, so a start index at or beyond the end of the list passes for it.
+///
+///
+/// It is a check of its own, besides
+/// ,
+/// because a backward search gives the index of the last element of the range, not of the first, and the members accept a
+/// last element at or beyond the end of the list. It serves the functions that search backward for the last index of an
+/// item or of an element that satisfies a predicate.
+///
+///
+let backward (startIndexName : string) (startIndex : int) (countName : string) (count : int) (length : int) =
+ if startIndex < 0 then
+ raise (ArgumentOutOfRangeException startIndexName)
+
+ if count < 0 || count > length then
+ raise (ArgumentOutOfRangeException countName)
+
+ // The range starts at startIndex - count + 1: the count 1 starts at the start index itself. The subtraction cannot
+ // overflow, because the count is positive.
+ if
+ count > 0
+ && (startIndex >= length || count - 1 > startIndex)
+ then
+ raise (ArgumentOutOfRangeException startIndexName)
+
+///
+/// Checks the range of a binary search, which the members do not check against the length of the list.
+///
+/// The name of the parameter of the index.
+/// The index of the first element of the range.
+/// The name of the parameter of the count.
+/// The number of elements of the range.
+/// The length of the list.
+/// The index or the count is negative.
+///
+/// The range extends beyond the list. The documentation of
+///
+/// promises this exception, and the member returns a result instead (ADR 0001, D14).
+///
+///
+/// It is a check of its own, besides
+/// ,
+/// because the exception for a range beyond the end is an , as
+///
+/// throws it, where the other checks of the module throw an
+/// . It serves the functions that search a range of a sorted list.
+///
+let search (indexName : string) (index : int) (countName : string) (count : int) (length : int) =
+ if index < 0 then
+ raise (ArgumentOutOfRangeException indexName)
+
+ if count < 0 then
+ raise (ArgumentOutOfRangeException countName)
+
+ if count > length - index then
+ raise (ArgumentException (OffsetAndLengthOutOfBounds, countName))
+
+///
+/// Checks the ranges of a copy of elements from a list into an array: the checks of
+/// .
+///
+/// The name of the parameter of the index in the list.
+/// The index of the first element to copy.
+/// The name of the parameter of the count.
+/// The number of elements to copy.
+/// The length of the list.
+/// The name of the parameter of the index in the array.
+/// The index in the array at which the copy starts.
+/// The length of the array.
+///
+/// The source index, the count or the target index is negative, or a range lies beyond the list or the array.
+///
+///
+/// It checks the range in the list as
+///
+/// does, with the count as the name for a range beyond the end, and then the range in the array. It serves the copying
+/// functions of the list and of its builder.
+///
+let blit
+ (sourceIndexName : string)
+ (sourceIndex : int)
+ (countName : string)
+ (count : int)
+ (sourceLength : int)
+ (targetIndexName : string)
+ (targetIndex : int)
+ (targetLength : int)
+ =
+ forward sourceIndexName sourceIndex countName count countName sourceLength
+
+ if targetIndex < 0 || count > targetLength - targetIndex then
+ raise (ArgumentOutOfRangeException targetIndexName)
diff --git a/src/FSharp.Collections.Immutable/Helper/Raise.fs b/src/FSharp.Collections.Immutable/Helper/Raise.fs
index b36a1141..f9515d00 100644
--- a/src/FSharp.Collections.Immutable/Helper/Raise.fs
+++ b/src/FSharp.Collections.Immutable/Helper/Raise.fs
@@ -25,6 +25,30 @@ let invalidArgDifferentListLength (arg1 : string) (arg2 : string) (diff : int) :
raise (ArgumentException (message, arg1))
+///
+/// Raises the exception for two lists that both have elements and differ in length, as the fallback of
+///
+/// does.
+///
+/// The name of the parameter of the first list.
+/// The length of the first list.
+/// The name of the parameter of the second list.
+/// The length of the second list.
+/// Never: the function always raises. The result is generic so that the call can end a branch of any type.
+///
+/// Always. The message is , a line break
+/// and the lengths of both lists, and the parameter name is both names, separated by a comma.
+///
+///
+/// It is not ,
+/// which names the shorter list and says how much shorter it is: the fallback of the function copies the lists into
+/// arrays, compares their lengths and reports both.
+///
+let invalidArg2ListsDifferent (arg1 : string) (len1 : int) (arg2 : string) (len2 : int) : 'T =
+ let message = $"{ErrorStrings.ListsHadDifferentLengths}\n{arg1}.Length = {len1}, {arg2}.Length = {len2}"
+
+ raise (ArgumentException (message, $"{arg1}, {arg2}"))
+
/// The arrays of a function of three arrays have different lengths.
let invalidArg3ArraysDifferent (arg1 : string) (arg2 : string) (arg3 : string) (len1 : int) (len2 : int) (len3 : int) : 'T =
let message =
@@ -53,6 +77,39 @@ let invalidArgOutOfRange (arg : string) (index : int) (text : string) (bound : i
raise (ArgumentOutOfRangeException (arg, message))
+///
+/// Raises the exception for a randomizer that returned a number outside [0, 1), which the functions of FSharp.Core with a
+/// randomizer, such as
+/// ,
+/// throw.
+///
+/// The name of the parameter of the randomizer.
+/// The number that the randomizer returned.
+/// Never: the function always raises. The result is generic so that the call can end a branch of any type.
+///
+/// Always. The message is , a line break and the number
+/// with the range that is allowed, and the parameter name is .
+///
+let invalidArgRandomizerOutOfRange (arg : string) (value : float) : 'T =
+ let message = $"{ErrorStrings.OutOfRange}\n{arg} returned {value}, should be in range [0.0, 1.0)."
+
+ raise (ArgumentOutOfRangeException (arg, message))
+
+///
+/// Raises the exception for an empty list and an index beyond it, as
+///
+/// does.
+///
+/// Never: the function always raises. The result is generic so that the call can end a branch of any type.
+///
+/// Always. The message is .
+///
+///
+/// A longer list that is too short gets
+/// instead.
+///
+let invalidOpListEmpty () : 'T = raise (InvalidOperationException ErrorStrings.InputListWasEmpty)
+
/// A list is shorter than an index.
let invalidOpListNotEnoughElements (index : int) : 'T =
let elements = if index = 1 then "element" else "elements"
diff --git a/src/FSharp.Collections.Immutable/Helper/Snapshot.fs b/src/FSharp.Collections.Immutable/Helper/Snapshot.fs
new file mode 100644
index 00000000..c1d40399
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/Helper/Snapshot.fs
@@ -0,0 +1,44 @@
+///
+/// The snapshots that the modules of the collections without an underlying array take before they apply an algorithm of
+/// the FSharp.Core module, of
+/// or of
+/// (ADR 0001, D5).
+///
+///
+/// The algorithms work on arrays. A collection such as an , or
+/// a queue or a stack, has none, so the module copies its elements into a new array first, applies the algorithm and
+/// builds the result from the array it gets.
+///
+module internal FSharp.Collections.Immutable.Snapshot
+
+open System.Linq
+
+///
+/// The elements of a collection in a new array, in the order in which the collection enumerates them.
+///
+/// The name of the parameter of the collection to report: the caller passes the name of its own.
+/// The collection.
+/// A new array that holds the elements, which the caller may change in place.
+///
+/// The collection is . The parameter name is .
+///
+///
+///
+/// This is an O(n) operation, where n is the number of elements. A collection that implements
+/// copies its elements with
+/// in one pass, without the growing
+/// buffers of a collection that only enumerates; an empty one gives the shared empty array, which has nothing to change.
+///
+///
+/// It exists to check the collection and to copy it in one place. The null check reports the name of the parameter of the
+/// caller, where
+/// and
+///
+/// report "source" (ADR 0001, D6). The copy goes through
+/// , because the priority of
+/// ADR 0001, D5 puts System.Linq before FSharp.Core.
+///
+///
+let ofSeq (name : string) (source : seq<'T>) : 'T array =
+ checkNotNull name source
+ Enumerable.ToArray source
diff --git a/src/FSharp.Collections.Immutable/ImmutableList.fs b/src/FSharp.Collections.Immutable/ImmutableList.fs
deleted file mode 100644
index 6683f84a..00000000
--- a/src/FSharp.Collections.Immutable/ImmutableList.fs
+++ /dev/null
@@ -1,353 +0,0 @@
-#if INTERACTIVE
-namespace global
-#else
-namespace FSharp.Collections.Immutable
-
-open FSharp.Collections.Immutable.ImmutableCollectionUtil
-#endif
-open System.Collections.Immutable
-
-[]
-module ImmutableList =
-
- ////////// Factory //////////
-
- let inline internal check (list : IImmutableList<_>) = checkNotNull (nameof list) list
-
- let inline empty<'T> = ImmutableList.Create<'T>()
- let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T>(item)
-
- let inline ofSeq source =
- checkNotNull (nameof source) source
- ImmutableList.CreateRange source
-
- let inline ofArray (source : _ array) =
- checkNotNull (nameof source) source
- ImmutableList.CreateRange source
-
- let inline ofList (list : _ list) = ofSeq list
-
- let inline toSeq (list : ImmutableList<_>) = list :> seq<_>
-
- let inline toArray (list : ImmutableList<_>) =
- check list
- Seq.toArray list
-
- ////////// Building //////////
-
- let inline ofBuilder (builder : ImmutableList<_>.Builder) = builder.ToImmutable ()
-
- let inline builder () = ImmutableList.CreateBuilder ()
-
- let toBuilder (list : ImmutableList<_>) =
- check list
- list.ToBuilder ()
-
- let inline build f =
- let builder = builder ()
- f builder
- builder.ToImmutable ()
-
- let inline update f list =
- let builder = toBuilder list
- f builder
- builder.ToImmutable ()
-
-
- open System.Collections.Generic
- open System
-
- ////////// IReadOnly* //////////
-
- let length list =
- check list
- list.Count
-
- let item index list =
- check list
- list.[index]
-
- ////////// ImmutableList //////////
-
- let contains item (list : ImmutableList<_>) = list.Contains (item)
-
- let reverse (list : ImmutableList<_>) = list.Reverse ()
-
- let reverseRange (index, count) (list : ImmutableList<_>) = list.Reverse (index, count)
-
- ////////// IImmutableList //////////
-
- /// Replaces an element in the list at a given position with the specified element.
- let withItem index value list =
- check list
- list.SetItem (index, value)
-
- /// Returns a new list with the first matching element in the list replaced with the specified element with
- /// the given comparer.
- let replaceWith comparer oldValue value list =
- check list
- list.Replace (oldValue, value, comparer)
-
- /// Returns a new list with the first matching element in the list replaced with the specified element.
- let replace oldValue value list = replaceWith HashIdentity.Structural oldValue value list
-
-
- /// Creates a list with all the items removed, but with the same sorting and ordering semantics as
- /// this list.
- let clear list =
- check list
- list.Clear ()
-
- /// Makes a copy of the list, and adds the specified object to the end of the copied list.
- let add item list =
- check list
- list.Add item
-
-
- /// Makes a copy of the list and adds the specified objects to the end of the copied list.
- let append list items =
- check list
- list.AddRange items
-
- /// Inserts the specified element at the specified index in a immutable list.
- let insert index item list =
- check list
- list.Insert (index, item)
-
- /// Inserts the specified elements at the specified index in the immutable list.
- let insertRange index items list =
- check list
- list.InsertRange (index, items) // TODO: rename
-
-
- /// Removes the first occurrence of a specified object from this immutable list using the given comparer.
- let removeWith comparer item list =
- check list
- list.Remove (item, comparer)
-
- /// Removes the first occurrence of a specified object from this immutable list.
- let remove item list = removeWith HashIdentity.Structural item list
-
-
- /// Removes the specified objects from the list with the given comparer.
- let exceptWith (comparer : IEqualityComparer<_>) items list =
- check list
- list.RemoveRange (items, comparer)
-
- /// Removes the specified objects from the list.
- let except items list = exceptWith HashIdentity.Structural items list
-
-
- /// Removes all the elements that do not match the conditions defined by the specified predicate.
- let filter predicate list =
- check list
- Predicate (not << predicate) |> list.RemoveAll
-
- /// Removes a range of elements from the System.Collections.Immutable.IImmutableList`1.
- let removeRange index (count : int) list =
- check list
- list.RemoveRange (index, count)
-
- /// Removes the element at the specified index of the immutable list.
- let removeAt index list =
- check list
- list.RemoveAt index
-
- /// Searches for the specified object and returns the zero-based index of the first occurrence within the range
- /// of elements in the list that starts at the specified index and
- /// contains the specified number of elements.
- let indexRangeWith comparer index count item list =
- check list
- list.IndexOf (item, index, count, comparer)
-
- let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list
- let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item
- let indexFrom index item list = indexFromWith HashIdentity.Structural index item list
- let indexWith comparer item list = indexFromWith comparer 0 item list
- let index item list = indexWith HashIdentity.Structural item list
-
-
- /// Searches for the specified object and returns the zero-based index of the last occurrence within the
- /// range of elements in the list that contains the specified number
- /// of elements and ends at the specified index.
- let lastIndexRangeWith comparer index count item list =
- check list
- list.LastIndexOf (item, index, count, comparer)
-
- let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list
- let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list
- let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list
- let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list
- let lastIndex item list = lastIndexWith HashIdentity.Structural item list
-
- ////////// Filter-based //////////
-
- let filterFold (predicate : 'State -> 'T -> bool * 'State) initial list =
- let state = ref initial
-
- filter
- (fun item ->
- let condition, state' = predicate !state item
- state := state'
- condition
- )
- list,
- !state
-
- let skipWhile predicate list =
- let condition = ref true
-
- filter
- (fun item ->
- if !condition then
- condition := !condition && predicate item
- !condition
- else
- false
- )
- list
-
- let skipUntil predicate list = skipWhile (not << predicate) list
-
- let takeWhile predicate list =
- let condition = ref true
-
- filter
- (fun item ->
- if !condition then
- condition := !condition && predicate item
- not !condition
- else
- true
- )
- list
-
- let takeUntil predicate list = takeWhile (not << predicate) list
-
- ////////// Loop-based //////////
-
- let concat lists =
- checkNotNull (nameof lists) lists
-
- build
- <| fun result ->
- for list in lists do
- result.AddRange list
-
- let map mapping list =
- check list
-
- build
- <| fun builder ->
- for item in list do
- builder.Add (mapping item)
-
- let choose chooser list =
- check list
-
- build
- <| fun builder ->
- for item in list do
- match chooser item with
- | Some item -> builder.Add item
- | None -> ()
-
-
- ////////// Based on other operations //////////
-
- let isEmpty list = length list = 0
-
- let take count list = removeRange count (length list - count) list
-
- let skip index list = removeRange 0 index list
-
- let truncate count list = if count < length list then take count list else list
-
- let splitAt index list = take index list, skip index list
-
- let head list = item 0 list
-
- let last (list : IImmutableList<_>) = list.[length list - 1]
-
- let tail list = removeAt 0 list
-
- let tryItem index list =
- if index >= length list || index < 0 then
- None
- else
- Some (list.[index])
-
- let tryHead list = tryItem 0 list
-
- let tryLast list = tryItem (length list - 1) list
-
- let tryTail list = if isEmpty list then None else Some <| tail list
-
- let collect mapping list = concat <| map mapping list
-
- let cons head list = insert 0 head list
-
- //let ofArray (array: 'T array) = ImmutableList.Create<'T>(items = [||])
-
- let init count initializer =
- if count < 0 then
- // throw the same exception
- try
- Seq.init count initializer |> ignore
- with exn ->
- raise exn // get the right stack trace
-
- build
- <| fun builder ->
- for i = 0 to count - 1 do
- builder.Add <| initializer i
-
- let unfold generator state =
- let rec unfoldLoop state (builder : ImmutableList<_>.Builder) =
- match generator state with
- | Some (state, item) ->
- builder.Add (item)
- unfoldLoop state builder
- | None -> ()
-
- build <| unfoldLoop state
-
-
- ////////// Seq-based //////////
-
- let find predicate list =
- check list
- Seq.find predicate list
-
- let tryFind predicate list =
- check list
- Seq.tryFind predicate list
-
- let findIndex predicate list =
- check list
- Seq.findIndex predicate list
-
- let tryFindIndex predicate list =
- check list
- Seq.tryFindIndex predicate list
-
- let pick chooser list =
- check list
- Seq.pick chooser list
-
- let fold folder state list =
- check list
- Seq.fold folder state list
-
- let forall predicate list =
- check list
- Seq.forall predicate list
-
- let forall2 predicate (list1 : IImmutableList<_>) (list2 : IImmutableList<_>) =
- checkNotNull (nameof list1) list1
- checkNotNull (nameof list2) list2
- Seq.forall2 predicate list1 list2
-
- let iter action list =
- check list
- Seq.iter action list
diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs
new file mode 100644
index 00000000..5e3356ff
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs
@@ -0,0 +1,1173 @@
+[]
+module FSharp.Collections.Immutable.IIndexedList
+
+open System
+open System.Collections.Generic
+open System.Collections.Immutable
+open System.Linq
+
+// The interface has no members of ImmutableList such as ConvertAll or GetRange, so the functions take a snapshot of the
+// elements, apply an Array function or a shared algorithm and build the result. A result with the element type of the
+// input keeps the implementation of the input: it comes from Clear and AddRange or another member of the input. The
+// checks of ListChecks throw what the List functions throw (ADR 0001, D5 and D6).
+
+/// A list of the implementation of the input with the given elements.
+let private rebuild (list : IIndexedList<'T>) (items : seq<'T>) = list.Clear().AddRange items
+
+/// A range of a list in a list of its implementation: the list itself for the whole list. An indexed list copies the range
+/// as IndexedList does, in O(log n + count); the other implementations are read from their start through the interface.
+let private keepRange (list : IIndexedList<'T>) (start : int) (count : int) =
+ if count = list.Count then
+ list
+ elif count = 0 then
+ list.Clear ()
+ else
+ match list with
+ | :? IndexedList<'T> as indexed -> IndexedList.sub indexed start count :> IIndexedList<'T>
+ | _ -> list.Clear().AddRange(Enumerable.Take (Enumerable.Skip (list, start), count))
+
+/// The index and the first element that satisfies the predicate, or ValueNone.
+let private firstMatch (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ use enumerator = list.GetEnumerator ()
+ let mutable result = ValueNone
+ let mutable index = 0
+
+ while result.IsNone && enumerator.MoveNext () do
+ let item = enumerator.Current
+
+ if predicate item then
+ result <- ValueSome (struct (index, item))
+
+ index <- index + 1
+
+ result
+
+/// The index and the last element that satisfies the predicate, or ValueNone. An indexed list searches from its end, where
+/// the other implementations are copied first, as List searches a copy from the end too: reading the copy takes O(n) when
+/// the last element satisfies the predicate (about 3 ms against 0.005 ms for 100 000 integers), and the indexer of an
+/// implementation can take O(n) for each element, so the copy is the safe way to go back through the interface.
+let private lastMatch (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ match list with
+ | :? IndexedList<'T> as indexed ->
+ let index = indexed.FindLastIndex (Predicate predicate)
+
+ if index < 0 then
+ ValueNone
+ else
+ ValueSome (struct (index, indexed[index]))
+ | _ ->
+ let array = Snapshot.ofSeq (nameof list) list
+ let mutable index = array.Length - 1
+
+ while index >= 0 && not (predicate array[index]) do
+ index <- index - 1
+
+ if index < 0 then
+ ValueNone
+ else
+ ValueSome (struct (index, array[index]))
+
+/// The first value that the chooser returns for the elements, in their order.
+let private firstChoice (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) =
+ use enumerator = list.GetEnumerator ()
+ let mutable result = ValueNone
+
+ while result.IsNone && enumerator.MoveNext () do
+ result <- chooser enumerator.Current
+
+ result
+
+// ---- Properties ----
+
+[]
+let isEmpty (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Count = 0
+
+[]
+let length (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Count
+
+// ---- Access ----
+
+[]
+let exactlyOne (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ // List.exactlyOne reports its input under the name "source"
+ ListChecks.exactlyOne "source" list.Count
+ list[0]
+
+[]
+let head (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.notEmpty (nameof list) list.Count
+ list[0]
+
+[]
+let item index (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.item (nameof index) index list.Count
+ list[index]
+
+[]
+let last (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.notEmpty (nameof list) list.Count
+ list[list.Count - 1]
+
+[]
+let tail (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.notEmpty (nameof list) list.Count
+ list.RemoveAt 0
+
+[]
+let tryExactlyOne (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count = 1 then ValueSome list[0] else ValueNone
+
+[]
+let tryHead (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count = 0 then ValueNone else ValueSome list[0]
+
+[]
+let tryItem index (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ if index < 0 || index >= list.Count then
+ ValueNone
+ else
+ ValueSome list[index]
+
+[]
+let tryLast (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ if list.Count = 0 then
+ ValueNone
+ else
+ ValueSome list[list.Count - 1]
+
+[]
+let tryTail (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count = 0 then
+ ValueNone
+ else
+ ValueSome (list.RemoveAt 0)
+
+[]
+let tryUncons (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ if list.Count = 0 then
+ ValueNone
+ else
+ ValueSome (struct (list[0], list.RemoveAt 0))
+
+[]
+let uncons (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ // An empty list throws what List.head throws
+ ListChecks.notEmpty (nameof list) list.Count
+ struct (list[0], list.RemoveAt 0)
+
+[]
+[]
+let (|Cons|_|) (list : IIndexedList<'T>) = tryUncons list
+
+[]
+let (|Nil|_|) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Count = 0
+
+// ---- Search ----
+
+[]
+let contains (value : 'T) (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ // F# equality with the value on the left, as List.contains compares
+ Seq.contains value source
+
+[]
+let exists (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ Seq.exists predicate list
+
+// exists2, forall2 and compareWith can decide before the end. Two indexed lists are walked together by the functions of
+// IndexedList, which read the elements up to the pair that decides. The other implementations are copied first, because
+// an enumerator reached through the interface takes more than twice as long as the copy on a walk to the end (see
+// IndexedList).
+
+[]
+let exists2 predicate (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ match list1, list2 with
+ | (:? IndexedList<'T1> as first), (:? IndexedList<'T2> as second) -> IndexedList.exists2 predicate first second
+ | _ -> ListArrays.exists2 (nameof list2) predicate (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2)
+
+[]
+let find (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstMatch predicate list with
+ | ValueSome (struct (_, item)) -> item
+ | ValueNone -> Raise.indexNotFound ()
+
+[]
+let findBack (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ match lastMatch predicate list with
+ | ValueSome (struct (_, item)) -> item
+ | ValueNone -> Raise.indexNotFound ()
+
+[]
+let findIndex (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstMatch predicate list with
+ | ValueSome (struct (index, _)) -> index
+ | ValueNone -> Raise.indexNotFound ()
+
+[]
+let findIndexBack (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ match lastMatch predicate list with
+ | ValueSome (struct (index, _)) -> index
+ | ValueNone -> Raise.indexNotFound ()
+
+[]
+let forall (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ Seq.forall predicate list
+
+[]
+let forall2 predicate (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ match list1, list2 with
+ | (:? IndexedList<'T1> as first), (:? IndexedList<'T2> as second) -> IndexedList.forall2 predicate first second
+ | _ ->
+ ListArrays.forall2
+ (nameof list1)
+ (nameof list2)
+ predicate
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+
+[]
+let indexOf (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ImmutableList.IndexOf (list, value)
+
+[]
+let indexOfRange startIndex count (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count
+ ImmutableList.IndexOf (list, value, startIndex, count)
+
+[]
+let indexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count
+ list.IndexOf (value, startIndex, count, comparer)
+
+[]
+let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ImmutableList.IndexOf (list, value, comparer)
+
+[]
+let lastIndexOf (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ImmutableList.LastIndexOf (list, value)
+
+[]
+let lastIndexOfRange startIndex count (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count
+
+ // An empty range has no equal element; the member of ImmutableList rejects a start index of Int32.MaxValue for it
+ if count = 0 then
+ -1
+ else
+ ImmutableList.LastIndexOf (list, value, startIndex, count)
+
+[]
+let lastIndexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count
+
+ if count = 0 then
+ -1
+ else
+ list.LastIndexOf (value, startIndex, count, comparer)
+
+[]
+let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ImmutableList.LastIndexOf (list, value, comparer)
+
+[]
+let pick (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstChoice chooser list with
+ | ValueSome value -> value
+ | ValueNone -> Raise.indexNotFound ()
+
+[]
+let tryFind (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstMatch predicate list with
+ | ValueSome (struct (_, item)) -> ValueSome item
+ | ValueNone -> ValueNone
+
+[]
+let tryFindBack (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ match lastMatch predicate list with
+ | ValueSome (struct (_, item)) -> ValueSome item
+ | ValueNone -> ValueNone
+
+[]
+let tryFindIndex (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstMatch predicate list with
+ | ValueSome (struct (index, _)) -> ValueSome index
+ | ValueNone -> ValueNone
+
+[]
+let tryFindIndexBack (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ match lastMatch predicate list with
+ | ValueSome (struct (index, _)) -> ValueSome index
+ | ValueNone -> ValueNone
+
+[]
+let tryPick (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ firstChoice chooser list
+
+// ---- Transformation ----
+
+[]
+let add (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Add value
+
+[]
+let allPairs (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ // Every element of list1 meets every element of list2, so list2 is read again for each of them. Two snapshots read
+ // each list once and then index arrays; reading list2 through an enumerator for each element of list1 took 1.4 to
+ // 2.5 times as long, and a snapshot of list2 alone was not clearly faster (see IndexedList.allPairs).
+ Arrays.allPairs structPair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2)
+ |> IndexedList.ofArray
+
+[]
+let append (list1 : IIndexedList<'T>) (list2 : IIndexedList<'T>) =
+ checkNotNull (nameof list1) list1
+ checkNotNull (nameof list2) list2
+ list1.AddRange list2
+
+[]
+let choose (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ // The number of chosen elements is not known in advance, so they wait in a ResizeArray and CreateRange builds the
+ // balanced tree once. A builder rebalances its tree after every Add and took 2.3 times as long for 100 000
+ // elements; there is no tree of an input to join to its tree here, as collect and concat have (see Collectors).
+ let chosen = ResizeArray<'U>()
+
+ for item in list do
+ match chooser item with
+ | ValueSome value -> chosen.Add value
+ | ValueNone -> ()
+
+ ImmutableList.CreateRange chosen
+
+[]
+let chunkBySize chunkSize (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.positive (nameof chunkSize) chunkSize
+
+ // One copy of the list and one tree for each chunk. Cutting the chunks from the copy with spans, instead of the arrays
+ // that Array.chunkBySize allocates, was between 1.2 times faster and as fast for chunks of 2, 100 and 10 000 elements
+ // of a list of a million, so the array function stays.
+ Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof list) list)
+ |> Seq.map IndexedList.ofArray
+ |> IndexedList.ofSeq
+
+[]
+let collect (mapping : 'T -> IIndexedList<'U>) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ // The collector copies the short results and joins the long indexed lists to its tree as they are (see Collectors)
+ let collector = Collectors.ListCollector<'U>()
+
+ for item in list do
+ collector.Add (mapping item)
+
+ collector.ToImmutable ()
+
+[]
+let concat (lists : seq>) =
+ checkNotNullOrDefaultSeq (nameof lists) lists
+ // The collector copies the short lists and joins the long indexed lists to its tree as they are (see Collectors)
+ let collector = Collectors.ListCollector<'T>()
+
+ for list in lists do
+ // A null list is a null input, and so is a default flat list, which implements the interface and fails when it is
+ // read, as a default element is for FlatList.concat (ADR 0001, D6)
+ checkNotNullOrDefaultSeq (nameof lists) list
+ collector.Add list
+
+ collector.ToImmutable ()
+
+[]
+let cons (value : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Insert (0, value)
+
+[]
+let filter (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ let array = Snapshot.ofSeq (nameof list) list
+ rebuild list (Array.filter predicate array)
+
+[]
+let indexed (list : IIndexedList<'T>) =
+ Arrays.indexed structPair (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let insertAt index (value : 'T) (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.insertAt (nameof index) index source.Count
+ source.Insert (index, value)
+
+[]
+let insertManyAt index (values : seq<'T>) (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ // List checks the index before it reads the values
+ ListChecks.insertAt (nameof index) index source.Count
+ checkNotNullOrDefaultSeq (nameof values) values
+ source.InsertRange (index, values)
+
+[]
+let map (mapping : 'T -> 'U) (list : IIndexedList<'T>) =
+ Array.map mapping (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let map2 (mapping : 'T1 -> 'T2 -> 'U) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ ListArrays.map2
+ (nameof list1)
+ (nameof list2)
+ mapping
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+ |> IndexedList.ofArray
+
+[]
+let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (list3 : IIndexedList<'T3>) =
+ ListArrays.map3
+ (nameof list1)
+ (nameof list2)
+ (nameof list3)
+ mapping
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+ (Snapshot.ofSeq (nameof list3) list3)
+ |> IndexedList.ofArray
+
+[]
+let mapi (mapping : int -> 'T -> 'U) (list : IIndexedList<'T>) =
+ Array.mapi mapping (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ ListArrays.mapi2
+ (nameof list1)
+ (nameof list2)
+ mapping
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+ |> IndexedList.ofArray
+
+[]
+let pairwise (list : IIndexedList<'T>) =
+ Arrays.pairwise structPair (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let permute indexMap (list : IIndexedList<'T>) =
+ // List.permute applies the algorithm of Array.permute to a copy of the list, with the same checks
+ let array = Snapshot.ofSeq (nameof list) list
+ rebuild list (Array.permute indexMap array)
+
+[]
+let remove (item : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ImmutableList.Remove (list, item)
+
+[]
+let removeAt index (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.removeAt (nameof index) index source.Count
+ source.RemoveAt index
+
+[]
+let removeMany (items : seq<'T>) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ checkNotNullOrDefaultSeq (nameof items) items
+ ImmutableList.RemoveRange (list, items)
+
+[]
+let removeManyAt index count (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.removeManyAt (nameof index) index count source.Count
+ // List walks the first index + count elements, so it removes nothing for a count that is not positive or for a sum
+ // that overflows to a negative number
+ if count <= 0 || index + count < 0 then
+ source
+ else
+ source.RemoveRange (index, count)
+
+[]
+let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ checkNotNullOrDefaultSeq (nameof items) items
+ list.RemoveRange (items, comparer)
+
+[]
+let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Remove (item, comparer)
+
+[]
+let replace (oldValue : 'T) (newValue : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ImmutableList.Replace (list, oldValue, newValue)
+
+[]
+let replaceWith (comparer : IEqualityComparer<'T>) (oldValue : 'T) (newValue : 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Replace (oldValue, newValue, comparer)
+
+[]
+let rev (list : IIndexedList<'T>) =
+ // The copy is fresh, so it is reversed in place, as IndexedList.rev does (ADR 0001, D5)
+ let array = Snapshot.ofSeq (nameof list) list
+ Array.Reverse array
+ rebuild list array
+
+[]
+let transpose (lists : seq>) =
+ checkNotNullOrDefaultSeq (nameof lists) lists
+
+ let arrays =
+ lists
+ |> Seq.map (fun list ->
+ // A null list, or a default flat list that implements the interface, is a null input named after the sequence
+ checkNotNullOrDefaultSeq (nameof lists) list
+ Snapshot.ofSeq (nameof lists) list
+ )
+ |> Seq.toArray
+
+ // List.transpose names the lists "list.[0]", "list.[1]", …; once their lengths agree, Array.transpose agrees with it
+ ListChecks.transpose "list" (arrays |> Array.map _.Length)
+
+ Array.transpose arrays
+ |> Seq.map IndexedList.ofArray
+ |> IndexedList.ofSeq
+
+[]
+let unzip (list : IIndexedList) =
+ let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof list) list)
+ struct (IndexedList.ofArray first, IndexedList.ofArray second)
+
+[]
+let unzip3 (list : IIndexedList) =
+ let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof list) list)
+ struct (IndexedList.ofArray first, IndexedList.ofArray second, IndexedList.ofArray third)
+
+[]
+let updateAt index (value : 'T) (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.removeAt (nameof index) index source.Count
+ source.SetItem (index, value)
+
+[]
+let where (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ let array = Snapshot.ofSeq (nameof list) list
+ rebuild list (Array.filter predicate array)
+
+[]
+let windowed windowSize (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.positive (nameof windowSize) windowSize
+
+ Array.windowed windowSize (Snapshot.ofSeq (nameof list) list)
+ |> Seq.map IndexedList.ofArray
+ |> IndexedList.ofSeq
+
+[]
+let zip (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ checkNotNull (nameof list1) list1
+ checkNotNull (nameof list2) list2
+ ListChecks.sameLengths (nameof list1) list1.Count (nameof list2) list2.Count
+
+ Arrays.zip structPair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2)
+ |> IndexedList.ofArray
+
+[]
+let zip3 (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (list3 : IIndexedList<'T3>) =
+ checkNotNull (nameof list1) list1
+ checkNotNull (nameof list2) list2
+ checkNotNull (nameof list3) list3
+ ListChecks.zip3 (nameof list1) list1.Count (nameof list2) list2.Count (nameof list3) list3.Count
+
+ Arrays.zip3
+ structTriple
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+ (Snapshot.ofSeq (nameof list3) list3)
+ |> IndexedList.ofArray
+
+// ---- Folding ----
+
+// The inline functions can use public members only, so they check the list themselves; the empty list throws what
+// List.average and List.averageBy throw, with the name "source"
+[]
+let inline average (list : IIndexedList< ^T >) : ^T =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ if list.Count = 0 then
+ invalidArg "source" ErrorStrings.InputSequenceEmpty
+
+ let mutable sum = LanguagePrimitives.GenericZero< ^T>
+
+ for item in list do
+ sum <- Checked.(+) sum item
+
+ LanguagePrimitives.DivideByInt< ^T> sum list.Count
+
+[]
+let inline averageBy ([] projection : 'T -> ^U) (list : IIndexedList<'T>) : ^U =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ if list.Count = 0 then
+ invalidArg "source" ErrorStrings.InputSequenceEmpty
+
+ let mutable sum = LanguagePrimitives.GenericZero< ^U>
+
+ for item in list do
+ sum <- Checked.(+) sum (projection item)
+
+ LanguagePrimitives.DivideByInt< ^U> sum list.Count
+
+[]
+let compareWith (comparer : 'T -> 'T -> int) (list1 : IIndexedList<'T>) (list2 : IIndexedList<'T>) =
+ match list1, list2 with
+ | (:? IndexedList<'T> as first), (:? IndexedList<'T> as second) -> IndexedList.compareWith comparer first second
+ | _ ->
+ // Array.compareWith compares as List.compareWith does: the first difference, then the lengths
+ Array.compareWith comparer (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2)
+
+[]
+let fold (folder : 'State -> 'T -> 'State) (state : 'State) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ let mutable state = state
+
+ for item in list do
+ state <- folder state item
+
+ state
+
+[]
+let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ ListArrays.fold2
+ (nameof list1)
+ (nameof list2)
+ folder
+ state
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+
+[]
+let foldBack (folder : 'T -> 'State -> 'State) (list : IIndexedList<'T>) (state : 'State) =
+ Array.foldBack folder (Snapshot.ofSeq (nameof list) list) state
+
+[]
+let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (state : 'State) =
+ checkNotNull (nameof list1) list1
+ checkNotNull (nameof list2) list2
+ // List.foldBack2 checks the lengths before it calls the folder
+ ListChecks.foldBack2 (nameof list1) list1.Count (nameof list2) list2.Count
+ Array.foldBack2 folder (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) state
+
+[]
+let iter (action : 'T -> unit) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ for item in list do
+ action item
+
+[]
+let iter2 (action : 'T1 -> 'T2 -> unit) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ ListArrays.iter2
+ (nameof list1)
+ (nameof list2)
+ action
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+
+[]
+let iteri (action : int -> 'T -> unit) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ let mutable index = 0
+
+ for item in list do
+ action index item
+ index <- index + 1
+
+[]
+let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) =
+ ListArrays.iteri2
+ (nameof list1)
+ (nameof list2)
+ action
+ (Snapshot.ofSeq (nameof list1) list1)
+ (Snapshot.ofSeq (nameof list2) list2)
+
+[]
+let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (list : IIndexedList<'T>) =
+ let source = Snapshot.ofSeq (nameof list) list
+ let results = Array.zeroCreate source.Length
+ let mutable state = state
+
+ for index = 0 to source.Length - 1 do
+ let struct (result, next) = mapping state source[index]
+ results[index] <- result
+ state <- next
+
+ struct (IndexedList.ofArray results, state)
+
+[]
+let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (list : IIndexedList<'T>) (state : 'State) =
+ let source = Snapshot.ofSeq (nameof list) list
+ let results = Array.zeroCreate source.Length
+ let mutable state = state
+
+ for index = source.Length - 1 downto 0 do
+ let struct (result, next) = mapping source[index] state
+ results[index] <- result
+ state <- next
+
+ struct (IndexedList.ofArray results, state)
+
+// max, maxBy, min and minBy are inline, as the ones of List are, and compare as they do; see IndexedList for the reasons.
+
+[]
+let inline max (list : IIndexedList<'T>) : 'T =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ if list.Count = 0 then
+ invalidArg (nameof list) ErrorStrings.InputSequenceEmpty
+
+ let mutable first = true
+ let mutable greatest = Unchecked.defaultof<'T>
+
+ for item in list do
+ if first then
+ greatest <- item
+ first <- false
+ elif item > greatest then
+ greatest <- item
+
+ greatest
+
+[]
+let inline maxBy ([] projection : 'T -> 'U) (list : IIndexedList<'T>) : 'T =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ if list.Count = 0 then
+ invalidArg (nameof list) ErrorStrings.InputSequenceEmpty
+
+ let mutable first = true
+ let mutable greatest = Unchecked.defaultof<'T>
+ let mutable greatestKey = Unchecked.defaultof<'U>
+
+ for item in list do
+ let key = projection item
+
+ if first then
+ greatest <- item
+ greatestKey <- key
+ first <- false
+ elif key > greatestKey then
+ greatest <- item
+ greatestKey <- key
+
+ greatest
+
+[]
+let inline min (list : IIndexedList<'T>) : 'T =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ if list.Count = 0 then
+ invalidArg (nameof list) ErrorStrings.InputSequenceEmpty
+
+ let mutable first = true
+ let mutable smallest = Unchecked.defaultof<'T>
+
+ for item in list do
+ if first then
+ smallest <- item
+ first <- false
+ elif item < smallest then
+ smallest <- item
+
+ smallest
+
+[]
+let inline minBy ([] projection : 'T -> 'U) (list : IIndexedList<'T>) : 'T =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ if list.Count = 0 then
+ invalidArg (nameof list) ErrorStrings.InputSequenceEmpty
+
+ let mutable first = true
+ let mutable smallest = Unchecked.defaultof<'T>
+ let mutable smallestKey = Unchecked.defaultof<'U>
+
+ for item in list do
+ let key = projection item
+
+ if first then
+ smallest <- item
+ smallestKey <- key
+ first <- false
+ elif key < smallestKey then
+ smallest <- item
+ smallestKey <- key
+
+ smallest
+
+[]
+let reduce (reduction : 'T -> 'T -> 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.notEmpty (nameof list) list.Count
+ Seq.reduce reduction list
+
+[]
+let reduceBack (reduction : 'T -> 'T -> 'T) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.notEmpty (nameof list) list.Count
+ Array.reduceBack reduction (Snapshot.ofSeq (nameof list) list)
+
+[]
+let scan (folder : 'State -> 'T -> 'State) (state : 'State) (list : IIndexedList<'T>) =
+ Array.scan folder state (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let scanBack (folder : 'T -> 'State -> 'State) (list : IIndexedList<'T>) (state : 'State) =
+ Array.scanBack folder (Snapshot.ofSeq (nameof list) list) state
+ |> IndexedList.ofArray
+
+[]
+let inline sum (list : IIndexedList< ^T >) : ^T =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ let mutable sum = LanguagePrimitives.GenericZero< ^T>
+
+ for item in list do
+ sum <- Checked.(+) sum item
+
+ sum
+
+[]
+let inline sumBy ([] projection : 'T -> ^U) (list : IIndexedList<'T>) : ^U =
+ if obj.ReferenceEquals (list, null) then
+ nullArg (nameof list)
+
+ let mutable sum = LanguagePrimitives.GenericZero< ^U>
+
+ for item in list do
+ sum <- Checked.(+) sum (projection item)
+
+ sum
+
+// ---- Partitioning and grouping ----
+
+[]
+let countBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) =
+ Arrays.countBy structPair projection (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let distinct (list : IIndexedList<'T>) =
+ let array = Snapshot.ofSeq (nameof list) list
+ rebuild list (Array.distinct array)
+
+[]
+let distinctBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) =
+ let array = Snapshot.ofSeq (nameof list) list
+ rebuild list (Array.distinctBy projection array)
+
+[]
+let except (itemsToExclude : seq<'T>) (list : IIndexedList<'T>) =
+ // List.except checks the items to exclude first and does not read them for an empty list
+ checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude
+ checkNotNull (nameof list) list
+
+ if list.Count = 0 then
+ list
+ else
+ rebuild list (Array.except itemsToExclude (Snapshot.ofSeq (nameof list) list))
+
+[]
+let groupBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) =
+ Arrays.groupBy (fun key items -> struct (key, IndexedList.ofArray items)) projection (Snapshot.ofSeq (nameof list) list)
+ |> IndexedList.ofArray
+
+[]
+let partition (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof list) list)
+ struct (rebuild list accepted, rebuild list rejected)
+
+[]
+let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IIndexedList<'T>) =
+ let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof list) list)
+ struct (IndexedList.ofArray first, IndexedList.ofArray second)
+
+[]
+let skip count (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.skip (nameof count) count list.Count
+ let count = Operators.max count 0
+ keepRange list count (list.Count - count)
+
+[]
+let skipLast count (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ // The checks of List.skip
+ ListChecks.skip (nameof count) count list.Count
+ keepRange list 0 (list.Count - Operators.max count 0)
+
+[]
+let skipWhile (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstMatch (fun item -> not (predicate item)) list with
+ | ValueSome (struct (index, _)) -> keepRange list index (list.Count - index)
+ | ValueNone -> list.Clear ()
+
+[]
+let splitAt index (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.splitAt (nameof index) index list.Count
+ struct (keepRange list 0 index, keepRange list index (list.Count - index))
+
+[]
+let splitInto count (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.positive (nameof count) count
+
+ Array.splitInto count (Snapshot.ofSeq (nameof list) list)
+ |> Seq.map IndexedList.ofArray
+ |> IndexedList.ofSeq
+
+[]
+let take count (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ ListChecks.take (nameof count) count list.Count
+ keepRange list 0 count
+
+[]
+let takeLast count (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ // The checks of List.take
+ ListChecks.take (nameof count) count list.Count
+ keepRange list (list.Count - count) count
+
+[]
+let takeWhile (predicate : 'T -> bool) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ match firstMatch (fun item -> not (predicate item)) list with
+ | ValueSome (struct (index, _)) -> keepRange list 0 index
+ | ValueNone -> list
+
+[]
+let truncate count (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ keepRange list 0 (Operators.max 0 (Operators.min count list.Count))
+
+// ---- Sorting ----
+
+// The sorts are stable, as the sorts of List are (ADR 0001, D5). The functions that compare the elements, or that call
+// the projection in the comparison as List.sortByDescending does, sort with the Seq functions of FSharp.Core, which share
+// the stable sort of List: the same comparisons run in Array.Sort, which wraps the exception of a comparison in an
+// InvalidOperationException (D6). Seq.sortDescending and Seq.sortByDescending are inline and box the elements in every
+// comparison once they are compiled into a generic function, so the two descending sorts call Seq.sortWith through
+// DescendingSorts, with the cached comparer. sortBy and sortWithComparer sort with LINQ, which computes the keys first, as
+// List.sortBy does, and wraps the exception of a comparison as Array.Sort does. A list of one element is returned without
+// a call of the projection or the comparer, as List returns it.
+
+[]
+let sort (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count <= 1 then
+ list
+ else
+ rebuild list (Seq.sort list)
+
+[]
+let sortBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ if list.Count <= 1 then
+ list
+ else
+ rebuild list (list.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray())
+
+[]
+let sortByDescending (projection : 'T -> 'Key) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count <= 1 then
+ list
+ else
+ rebuild list (DescendingSorts.sortByDescending projection list)
+
+[]
+let sortDescending (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count <= 1 then
+ list
+ else
+ rebuild list (DescendingSorts.sortDescending list)
+
+[]
+let sortWith (comparer : 'T -> 'T -> int) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ if list.Count <= 1 then
+ list
+ else
+ rebuild list (Seq.sortWith comparer list)
+
+[]
+let sortWithComparer (comparer : IComparer<'T>) (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+
+ if list.Count <= 1 then
+ list
+ else
+ rebuild list (list.Order(comparer).ToArray())
+
+// ---- Random ----
+
+// The functions draw the random numbers as the List functions do; the ones that build a list apply the Array function
+// to a snapshot after the List checks
+
+[]
+let randomChoice (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.notEmptySequence (nameof source) source.Count
+ source[Random.Shared.Next (0, source.Count)]
+
+[]
+let randomChoiceBy (randomizer : unit -> float) (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.notEmptySequence (nameof source) source.Count
+ source[ListChecks.randomIndex (nameof randomizer) randomizer source.Count]
+
+[]
+let randomChoiceWith (random : Random) (source : IIndexedList<'T>) =
+ checkNotNull (nameof random) random
+ checkNotNull (nameof source) source
+ ListChecks.notEmptySequence (nameof source) source.Count
+ source[random.Next (0, source.Count)]
+
+[]
+let randomChoices count (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.randomChoices (nameof count) count (nameof source) source.Count
+ rebuild source (Array.randomChoices count (Snapshot.ofSeq (nameof source) source))
+
+[]
+let randomChoicesBy (randomizer : unit -> float) count (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.randomChoices (nameof count) count (nameof source) source.Count
+ rebuild source (Array.randomChoicesBy randomizer count (Snapshot.ofSeq (nameof source) source))
+
+[]
+let randomChoicesWith (random : Random) count (source : IIndexedList<'T>) =
+ checkNotNull (nameof random) random
+ checkNotNull (nameof source) source
+ ListChecks.randomChoices (nameof count) count (nameof source) source.Count
+ rebuild source (Array.randomChoicesWith random count (Snapshot.ofSeq (nameof source) source))
+
+[]
+let randomSample count (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.randomSample (nameof count) count (nameof source) source.Count
+ rebuild source (Array.randomSample count (Snapshot.ofSeq (nameof source) source))
+
+[]
+let randomSampleBy (randomizer : unit -> float) count (source : IIndexedList<'T>) =
+ checkNotNull (nameof source) source
+ ListChecks.randomSample (nameof count) count (nameof source) source.Count
+ rebuild source (Array.randomSampleBy randomizer count (Snapshot.ofSeq (nameof source) source))
+
+[]
+let randomSampleWith (random : Random) count (source : IIndexedList<'T>) =
+ checkNotNull (nameof random) random
+ checkNotNull (nameof source) source
+ ListChecks.randomSample (nameof count) count (nameof source) source.Count
+ rebuild source (Array.randomSampleWith random count (Snapshot.ofSeq (nameof source) source))
+
+[]
+let randomShuffle (source : IIndexedList<'T>) =
+ let array = Snapshot.ofSeq (nameof source) source
+ Array.randomShuffleInPlace array
+ rebuild source array
+
+[]
+let randomShuffleBy (randomizer : unit -> float) (source : IIndexedList<'T>) =
+ let array = Snapshot.ofSeq (nameof source) source
+ Array.randomShuffleInPlaceBy randomizer array
+ rebuild source array
+
+[]
+let randomShuffleWith (random : Random) (source : IIndexedList<'T>) =
+ checkNotNull (nameof random) random
+ let array = Snapshot.ofSeq (nameof source) source
+ Array.randomShuffleInPlaceWith random array
+ rebuild source array
+
+// ---- Conversion ----
+
+[]
+let toArray (list : IIndexedList<'T>) = Snapshot.ofSeq (nameof list) list
+
+[]
+let toList (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ List.ofSeq list
+
+[]
+let toSeq (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list :> seq<'T>
+
+// ---- Type-specific ----
+
+[]
+let clear (list : IIndexedList<'T>) =
+ checkNotNull (nameof list) list
+ list.Clear ()
diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi
new file mode 100644
index 00000000..9cfc03a1
--- /dev/null
+++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi
@@ -0,0 +1,1808 @@
+///
+/// Functions for , the interface of the immutable lists.
+///
+///
+///
+/// The module mirrors as
+/// does: each function has the semantics, the parameter names,
+/// the argument checks and the exceptions of the function with the same name in that module (ADR 0001), and its sorting
+/// functions are stable. The module has no creation and no builder functions, because an interface cannot create a list.
+///
+///
+/// A function whose result has the element type of its input keeps the implementation of the input: it builds the result
+/// with the members of the input, such as followed by
+/// ,
+/// or
+/// . A function that changes the
+/// element type, or that combines several lists into one, returns an .
+///
+///
+/// Every function throws for a list. A function that
+/// takes a sequence throws it for a sequence and for a default
+/// that is given as a sequence, which would fail with
+/// when the function enumerates it. The complexities in the remarks are
+/// the ones of an .
+///
+///
+/// Functions that return an optional value return a , and functions
+/// that return tuples return struct tuples. The namespaces and
+/// contain a module with the same name whose functions use options
+/// and reference tuples instead.
+///
+///
+[]
+module FSharp.Collections.Immutable.IIndexedList
+
+open System
+open System.Collections.Generic
+
+// ---- Properties ----
+
+/// Returns whether the list has no elements.
+/// The input list.
+/// when the list is empty.
+/// O(1).
+[]
+val isEmpty : list : IIndexedList<'T> -> bool
+
+/// Returns the number of elements of the list.
+/// The input list.
+/// The length of the list.
+/// O(1).
+[]
+val length : list : IIndexedList<'T> -> int
+
+// ---- Access ----
+
+/// Returns the only element of the list.
+/// The input list.
+/// The only element.
+/// Thrown when the list does not have exactly one element.
+/// O(1).
+[]
+val exactlyOne : list : IIndexedList<'T> -> 'T
+
+/// Returns the first element of the list.
+/// The input list.
+/// The first element.
+/// Thrown when the list is empty.
+/// O(log n).
+[]
+val head : list : IIndexedList<'T> -> 'T
+
+/// Returns the element at an index.
+/// The index of the element.
+/// The input list.
+/// The element at the index.
+/// Thrown when the index is negative or not less than the length.
+/// O(log n).
+[]
+val item : index : int -> list : IIndexedList<'T> -> 'T
+
+/// Returns the last element of the list.
+/// The input list.
+/// The last element.
+/// Thrown when the list is empty.
+/// O(log n).
+[]
+val last : list : IIndexedList<'T> -> 'T
+
+///
+/// Returns the list without its first element.
+///
+/// The input list.
+/// The elements after the first one, in a list of the implementation of the input.
+/// Thrown when the list is empty.
+///
+/// O(log n). The function wraps .
+///
+[]
+val tail : list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns the only element of the list, or ValueNone when it does not have exactly one element.
+///
+/// The input list.
+/// The only element, or ValueNone.
+///
+/// O(1).
+///
+[]
+val tryExactlyOne : list : IIndexedList<'T> -> 'T voption
+
+///
+/// Returns the first element of the list, or ValueNone when it is empty.
+///
+/// The input list.
+/// The first element, or ValueNone.
+///
+/// O(log n).
+///
+[]
+val tryHead : list : IIndexedList<'T> -> 'T voption
+
+///
+/// Returns the element at an index, or ValueNone when the index is out of range.
+///
+/// The index of the element.
+/// The input list.
+/// The element at the index, or ValueNone.
+///
+/// O(log n).
+///
+[]
+val tryItem : index : int -> list : IIndexedList<'T> -> 'T voption
+
+///
+/// Returns the last element of the list, or ValueNone when it is empty.
+///
+/// The input list.
+/// The last element, or ValueNone.
+///
+/// O(log n).
+///
+[]
+val tryLast : list : IIndexedList<'T> -> 'T voption
+
+///
+/// Returns the list without its first element, or ValueNone when it is empty.
+///
+/// The input list.
+/// The elements after the first one, in a list of the implementation of the input, or ValueNone.
+///
+/// O(log n).
+///
+[]
+val tryTail : list : IIndexedList<'T> -> IIndexedList<'T> voption
+
+///
+/// Returns the first element and the other elements, or ValueNone when the list is empty.
+///
+/// The input list.
+/// The first element and the elements after it, or ValueNone.
+///
+/// O(log n).
+///
+[]
+val tryUncons : list : IIndexedList<'T> -> struct ('T * IIndexedList<'T>) voption
+
+///
+/// Returns the first element and the other elements.
+///
+/// The input list.
+/// The first element and the elements after it, in a list of the implementation of the input.
+/// Thrown when the list is empty.
+///
+/// O(log n). The function throws what
+/// throws.
+///
+[]
+val uncons : list : IIndexedList<'T> -> struct ('T * IIndexedList<'T>)
+
+///
+/// Matches a list with a first element, and returns that element and the other elements.
+///
+/// The input list.
+/// The first element and the elements after it, or ValueNone for an empty list.
+///
+/// O(log n). A struct partial pattern, so that a match allocates no option.
+///
+[]
+[]
+val (|Cons|_|) : list : IIndexedList<'T> -> struct ('T * IIndexedList<'T>) voption
+
+/// Matches an empty list.
+/// The input list.
+/// when the list is empty.
+/// O(1).
+[]
+val (|Nil|_|) : list : IIndexedList<'T> -> bool
+
+// ---- Search ----
+
+///
+/// Returns whether the list contains an element equal to a value, in F# structural equality.
+///
+/// The value to find.
+/// The input list.
+/// when an element equals the value.
+///
+/// O(n).
+///
+[]
+val contains : value : 'T -> source : IIndexedList<'T> -> bool when 'T : equality
+
+/// Returns whether any element satisfies the predicate.
+/// The function to test the elements.
+/// The input list.
+/// when an element satisfies the predicate.
+/// O(n).
+[]
+val exists : predicate : ('T -> bool) -> list : IIndexedList<'T> -> bool
+
+///
+/// Returns whether any pair of elements at the same index satisfies the predicate.
+///
+/// The function to test the pairs.
+/// The first list.
+/// The second list.
+/// when a pair satisfies the predicate.
+/// Thrown when no pair satisfies the predicate and the lists have different lengths.
+///
+/// O(n). Two indexed lists are walked together, and the walk stops at the first pair that satisfies the predicate; the
+/// other implementations are copied first.
+///
+[]
+val exists2 : predicate : ('T1 -> 'T2 -> bool) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> bool
+
+/// Returns the first element that satisfies the predicate.
+/// The function to test the elements.
+/// The input list.
+/// The first element that satisfies the predicate.
+/// Thrown when no element satisfies the predicate.
+/// O(n).
+[]
+val find : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T
+
+/// Returns the last element that satisfies the predicate.
+/// The function to test the elements.
+/// The input list.
+/// The last element that satisfies the predicate.
+/// Thrown when no element satisfies the predicate.
+///
+/// O(n). An indexed list is searched from its end, and the other implementations in a copy.
+///
+[]
+val findBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T
+
+///
+/// Returns the index of the first element that satisfies the predicate.
+///
+/// The function to test the elements.
+/// The input list.
+/// The index of the first element that satisfies the predicate.
+/// Thrown when no element satisfies the predicate.
+///
+/// O(n).
+///
+[]
+val findIndex : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the last element that satisfies the predicate.
+///
+/// The function to test the elements.
+/// The input list.
+/// The index of the last element that satisfies the predicate.
+/// Thrown when no element satisfies the predicate.
+///
+/// O(n). An indexed list is searched from its end, and the other implementations in a copy.
+///
+[]
+val findIndexBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int
+
+/// Returns whether all elements satisfy the predicate.
+/// The function to test the elements.
+/// The input list.
+/// when every element satisfies the predicate.
+/// O(n).
+[]
+val forall : predicate : ('T -> bool) -> list : IIndexedList<'T> -> bool
+
+///
+/// Returns whether all pairs of elements at the same index satisfy the predicate.
+///
+/// The function to test the pairs.
+/// The first list.
+/// The second list.
+/// when every pair satisfies the predicate.
+/// Thrown when every pair satisfies the predicate and the lists have different lengths.
+///
+/// O(n). Two indexed lists are walked together, and the walk stops at the first pair that does not satisfy the predicate;
+/// the other implementations are copied first.
+///
+[]
+val forall2 : predicate : ('T1 -> 'T2 -> bool) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> bool
+
+///
+/// Returns the index of the first element equal to a value, in .
+///
+/// The value to find.
+/// The input list.
+/// The index of the first equal element, or -1.
+///
+/// O(n). The function wraps
+/// .
+///
+[]
+val indexOf : value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the first element of a range that equals a value, in
+/// .
+///
+/// The index of the first element of the range.
+/// The number of elements of the range.
+/// The value to find.
+/// The input list.
+/// The index in the list, not in the range, of the first equal element of the range, or -1.
+///
+/// Thrown when or is negative, or the range extends beyond the end
+/// of the list.
+///
+///
+/// O(log n + count). The function checks the range for every implementation of the interface, and wraps
+/// .
+///
+[]
+val indexOfRange : startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the first element of a range that a given equality comparer finds equal to a value.
+///
+/// The equality comparer.
+/// The index of the first element of the range.
+/// The number of elements of the range.
+/// The value to find.
+/// The input list.
+/// The index in the list, not in the range, of the first equal element of the range, or -1.
+///
+/// Thrown when or is negative, or the range extends beyond the end
+/// of the list.
+///
+///
+/// O(log n + count). The function checks the range for every implementation of the interface, and wraps
+/// .
+///
+[]
+val indexOfRangeWith :
+ comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the first element equal to a value in a given equality comparer.
+///
+/// The equality comparer.
+/// The value to find.
+/// The input list.
+/// The index of the first equal element, or -1.
+///
+/// O(n). The function wraps
+/// .
+///
+[]
+val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the last element equal to a value, in .
+///
+/// The value to find.
+/// The input list.
+/// The index of the last equal element, or -1.
+///
+/// O(n). The function wraps
+/// .
+///
+[]
+val lastIndexOf : value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the last element of a range that equals a value, in
+/// . The search walks backward from the index of the
+/// last element of the range.
+///
+/// The index of the last element of the range, where the search starts.
+/// The number of elements of the range, which ends at .
+/// The value to find.
+/// The input list.
+/// The index in the list, not in the range, of the last equal element of the range, or -1.
+///
+/// Thrown when or is negative, exceeds the
+/// length, or a range of elements does not lie in the list: its first element, at startIndex - count + 1, is before
+/// the start, or its last element, at , is at or after the length.
+///
+///
+/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function checks
+/// the range for every implementation of the interface, and wraps
+/// .
+///
+[]
+val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the last element of a range that a given equality comparer finds equal to a value. The search
+/// walks backward from the index of the last element of the range.
+///
+/// The equality comparer.
+/// The index of the last element of the range, where the search starts.
+/// The number of elements of the range, which ends at .
+/// The value to find.
+/// The input list.
+/// The index in the list, not in the range, of the last equal element of the range, or -1.
+///
+/// Thrown when or is negative, exceeds the
+/// length, or a range of elements does not lie in the list: its first element, at startIndex - count + 1, is before
+/// the start, or its last element, at , is at or after the length.
+///
+///
+/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function checks
+/// the range for every implementation of the interface, and wraps
+/// .
+///
+[]
+val lastIndexOfRangeWith :
+ comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the index of the last element equal to a value in a given equality comparer.
+///
+/// The equality comparer.
+/// The value to find.
+/// The input list.
+/// The index of the last equal element, or -1.
+///
+/// O(n). The function wraps
+/// .
+///
+[]
+val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> list : IIndexedList<'T> -> int
+
+///
+/// Returns the first value that the chooser returns for the elements.
+///
+/// The function that returns a value, or ValueNone to go on.
+/// The input list.
+/// The first chosen value.
+/// Thrown when the chooser returns ValueNone for every element.
+///
+/// O(n).
+///
+[]
+val pick : chooser : ('T -> 'U voption) -> list : IIndexedList<'T> -> 'U
+
+///
+/// Returns the first element that satisfies the predicate, or ValueNone.
+///
+/// The function to test the elements.
+/// The input list.
+/// The first element that satisfies the predicate, or ValueNone.
+///
+/// O(n).
+///
+[]
+val tryFind : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T voption
+
+///
+/// Returns the last element that satisfies the predicate, or ValueNone.
+///
+/// The function to test the elements.
+/// The input list.
+/// The last element that satisfies the predicate, or ValueNone.
+///
+/// O(n). An indexed list is searched from its end, and the other implementations in a copy.
+///
+[]
+val tryFindBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T voption
+
+///
+/// Returns the index of the first element that satisfies the predicate, or ValueNone.
+///
+/// The function to test the elements.
+/// The input list.
+/// The index of the first element that satisfies the predicate, or ValueNone.
+///
+/// O(n).
+///
+[]
+val tryFindIndex : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int voption
+
+///
+/// Returns the index of the last element that satisfies the predicate, or ValueNone.
+///
+/// The function to test the elements.
+/// The input list.
+/// The index of the last element that satisfies the predicate, or ValueNone.
+///
+/// O(n). An indexed list is searched from its end, and the other implementations in a copy.
+///
+[]
+val tryFindIndexBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int voption
+
+///
+/// Returns the first value that the chooser returns for the elements, or ValueNone.
+///
+/// The function that returns a value, or ValueNone to go on.
+/// The input list.
+/// The first chosen value, or ValueNone.
+///
+/// O(n).
+///
+[]
+val tryPick : chooser : ('T -> 'U voption) -> list : IIndexedList<'T> -> 'U voption
+
+// ---- Transformation ----
+
+///
+/// Returns a list with a value added after the last element.
+///
+/// The value to add.
+/// The input list.
+/// A list of the implementation of the input with the elements and the value.
+///
+/// O(log n). The function wraps .
+///
+[]
+val add : value : 'T -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns every element of the first list paired with every element of the second one.
+///
+/// The first list.
+/// The second list.
+/// The pairs, in the order of the first list and then of the second one.
+///
+/// O(n × m).
+///
+[]
+val allPairs : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList
+
+///
+/// Returns a list of the elements of the first list followed by the elements of the second one.
+///
+/// The first list.
+/// The second list.
+/// The concatenation, in a list of the implementation of the first list.
+///
+/// O(m log(n + m)). The function wraps
+///
+/// of the first list.
+///
+[]
+val append : list1 : IIndexedList<'T> -> list2 : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns the values that the chooser returns for the elements.
+///
+/// The function that returns a value, or ValueNone to skip the element.
+/// The input list.
+/// The chosen values.
+///
+/// O(n).
+///
+[]
+val choose : chooser : ('T -> 'U voption) -> list : IIndexedList<'T> -> IndexedList<'U>
+
+/// Divides the list into chunks of a given size.
+/// The maximum size of a chunk.
+/// The input list.
+/// The chunks; the last one can be smaller.
+/// Thrown when is not positive.
+/// O(n).
+[]
+val chunkBySize : chunkSize : int -> list : IIndexedList<'T> -> IndexedList>
+
+///
+/// Concatenates the lists that a function returns for the elements.
+///
+/// The function that returns a list for each element.
+/// The input list.
+/// The concatenation of the returned lists.
+///
+/// O(n + the total length of the returned lists that have fewer than 16 elements), and O(log m) for each returned
+/// indexed list of at least 16 elements, where m is the length of the result: the result shares the nodes of such a list.
+///
+[]
+val collect : mapping : ('T -> IIndexedList<'U>) -> list : IIndexedList<'T> -> IndexedList<'U>
+
+/// Concatenates a sequence of lists.
+/// The lists.
+/// The concatenation, in an indexed list, because the lists can have different implementations.
+/// Thrown when is or a default flat list, or one of its lists is or a default flat list.
+///
+/// O(the total length of the lists that have fewer than 16 elements, and of the lists that are not indexed lists), and
+/// O(log m) for each indexed list of at least 16 elements, where m is the length of the result: the result shares the
+/// nodes of such a list.
+///
+[]
+val concat : lists : seq> -> IndexedList<'T>
+
+///
+/// Returns a list with a value added before the first element.
+///
+/// The value to add.
+/// The input list.
+/// A list of the implementation of the input with the value and the elements.
+///
+/// O(log n). The function wraps .
+///
+[]
+val cons : value : 'T -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+/// Returns the elements that satisfy the predicate.
+/// The function to test the elements.
+/// The input list.
+/// The elements that satisfy the predicate, in their order, in a list of the implementation of the input.
+/// O(n).
+[]
+val filter : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+/// Returns every element with its index.
+/// The input list.
+/// The pairs of index and element.
+/// O(n).
+[]
+val indexed : list : IIndexedList<'T> -> IndexedList
+
+///
+/// Returns a list with a value inserted at an index.
+///
+/// The index of the inserted value, from 0 to the length.
+/// The value to insert.
+/// The input list.
+/// A list of the implementation of the input with the value.
+/// Thrown when the index is negative or greater than the length.
+///
+/// O(log n). The function wraps .
+///
+[]
+val insertAt : index : int -> value : 'T -> source : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list with values inserted at an index.
+///
+/// The index of the first inserted value, from 0 to the length.
+/// The values to insert.
+/// The input list.
+/// A list of the implementation of the input with the values.
+/// Thrown when the index is negative or greater than the length.
+/// Thrown when is or a default flat list.
+///
+/// O(m log(n + m)). The function wraps
+/// .
+///
+[]
+val insertManyAt : index : int -> values : seq<'T> -> source : IIndexedList<'T> -> IIndexedList<'T>
+
+/// Returns the results of a function applied to each element.
+/// The function to transform the elements, called in the order of the elements.
+/// The input list.
+/// The results, in the order of the elements.
+/// O(n).
+[]
+val map : mapping : ('T -> 'U) -> list : IIndexedList<'T> -> IndexedList<'U>
+
+///
+/// Returns the results of a function applied to the pairs of elements at the same index.
+///
+/// The function to transform the pairs.
+/// The first list.
+/// The second list.
+/// The results.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val map2 : mapping : ('T1 -> 'T2 -> 'U) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList<'U>
+
+///
+/// Returns the results of a function applied to the triples of elements at the same index.
+///
+/// The function to transform the triples.
+/// The first list.
+/// The second list.
+/// The third list.
+/// The results.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val map3 :
+ mapping : ('T1 -> 'T2 -> 'T3 -> 'U) ->
+ list1 : IIndexedList<'T1> ->
+ list2 : IIndexedList<'T2> ->
+ list3 : IIndexedList<'T3> ->
+ IndexedList<'U>
+
+///
+/// Returns the results of a function applied to each element and its index.
+///
+/// The function to transform the elements.
+/// The input list.
+/// The results.
+///
+/// O(n).
+///
+[]
+val mapi : mapping : (int -> 'T -> 'U) -> list : IIndexedList<'T> -> IndexedList<'U>
+
+///
+/// Returns the results of a function applied to the index and the pair of elements at that index.
+///
+/// The function to transform the pairs.
+/// The first list.
+/// The second list.
+/// The results.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList<'U>
+
+/// Returns every element with its successor.
+/// The input list.
+/// The pairs of neighbours; empty for fewer than two elements.
+/// O(n).
+[]
+val pairwise : list : IIndexedList<'T> -> IndexedList
+
+///
+/// Returns the elements moved to the indices that a function computes.
+///
+/// The function that maps the index of each element to its new index.
+/// The input list.
+/// The permuted elements, in a list of the implementation of the input.
+/// Thrown when the function does not compute a permutation.
+///
+/// O(n).
+///
+[]
+val permute : indexMap : (int -> int) -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list without the first element that equals a value, in
+/// .
+///
+/// The value to remove.
+/// The input list.
+/// A list of the implementation of the input without the element.
+///
+///
+/// O(n). An that has no equal element comes back as it is.
+/// The function wraps
+/// .
+///
+///
+/// To remove several values, use
+/// ;
+/// to remove by index, use
+/// ;
+/// to remove the elements that satisfy a predicate, use
+///
+/// with the negated predicate.
+///
+///
+[]
+val remove : item : 'T -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list without the element at an index.
+///
+/// The index of the removed element.
+/// The input list.
+/// A list of the implementation of the input without the element.
+/// Thrown when the index is out of range.
+///
+/// O(log n). The function wraps .
+///
+[]
+val removeAt : index : int -> source : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list without one element for each of the given values, in
+/// .
+///
+/// The values to remove.
+/// The input list.
+/// A list of the implementation of the input without the elements.
+/// Thrown when is or a default flat list.
+///
+///
+/// O(n × m) for m values. Each value removes the first element that equals it and that no earlier value has removed; a
+/// value that equals no remaining element removes nothing, and an
+/// that has none of the values comes back as it is. The
+/// function wraps
+/// .
+///
+///
+/// To remove every element that equals one of the values, and the duplicates of the other elements, use
+/// .
+///
+///
+[]
+val removeMany : items : seq<'T> -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list without a range of elements.
+///
+/// The index of the first removed element.
+///
+/// The number of removed elements. A count that is not positive removes nothing, and so does a count whose sum with the
+/// index exceeds , as in
+/// .
+///
+/// The input list.
+/// A list of the implementation of the input without the range.
+/// Thrown when the index is negative or the range ends beyond the list.
+///
+/// O(count × log n). The function wraps
+/// .
+///
+[]
+val removeManyAt : index : int -> count : int -> source : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list without one element for each of the given values, in a given equality comparer.
+///
+/// The equality comparer.
+/// The values to remove.
+/// The input list.
+/// A list of the implementation of the input without the elements.
+/// Thrown when is or a default flat list.
+///
+/// O(n × m) for m values. Each value removes the first element that the comparer finds equal to it and that no earlier
+/// value has removed; a value that equals no remaining element removes nothing, and an
+/// that has none of the values comes back as it is. The
+/// function wraps
+/// .
+///
+[]
+val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list without the first element that equals a value in a given equality comparer.
+///
+/// The equality comparer.
+/// The value to remove.
+/// The input list.
+/// A list of the implementation of the input without the element.
+///
+/// O(n). An that has no equal element comes back as it is.
+/// The function wraps
+/// .
+///
+[]
+val removeWith : comparer : IEqualityComparer<'T> -> item : 'T -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list with the first element that equals a value replaced by another value, in
+/// .
+///
+/// The value to replace.
+/// The new value.
+/// The input list.
+/// A list of the implementation of the input with the new value in place of the element.
+/// Thrown when no element equals .
+///
+///
+/// O(n). The function wraps
+/// .
+///
+///
+/// To replace every element that equals a value, use
+/// ;
+/// to replace the element at an index, use
+/// .
+///
+///
+[]
+val replace : oldValue : 'T -> newValue : 'T -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns a list with the first element that equals a value in a given equality comparer replaced by another value.
+///
+/// The equality comparer.
+/// The value to replace.
+/// The new value.
+/// The input list.
+/// A list of the implementation of the input with the new value in place of the element.
+///
+/// Thrown when the comparer finds no element equal to .
+///
+///
+/// O(n). The function wraps
+/// .
+///
+[]
+val replaceWith :
+ comparer : IEqualityComparer<'T> -> oldValue : 'T -> newValue : 'T -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns the elements in reverse order.
+///
+/// The input list.
+/// The reversed elements, in a list of the implementation of the input.
+///
+/// O(n). The function copies the elements to an array and reverses it with
+/// , as
+///
+/// does. Then it adds the reversed elements with
+/// to
+/// the empty list that of the input returns.
+///
+[]
+val rev : list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns the transpose of a sequence of lists of the same length.
+///
+/// The lists.
+/// An indexed list whose element i holds the element i of every input list.
+/// Thrown when is or a default flat list, or one of its lists is or a default flat list.
+/// Thrown when the lists have different lengths.
+///
+/// O(the total length).
+///
+[]
+val transpose : lists : seq> -> IndexedList>
+
+/// Splits a list of pairs into two indexed lists.
+/// The input list.
+/// The first components and the second components.
+/// O(n).
+[]
+val unzip : list : IIndexedList -> struct (IndexedList<'T1> * IndexedList<'T2>)
+
+/// Splits a list of triples into three indexed lists.
+/// The input list.
+/// The first, the second and the third components.
+/// O(n).
+[]
+val unzip3 : list : IIndexedList -> struct (IndexedList<'T1> * IndexedList<'T2> * IndexedList<'T3>)
+
+///
+/// Returns a list with the element at an index replaced by a value.
+///
+/// The index of the replaced element.
+/// The new value.
+/// The input list.
+/// A list of the implementation of the input with the new value.
+/// Thrown when the index is out of range.
+///
+/// O(log n). The function wraps .
+///
+[]
+val updateAt : index : int -> value : 'T -> source : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns the elements that satisfy the predicate.
+///
+/// The function to test the elements.
+/// The input list.
+/// The elements that satisfy the predicate, in their order, in a list of the implementation of the input.
+///
+/// O(n). A synonym of .
+///
+[]
+val where : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T>
+
+///
+/// Returns the windows of a given size that slide over the list.
+///
+/// The size of a window.
+/// The input list.
+/// The windows; empty when the list is shorter than a window.
+/// Thrown when is not positive.
+///
+/// O(n × windowSize).
+///
+[]
+val windowed : windowSize : int -> list : IIndexedList<'T> -> IndexedList>
+
+/// Combines two lists into an indexed list of pairs.
+/// The first list.
+/// The second list.
+/// The pairs of elements at the same index.
+/// Thrown when the lists have different lengths.
+/// O(n).
+[]
+val zip : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList
+
+/// Combines three lists into an indexed list of triples.
+/// The first list.
+/// The second list.
+/// The third list.
+/// The triples of elements at the same index.
+/// Thrown when the lists have different lengths.
+/// O(n).
+[]
+val zip3 :
+ list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> list3 : IIndexedList<'T3> -> IndexedList
+
+// ---- Folding ----
+
+/// Returns the average of the elements.
+/// The input list.
+/// The average.
+/// Thrown when the list is empty.
+/// O(n).
+[]
+val inline average :
+ list : IIndexedList< ^T > -> ^T
+ when ^T : (static member (+) : ^T * ^T -> ^T)
+ and ^T : (static member DivideByInt : ^T * int -> ^T)
+ and ^T : (static member Zero : ^T)
+
+///
+/// Returns the average of the results of a function applied to each element.
+///
+/// The function to transform the elements.
+/// The input list.
+/// The average of the results.
+/// Thrown when the list is empty.
+///
+/// O(n).
+///
+[]
+val inline averageBy :
+ projection : ('T -> ^U) -> list : IIndexedList<'T> -> ^U
+ when ^U : (static member (+) : ^U * ^U -> ^U)
+ and ^U : (static member DivideByInt : ^U * int -> ^U)
+ and ^U : (static member Zero : ^U)
+
+/// Compares two lists element by element with a comparer.
+/// The function that compares two elements.
+/// The first list.
+/// The second list.
+///
+/// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal.
+///
+///
+/// O(n). Two indexed lists are walked together, and the walk stops at the first pair of elements that the comparer does
+/// not consider equal; the other implementations are copied first.
+///
+[]
+val compareWith : comparer : ('T -> 'T -> int) -> list1 : IIndexedList<'T> -> list2 : IIndexedList<'T> -> int
+
+///
+/// Applies a function to a state and each element, from the first element to the last one.
+///
+/// The function that computes the next state.
+/// The initial state.
+/// The input list.
+/// The final state.
+///
+/// O(n).
+///
+[]
+val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> list : IIndexedList<'T> -> 'State
+
+///
+/// Applies a function to a state and each pair of elements at the same index, from the first index to the last one.
+///
+/// The function that computes the next state.
+/// The initial state.
+/// The first list.
+/// The second list.
+/// The final state.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val fold2 :
+ folder : ('State -> 'T1 -> 'T2 -> 'State) ->
+ state : 'State ->
+ list1 : IIndexedList<'T1> ->
+ list2 : IIndexedList<'T2> ->
+ 'State
+
+///
+/// Applies a function to each element and a state, from the last element to the first one.
+///
+/// The function that computes the next state.
+/// The input list.
+/// The initial state.
+/// The final state.
+///
+/// O(n).
+///
+[]
+val foldBack : folder : ('T -> 'State -> 'State) -> list : IIndexedList<'T> -> state : 'State -> 'State
+
+///
+/// Applies a function to each pair of elements at the same index and a state, from the last index to the first one.
+///
+/// The function that computes the next state.
+/// The first list.
+/// The second list.
+/// The initial state.
+/// The final state.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val foldBack2 :
+ folder : ('T1 -> 'T2 -> 'State -> 'State) ->
+ list1 : IIndexedList<'T1> ->
+ list2 : IIndexedList<'T2> ->
+ state : 'State ->
+ 'State
+
+/// Applies an action to each element.
+/// The action.
+/// The input list.
+/// O(n).
+[]
+val iter : action : ('T -> unit) -> list : IIndexedList<'T> -> unit
+
+///
+/// Applies an action to each pair of elements at the same index.
+///
+/// The action.
+/// The first list.
+/// The second list.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val iter2 : action : ('T1 -> 'T2 -> unit) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> unit
+
+/// Applies an action to each element and its index.
+/// The action.
+/// The input list.
+/// O(n).
+[]
+val iteri : action : (int -> 'T -> unit) -> list : IIndexedList<'T> -> unit
+
+///
+/// Applies an action to the index and the pair of elements at that index.
+///
+/// The action.
+/// The first list.
+/// The second list.
+/// Thrown when the lists have different lengths.
+///
+/// O(n).
+///
+[]
+val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> unit
+
+///
+/// Maps each element and threads a state through the mapping, from the first element to the last one.
+///
+/// The function that returns the result for an element and the next state.
+/// The initial state.
+/// The input list.
+/// The results and the final state.
+///
+/// O(n).
+///
+[]
+val mapFold :
+ mapping : ('State -> 'T -> struct ('Result * 'State)) ->
+ state : 'State ->
+ list : IIndexedList<'T> ->
+ struct (IndexedList<'Result> * 'State)
+
+///