Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`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.
* `Queue` has every function of the FSharp.Core `List` module that applies to an immutable queue, over the elements in the
order in which they leave the queue, with its semantics and exceptions and with stable sorts, plus `enqueueMany`,
`ofReadOnlySpan`, `ofSpan`, `skipLast`, `sortWithComparer`, `takeLast`, `tryTail`, `tryUncons` and `uncons`. Its
parameters and exceptions name the queues `queue`, `queue1`, `queue2`, `queue3` and `queues` where `List` names its
lists. `enqueueMany`, `uncons`, `tryHead`, `tryTail` and `tryUncons` compile to `EnqueueMany`, `PeekDequeue`,
`TryPeek`, `TryDequeue` and `TryPeekDequeue`, in the `IQueue`, `Option` and `Tuple` modules too. 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 BCL would reject with `InvalidOperationException` while it enumerates it; the `IQueue`
module and the `queue { }` expression do the same.
* The `IQueue` module has the functions of `Queue` for `IImmutableQueue<'T>`, without the creation functions, plus
`clear`. Its functions keep the implementation of the input queue where the result has the element type of the input,
and return a `Queue` otherwise. `append` enqueues the second queue into the first one, so its result keeps the
implementation of the first queue, also when that queue is empty.
* The `FSharp.Collections.Immutable.Option` and `FSharp.Collections.Immutable.Tuple` namespaces have `Queue` and `IQueue`
modules that repeat the functions that use value options or struct tuples with options and reference tuples.
* `queue { }` builds a queue.

### Changed

Expand Down Expand Up @@ -124,6 +140,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `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.
* `Queue` works on `Queue<'T>` (`ImmutableQueue<'T>`) instead of `IQueue<'T>`, and `enqueue`, `tail` and `filter` return a
`Queue<'T>`; the functions for `IImmutableQueue<'T>` are in the new `IQueue` module.
* `Queue`: `tryFind`, `tryFindIndex` and `tryPick` return `voption`, and `pick` and `tryPick` take functions that return
`voption`. The complete active pattern `Cons | Nil`, whose `Cons` case matched a reference tuple of the head and the
tail, is replaced by the struct partial active patterns `Cons`, which matches a struct tuple of the head and the tail,
and `Nil`.
* `Queue`: every function throws what the FSharp.Core `List` function of the same name throws. `head` and `tail` of an
empty queue throw `ArgumentException`, where they threw `InvalidOperationException`, and `iter2` of queues of
different lengths throws `ArgumentException`, where it stopped at the shorter queue. A default `FlatList` given as a
sequence to `ofSeq` throws `ArgumentNullException`, where it threw `InvalidOperationException`.
* `Queue.forall` and `Queue.averageBy` apply to the queue; they returned a function of a sequence before.
* `Queue`: `enqueue`, `head` and `tail` compile to `Enqueue`, `Peek` and `Dequeue`, and the other functions to
PascalCase names such as `IsEmpty`, where 1.0.0 compiled all of them under their F# names, such as `isEmpty`.

### Removed

Expand Down Expand Up @@ -161,6 +190,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* 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.
* `Queue.clear`: use `Queue.empty`, or `IQueue.clear` to keep the implementation of an interface value.

