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
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,64 @@ 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.
* `HashMap` has every function of the FSharp.Core `Map` module that applies to a hash map, with its semantics and
exceptions, except `add`, which stays the `Add` of `ImmutableDictionary`, as in 1.0.0; plus `choose` and `singleton`,
the builder functions `build` and `update`, and the wrappers of the `ImmutableDictionary` members `addRange`,
`containsBinding`, `containsValue`, `ofReadOnlySpan`, `ofSeqBy`, `removeRange`, `setItem`, `setItems`, `tryGetKey` and
`valueComparer`, which keep the semantics of the BCL.
* The transformations of `HashMap` keep the key comparer of the map, and the value comparer when the type of the values
does not change.
* `HashMap.containsBinding` and `IMap.containsBinding` tell whether a key is bound to a value, comparing the value with the
value comparer of the map, as `Contains` of a `KeyValuePair` does. `tryFind` followed by `=` compares with F# equality,
which can differ from the value comparer, and an `IMap` does not expose its value comparer.
* `HashMap.ofReadOnlySpan` and `HashMap.ofSpan` create a hash map from a `ReadOnlySpan` or a `Span` of
`KeyValuePair<'Key, 'Value>` with `ImmutableDictionary.CreateRangeWithOverwrite`, which takes a read-only span only, and
the members `ofReadOnlySpan` and `ofSpan` of `CustomComparersHashMap` do so with its comparers. A repeated key never
throws: the last binding wins, unless the value comparer finds the two values equal. F# converts a `Span` to the
parameter of `ofReadOnlySpan` only when it has inferred the types of the call, which is why `ofSpan` exists; an array
converts to either parameter under the same condition.
* `HashMap.build` creates a hash map with a builder that an action fills, `HashMap.update` changes a hash map with a
builder, and the member `build` of `CustomComparersHashMap` does the first with its comparers.
* `HashMap.ofArray` and `HashMap.ofList` are new, and `HashMap.ofSeq`, which took `KeyValuePair`s, takes tuples too. The
three take the bindings as `KeyValuePair`s, reference tuples or struct tuples: they are static members of
`ImmutableDictionary<'Key, 'Value>` in the auto-opened `HashMapExtensions` module, one overload per shape, and every
shape keeps the last binding of a repeated key, as `Map.ofSeq` does, unless the value comparer finds the two values
equal. They take a dictionary, an F# `Map` or another hash map too. C# does not see them as members of
`ImmutableDictionary`; it calls the public static methods `HashMapExtensions.OfArray`, `OfList` and `OfSeq`, or the
members of the struct below. A reflection over the `HashMap` module does not list them. To reject another value of a
repeated key, as `ImmutableDictionary.CreateRange` does, add the bindings to an empty map:
`HashMap.empty |> HashMap.addRange pairs`.
* `HashMap.forComparers` and `HashMap.forKeyComparer` return a `CustomComparersHashMap<'Key, 'Value>` struct whose members
`build`, `builder`, `empty`, `ofArray`, `ofList`, `ofReadOnlySpan`, `ofSeq`, `ofSeqBy`, `ofSpan` and `singleton` create
hash maps with its comparers: `(HashMap.forKeyComparer StringComparer.OrdinalIgnoreCase).ofSeq pairs`. A `null`
comparer throws `ArgumentNullException`.
* `ofSeq` of that struct also gives an existing hash map other comparers, as `WithComparers` does, with three differences.
It takes O(1) only when the key comparer of the map is the instance of the struct, and rebuilds the map otherwise.
Keys that the new comparer makes equal merge, the later binding winning, where `WithComparers` throws. And
`forKeyComparer` resets the value comparer, where `WithComparers` keeps it; to keep it, pass the value comparer of the
map: `HashMap.forComparers keyComparer (HashMap.valueComparer table)`.
* `HashMap.addRange` and `HashMap.setItems` take `KeyValuePair`s; the nested modules `HashMap.Tuple` and
`HashMap.ValueTuple` take reference and struct tuples: `HashMap.Tuple.addRange pairs table`.
* `HashMap` and `IMap` keep the convention of `ImmutableDictionary` where it differs from `Map`: a new value of a key that
the value comparer finds equal to the current one leaves the binding as it is, its key included, in `add`, `change`,
`setItem`, `setItems`, `addRange`, the creation functions and members, and `hashMap { }`. `add` is the `Add` of
`ImmutableDictionary`, as it was in 1.0.0: it throws `ArgumentException` for a key that is bound to a value that the
value comparer finds different, and returns the input itself for the same binding; `setItem` replaces the value.
* `change` with a function that keeps an existing value adds a binding only when the key has none, as
`ImmutableInterlocked.TryAdd` does. The other single-key idioms of the BCL need no new function: `tryFind` is
`TryGetValue`, and `tryFind` followed by `ValueOption.defaultValue` is `GetValueOrDefault`. `ToImmutableDictionary` with
an element selector has no function; to reject a repeated key like the BCL does, add the mapped pairs to an empty map,
`HashMap.empty |> HashMap.addRange (source |> Seq.map (fun x -> KeyValuePair (key x, element x)))`, and `HashMap.ofSeq`
of the mapped pairs lets the last binding win. Maps have no builder module: call `Add`, `AddRange`, `Remove`,
`RemoveRange`, the indexer and the other members on the builder.
* The `IMap` module works on any `IImmutableDictionary<'Key, 'Value>` with the same functions, except the creation, builder
and comparer functions and `containsValue`, which the interface does not declare, and has the nested modules `Tuple` and
`ValueTuple` too. Its `add` is the `Add` of the interface and its `containsBinding` the `Contains` of a `KeyValuePair`.
Its transformations keep the implementation and the comparers of the map; `map` and `choose` return a `HashMap`.
* The `FSharp.Collections.Immutable.Option` and `FSharp.Collections.Immutable.Tuple` namespaces have `HashMap` and `IMap`
modules that repeat the functions that use value options or return struct tuples with options and reference tuples.
* `hashMap { }` builds a hash map from `yield struct (key, value)` and `yield KeyValuePair (key, value)`; `HashMapCE` takes
a key comparer, and a `null` one throws `ArgumentNullException`.

### Changed

Expand All @@ -44,6 +102,13 @@ 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.
* `HashMap.tryFind` returns a `voption`, where it returned an `option`;
`FSharp.Collections.Immutable.Option.HashMap.tryFind` returns the `option`.
* `HashMap.ofSeq` keeps the last binding of a repeated key, where it threw `ArgumentException` for a key that is bound to
two different values.
* `HashMap.ofSeq` throws `ArgumentNullException` for a default flat list given as a sequence, where it threw
`InvalidOperationException`.
* `HashMap.filter` returns the bindings that satisfy the predicate, where it returned all the bindings of the hash map.

### Removed

Expand All @@ -61,6 +126,11 @@ 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`.
* `HashMap.length`, `append` and `except`: use `count`, `addRange` and `removeRange`. `addRange pairs table` takes the
bindings before the hash map, where `append map pairs` took the hash map first.
* `HashMap.check`: no replacement is needed, because every function of the module throws `ArgumentNullException` for a
`null` hash map.
* The `HashMapBuilder<'Key, 'Value>` type abbreviation: use `HashMap<'Key, 'Value>.Builder`.

## [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)
| `ImmutableList` | `ImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | `hashMap { }` |
| `IMap` | `IImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
| `SortedSet` | `ImmutableSortedSet<'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
| `ImmutableList` | `ImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | `hashMap { }` |
| `IMap` | `IImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
| `SortedSet` | `ImmutableSortedSet<'T>` | – |
Expand Down
10 changes: 8 additions & 2 deletions docsSrc/Tutorials/Getting_Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@ let list1 = ImmutableList.ofSeq [1; 2; 3]
// Create an immutable hash set
let set1 = HashSet.ofSeq ["a"; "b"]

// Create an immutable dictionary
let dict1 = HashMap.ofSeq [KeyValuePair ("key1", 42); KeyValuePair ("key2", 100)]
// Create an immutable dictionary from tuples, key/value pairs or struct tuples
let dict1 = HashMap.ofSeq [ "key1", 42; "key2", 100 ]

// Create an immutable dictionary whose keys ignore the case; the later binding of "KEY1" wins
let dict2 = (HashMap.forKeyComparer System.StringComparer.OrdinalIgnoreCase).ofSeq [ "key1", 42; "KEY1", 100 ]