## [1.0.0] - 2020-02-23

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Please also join the [F# Open Source Group](http://fsharp.github.com)
| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` |
| `IIndexedList` | `IImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | `queue { }` |
| `IQueue` | `IImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
Expand Down
3 changes: 2 additions & 1 deletion docsSrc/Explanations/Background.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ While F# has its own built-in immutable collections, System.Collections.Immutabl
| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` |
| `IIndexedList` | `IImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | `queue { }` |
| `IQueue` | `IImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
Expand Down
3 changes: 2 additions & 1 deletion docsSrc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us
| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` |
| `IIndexedList` | `IImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | `queue { }` |
| `IQueue` | `IImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
Expand Down
2 changes: 2 additions & 0 deletions src/FSharp.Collections.Immutable/ComputationExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ module FSharp.Collections.Immutable.ComputationExpressions
let flatList = FlatListCE ()

let indexedList = IndexedListCE ()

let queue = QueueCE ()
14 changes: 14 additions & 0 deletions src/FSharp.Collections.Immutable/ComputationExpressions.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,17 @@ val flatList : FlatListCE
/// Evaluates to an indexed list of 0, 1, 4, 9, 100 and 200.
/// </example>
val indexedList : IndexedListCE

/// <summary>Builds a queue.</summary>
/// <example id="queue-1">
/// <code lang="fsharp">
/// let squares = queue {
/// yield 0
/// for i in 1..3 do
/// yield i * i
/// yield! [ 100; 200 ]
/// }
/// </code>
/// Evaluates to a queue of 0, 1, 4, 9, 100 and 200, from which 0 leaves first.
/// </example>
val queue : QueueCE
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Compile Include="Helper\Lengths.fs" />
<Compile Include="Helper\DescendingSorts.fs" />
<Compile Include="Helper\ListRanges.fs" />
<Compile Include="Helper\Queues.fs" />
<Compile Include="FlatList\FlatListTypes.fs" />
<Compile Include="FlatList\FlatList.fsi" />
<Compile Include="FlatList\FlatList.fs" />
Expand Down Expand Up @@ -68,8 +69,22 @@
<Compile Include="IndexedList\IndexedListBuilder.fs" />
<Compile Include="IndexedList\IndexedListCE.fsi" />
<Compile Include="IndexedList\IndexedListCE.fs" />
<Compile Include="Queue\QueueTypes.fs" />
<Compile Include="Queue\Queue.fsi" />
<Compile Include="Queue\Queue.fs" />
<Compile Include="Queue\QueueOption.fsi" />
<Compile Include="Queue\QueueOption.fs" />
<Compile Include="Queue\QueueTuple.fsi" />
<Compile Include="Queue\QueueTuple.fs" />
<Compile Include="Queue\IQueue.fsi" />
<Compile Include="Queue\IQueue.fs" />
<Compile Include="Queue\IQueueOption.fsi" />
<Compile Include="Queue\IQueueOption.fs" />
<Compile Include="Queue\IQueueTuple.fsi" />
<Compile Include="Queue\IQueueTuple.fs" />
<Compile Include="Queue\QueueCE.fsi" />
<Compile Include="Queue\QueueCE.fs" />
<Compile Include="Stack.fs" />
<Compile Include="Queue.fs" />
<Compile Include="Maps.fs" />
<Compile Include="Sets.fs" />
<Compile Include="ComputationExpressions.fsi" />
Expand Down
137 changes: 137 additions & 0 deletions src/FSharp.Collections.Immutable/Helper/Queues.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/// <summary>
/// The helpers that the queue modules share (ADR 0001, D5): the queues of a fresh array and of a buffer, and the walks that
/// read the elements of a queue only up to the ones that a function of <see cref="T:FSharp.Collections.Immutable.Queue"/>
/// or <see cref="T:FSharp.Collections.Immutable.IQueue"/> needs.
/// </summary>
/// <remarks>
/// <para>
/// The walks take the interface, which both modules pass, and the caller checks the queue for <see langword="null"/>. They
/// enumerate an <see cref="T:System.Collections.Immutable.ImmutableQueue`1"/> with its struct enumerator, which allocates
/// nothing, also when a function of <see cref="T:FSharp.Collections.Immutable.IQueue"/> receives it, and any other queue
/// with its own enumerator, which they dispose.
/// </para>
/// <para>
/// Each walk is one generic loop over an enumerator type that implements
/// <see cref="T:System.Collections.Generic.IEnumerator`1"/>. The loop runs with the struct <c>ImmutableQueueEnumerator</c>
/// or with the enumerator of the other queue. The JIT compiles generic code for each struct type argument and calls its
/// members without boxing it, so the struct enumerator of the queue advances in place. Member constraints of an inline
/// loop would need the struct to be public, because they resolve only to members that are accessible from everywhere.
/// </para>
/// </remarks>
module internal FSharp.Collections.Immutable.Queues

open System
open System.Collections.Generic
open System.Collections.Immutable
open System.Runtime.InteropServices

/// The struct enumerator of an ImmutableQueue as an IEnumerator, for the loops below, which take it as a type argument.
[<Struct; NoComparison; NoEquality>]
type ImmutableQueueEnumerator<'T> =
val mutable private enumerator : ImmutableQueue<'T>.Enumerator
new (queue : ImmutableQueue<'T>) = { enumerator = queue.GetEnumerator () }

interface IEnumerator<'T> with
member this.Current = this.enumerator.Current

interface Collections.IEnumerator with
member this.Current = box this.enumerator.Current
member this.MoveNext () = this.enumerator.MoveNext ()
// The walks never reset an enumerator, and the struct enumerator of the queue cannot be reset
member _.Reset () = raise (NotSupportedException ())

interface IDisposable with
member _.Dispose () = ()

/// Runs a loop over the struct enumerator of an ImmutableQueue, or over the enumerator of another queue, which it
/// disposes. The callers pass the same generic loop twice, once for each type of enumerator.
let inline private walk
([<InlineIfLambda>] immutableQueueLoop : ImmutableQueueEnumerator<'T> -> 'Result)
([<InlineIfLambda>] otherQueueLoop : IEnumerator<'T> -> 'Result)
(queue : IImmutableQueue<'T>)
=
match queue with
| :? ImmutableQueue<'T> as queue -> immutableQueueLoop (new ImmutableQueueEnumerator<'T> (queue))
| _ ->
use enumerator = queue.GetEnumerator ()
otherQueueLoop enumerator

/// The loop of firstMatch.
let private firstMatchLoop (predicate : 'T -> bool) (enumerator : 'Enumerator :> IEnumerator<'T>) =
// A struct enumerator advances only as a mutable value; a call on the parameter would advance a copy
let mutable enumerator = enumerator
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 loop of firstChoice.
let private firstChoiceLoop (chooser : 'T -> 'U voption) (enumerator : 'Enumerator :> IEnumerator<'T>) =
let mutable enumerator = enumerator
let mutable result = ValueNone

while result.IsNone && enumerator.MoveNext () do
result <- chooser enumerator.Current

result

/// The loop of front.
let private frontLoop (count : int) (enumerator : 'Enumerator :> IEnumerator<'T>) =
let mutable enumerator = enumerator
let items = ResizeArray<'T>()
let mutable more = enumerator.MoveNext ()

while more && items.Count < count do
items.Add enumerator.Current
more <- enumerator.MoveNext ()

struct (items, more)

/// <summary>
/// A queue of the elements of a fresh array, without the null check of
/// <see cref="M:FSharp.Collections.Immutable.Queue.OfArray``1(``0[])"/>.
/// </summary>
/// <remarks>
/// <see cref="M:System.Collections.Immutable.ImmutableQueue.Create``1(System.ReadOnlySpan{``0})"/> copies the elements
/// into the front stack of the queue, from the last one to the first one, so that the queue dequeues them without
/// reversing a back stack first.
/// </remarks>
let inline ofFreshArray (array : 'T array) = ImmutableQueue.Create<'T>(ReadOnlySpan<'T> array)

/// A queue of the elements of a buffer that nobody changes any more, built as ofFreshArray builds it, without a copy of
/// the buffer.
let ofBuffer (buffer : ResizeArray<'T>) =
ImmutableQueue.Create<'T>(Span<'T>.op_Implicit(CollectionsMarshal.AsSpan buffer))

/// The index and the first element that satisfies the predicate, or ValueNone.
let firstMatch (predicate : 'T -> bool) (queue : IImmutableQueue<'T>) =
walk (firstMatchLoop predicate) (firstMatchLoop predicate) queue

/// The first value that the chooser returns for the elements, in their order, or ValueNone.
let firstChoice (chooser : 'T -> 'U voption) (queue : IImmutableQueue<'T>) =
walk (firstChoiceLoop chooser) (firstChoiceLoop chooser) queue

/// The first elements of a queue, at most the count, and whether the queue has more elements.
let front (count : int) (queue : IImmutableQueue<'T>) = walk (frontLoop count) (frontLoop count) queue

/// The index and the last element that satisfies the predicate, or ValueNone; List searches a copy from the end too. A
/// null queue throws ArgumentNullException for "queue", the name of the parameter in both modules.
let lastMatch (predicate : 'T -> bool) (queue : IImmutableQueue<'T>) =
let array = Snapshot.ofSeq (nameof queue) queue
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]))
Loading