printfn "List: %A" (list1 |> Seq.toList)
printfn "Set: %A" (set1 |> Seq.toList)
printfn "Dict: %A" (dict1 |> Seq.toList)
printfn "Dict ignoring the case: %A" (dict2 |> Seq.toList)
```

## Next Steps

- Check out the [How-To Guides](../How_Tos/Doing_A_Thing.html) for common scenarios
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
| `ImmutableList` | `ImmutableList<'T>` | – |
| `Stack` | `ImmutableStack<'T>` | – |
| `Queue` | `ImmutableQueue<'T>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – |
| `HashMap` | `ImmutableDictionary<'Key, 'Value>` | `hashMap { }` |
| `IMap` | `IImmutableDictionary<'Key, 'Value>` | – |
| `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – |
| `HashSet` | `ImmutableHashSet<'T>` | – |
| `SortedSet` | `ImmutableSortedSet<'T>` | – |
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Collections.Immutable/ComputationExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ module FSharp.Collections.Immutable.ComputationExpressions
// ---- Creation ----

let flatList = FlatListCE ()

[<GeneralizableValue>]
let hashMap<'Key when 'Key : not null> = HashMapCE<'Key>()
22 changes: 22 additions & 0 deletions src/FSharp.Collections.Immutable/ComputationExpressions.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,25 @@ module FSharp.Collections.Immutable.ComputationExpressions
/// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200.
/// </example>
val flatList : FlatListCE

/// <summary>
/// Builds a hash map with the default comparers.
/// </summary>
/// <remarks>
/// A later binding of a key replaces an earlier one, unless the default comparer of the values finds the two values
/// equal: then the earlier binding stays, its key included (ADR 0001, D14). The instance is generic in the type of the
/// keys, which each expression infers; <see cref="T:FSharp.Collections.Immutable.HashMapCE`1"/> takes a key comparer.
/// </remarks>
/// <example id="hashMap-1">
/// <code lang="fsharp">
/// let squares = hashMap {
/// for i in 1..3 do
/// yield struct (i, i * i)
/// yield KeyValuePair (10, 100)
/// yield struct (1, -1)
/// }
/// </code>
/// Evaluates to a hash map of 1 to -1, 2 to 4, 3 to 9 and 10 to 100.
/// </example>
[<GeneralizableValue>]
val hashMap<'Key when 'Key : not null> : HashMapCE<'Key>
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<Compile Include="Helper\ArrayChecks.fs" />
<Compile Include="Helper\Arrays.fs" />
<Compile Include="Helper\ParallelArrays.fs" />
<Compile Include="Helper\Dictionaries.fs" />
<Compile Include="FlatList\FlatListTypes.fs" />
<Compile Include="FlatList\FlatList.fsi" />
<Compile Include="FlatList\FlatList.fs" />
Expand All @@ -44,6 +45,33 @@
<Compile Include="FlatList\FlatListBuilder.fs" />
<Compile Include="FlatList\FlatListCE.fsi" />
<Compile Include="FlatList\FlatListCE.fs" />
<Compile Include="HashMap\HashMapTypes.fs" />
<Compile Include="HashMap\HashMap.CustomComparers.fsi">
<DependentUpon>HashMap.fsi</DependentUpon>
</Compile>
<Compile Include="HashMap\HashMap.CustomComparers.fs">
<DependentUpon>HashMap.fs</DependentUpon>
</Compile>
<Compile Include="HashMap\HashMap.fsi" />
<Compile Include="HashMap\HashMap.fs" />
<Compile Include="HashMap\HashMapOption.fsi" />
<Compile Include="HashMap\HashMapOption.fs" />
<Compile Include="HashMap\HashMapTuple.fsi" />
<Compile Include="HashMap\HashMapTuple.fs" />
<Compile Include="HashMap\HashMap.Extensions.fsi">
<DependentUpon>HashMap.fsi</DependentUpon>
</Compile>
<Compile Include="HashMap\HashMap.Extensions.fs">
<DependentUpon>HashMap.fs</DependentUpon>
</Compile>
<Compile Include="HashMap\IMap.fsi" />
<Compile Include="HashMap\IMap.fs" />
<Compile Include="HashMap\IMapOption.fsi" />
<Compile Include="HashMap\IMapOption.fs" />
<Compile Include="HashMap\IMapTuple.fsi" />
<Compile Include="HashMap\IMapTuple.fs" />
<Compile Include="HashMap\HashMapCE.fsi" />
<Compile Include="HashMap\HashMapCE.fs" />
<Compile Include="Stack.fs" />
<Compile Include="ImmutableList.fs" />
<Compile Include="Queue.fs" />
Expand Down
Loading
Loading