FSharp.Collections.Builders
This library offers a set of computation expressions for conveniently and efficiently constructing common collections.
Installation
Get it on NuGet: FSharp.Collections.Builders.
|
Or try it in an F# script file (.fsx
):
|
API documentation
See the API documentation for the full set of supported collections and operations.
Quick start
The computation expression builders exposed by this library support most of the operations available in the built-in
list, array, and sequence expressions, including for
, while
, yield!
, try
/with
, try
/finally
, and conditionals.^1
FSharp.Collections.Builders
Open FSharp.Collections.Builders
for type-inference-friendly builders that behave similarly to the built-in list,
array, and sequence expressions in that they treat any collection used with for
or yield!
as a _ seq
.
open FSharp.Collections.Builders
The builders in this namespace don't require any type annotations in situations like this:
// No need to add a type annotation to xs;
// xs is inferred to have type 'a seq.
let f xs =
resizeArray {
for x in xs -> string x
}
Mutable collections from System.Collections.Generic
resizeArray
Enables efficiently constructing and transforming instances of ResizeArray<'T>
(System.Collections.Generic.List<'T>
).
let xs = resizeArray { 1; 2; 3 }
Compiles to:
let xs = ResizeArray () xs.Add 1 xs.Add 2 xs.Add 3
let ys = resizeArray { for x in xs -> float x * 2.0 }
Compiles to:
let ys = ResizeArray () for x in xs do ys.Add (float x * 2.0)
let f xs = resizeArray { for x in xs do if x % 3 = 0 then x }
xs
isint seq
.
let g xs = resizeArray<float> { for x in xs -> x * x }
xs
isfloat seq
.
hashSet
Enables efficiently constructing and transforming instances of System.Collections.Generic.HashSet<'T>
.
let xs = hashSet { 1; 2; 3 }
hashSet [1; 2; 3]
let ys = hashSet { yield! xs; yield! xs }
hashSet [1; 2; 3]
sortedSet
Enables efficiently constructing and transforming instances of System.Collections.Generic.SortedSet<'T>
.
let xs = sortedSet { 3; 2; 1; 2 }
sortedSet [1; 2; 3]
dictionary
Enables efficiently constructing and transforming instances of System.Collections.Generic.Dictionary<'TKey, 'TValue>
.
let kvs = dictionary { 1, "a"; 2, "b"; 3, "c" }
let filtered = dictionary { for k, v in kvs do if k > 1 then k, v }
let list = [1..100]
let stringMap = dictionary { for i in list -> string i, i }
sortedDictionary
Enables efficiently constructing and transforming instances of System.Collections.Generic.SortedDictionary<'TKey, 'TValue>
.
let m = sortedDictionary { 2, "b"; 3, "c"; 1, "a"; 3, "d" }
sortedDictionary [1, "a"; 2, "b"; 3, "d"]
F# collections
The set'
^2 and map
builders enable ergonomically constructing immutable F# sets and maps
without requiring intermediate collections or the ceremony of folds or recursive functions.
set'
let xs = set' { 1; 2; 3 }
set [1; 2; 3]
let ys = set' { 1; 2; 3; 3 }
set [1; 2; 3]
let xs = [5; 1; 1; 1; 3; 3; 2; 2; 5; 5; 5]
let ys = set' { for x in xs do if x &&& 1 <> 0 then x }
set [1; 3; 5]
map
let m = map { 2, "b"; 3, "c"; 1, "a"; 3, "d" }
map [1, "a"; 2, "b"; 3, "d"]
let m = map { for x in 1..100 -> x, x * x }
Equivalent to
let m = Map.ofSeq (seq { for x in 1..100 -> x, x * x })
or
let m = (Map.empty, {1..100}) ||> Seq.fold (fun m x -> m.Add (x, x * x))
Immutable collections from System.Collections.Immutable
immutableArray
Enables efficiently constructing and transforming instances of System.Collections.Immutable.ImmutableArray<'T>
.
let xs = immutableArray { 1; 2; 3 }
Compiles to:
let xs = let builder = ImmutableArray.CreateBuilder () builder.Add 1 builder.Add 2 builder.Add 3 builder.ToImmutable ()
let ys = immutableArray { for x in xs -> string x }
immutableList
let xs = immutableList { 1; 2; 3 }
immutableList [1; 2; 3]
immutableHashSet
let xs = immutableHashSet { 3; 1; 2; 3 }
immutableHashSet [3; 1; 2]
immutableSortedSet
let xs = immutableSortedSet { 3; 1; 2; 3 }
immutableSortedSet [1; 2; 3]
immutableDictionary
let kvs = immutableDictionary { 1, "a"; 2, "b"; 3, "c" }
immutableDictionary [1, "a"; 2, "b"; 3, "c"]
immutableSortedDictionary
let kvs = immutableSortedDictionary { 2, "b"; 3, "c"; 1, "a"; 3, "d" }
immutableSortedDictionary [1, "a"; 2, "b"; 3, "d"]
Summation with sum
, Σ
Given
let xs = [1..100]
Instead of
let s =
xs
|> List.filter (fun x -> x % 3 = 0)
|> List.sum
sum
enables:
let s = sum { for x in xs do if x % 3 = 0 then x }
The Greek capital letter sigma Σ
may read better in certain domains:
Σ { for item in items -> item.Subtotal } <= 0.10 * total
FSharp.Collections.Builders.Specialized
To enable specialized overloads of for
and yield!
that give increased iteration performance
at the cost of requiring that the type of the collection being iterated be statically known:
open FSharp.Collections.Builders.Specialized
let f (xs : int list) =
resizeArray {
for x in xs -> x * x
}
Compiles down to a fast
'T list
iteration.
let g xs =
let len = Array.length xs
resizeArray {
for x in xs -> x * len
}
Compiles down to a fast integer
for
-loop.
Motivation
F#'s built-in list,
array,
and sequence expressions
make initializing and transforming 'T list
, 'T array
, and 'T seq
quite nice:
List literal
let nums = [1; 2; 3]
List expression with for
and ->
let doubled = [ for num in nums -> float num * 2.0 ]
Array expression with for
and ->
let doubledArr = [| for num in nums -> float num * 2.0 |]
Sequence expression with for
and ->
let doubledSeq = seq { for num in nums -> float num * 2.0 }
But when it comes time to work with one of the common mutable collection types from System.Collections.Generic
,
whether to interoperate with other .NET libraries or for specific performance or modeling reasons, F# doesn't
provide the same syntactic sugar, forcing you you to switch modes from expression-based to statement-based style:
let nums = ResizeArray ()
nums.Add 1
nums.Add 2
nums.Add 3
Or, to keep the ergonomics of sequence expressions, you must instantiate and iterate over intermediate collections:
Instantiates and iterates over an intermediate int list
let nums = ResizeArray [1; 2; 3]
Instantiates and iterates over an intermediate int seq
let nums = ResizeArray (seq { 1; 2; 3 })
Ergonomic collection initialization beyond lists, arrays, and sequences
C# offers collection initialization syntax for types that implement IEnumerable<T>
and have a public Add
method:
|
Or, with target-typed new()
:
|
F# 6's resumable code feature makes it straightforward to implement efficient, ergonomic computation expression builders for collection types that aren't special-cased by the F# compiler.
This library implements such builders for several common mutable and immutable collections from System.Collections.Generic
and System.Collections.Immutable
, and FSharp.Collections
,
as well as offering generic collection
and dict'
builders that support any collection type with a default constructor and an appropriate Add
method.
Future
There are a couple language suggestions that might someday (happily!) make parts of this library obsolete:
- https://github.com/fsharp/fslang-suggestions/issues/1086
- https://github.com/fsharp/fslang-suggestions/issues/619
Additional potential development directions:
- Add versions that take initial capacities, equality comparers, etc.
- Add a vectorized version of the
sum
expression.
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Collections
--------------------
namespace Microsoft.FSharp.Collections
<summary> Builds a <see cref="T:System.Collections.Generic.List`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let f xs = resizeArray { for x in xs -> x * x } </code><code lang="fsharp"> let a = 1 let xs = [|2..100|] let ys = resizeArray { 0; 1; yield! xs } </code></example>
val string: value: 'T -> string
--------------------
type string = System.String
val float: value: 'T -> float (requires member op_Explicit)
--------------------
type float = System.Double
--------------------
type float<'Measure> = float
<summary> Builds a <see cref="T:System.Collections.Generic.HashSet`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = hashSet { 1; 2; 3 } </code></example>
<summary> Builds a <see cref="T:System.Collections.Generic.SortedSet`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = sortedSet { 1; 2; 3 } </code></example>
<summary> Builds a <see cref="T:System.Collections.Generic.Dictionary`2" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let m = dictionary { 1, "a"; 2, "b"; 3, "c" } </code></example>
val list: int list
--------------------
type 'T list = List<'T>
<summary> Builds a <see cref="T:System.Collections.Generic.SortedDictionary`2" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let m = sortedDictionary { 1, "a"; 2, "b"; 3, "c" } </code></example>
<summary> Builds a <see cref="T:FSharp.Collections.FSharpSet`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = set' { 1; 2; 3 } </code></example>
<summary> Builds a <see cref="T:FSharp.Collections.FSharpMap`2" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let m = map { 1, "a"; 2, "b"; 3, "c" } </code></example>
module Map from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...
--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
<summary> Builds an <see cref="T:System.Collections.Immutable.ImmutableArray`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = immutableArray { 1; 2; 3 } </code></example>
<summary> Builds an <see cref="T:System.Collections.Immutable.ImmutableList`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = immutableList { 1; 2; 3 } </code></example>
<summary> Builds an <see cref="T:System.Collections.Immutable.ImmutableHashSet`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = immutableHashSet { 1; 2; 3 } </code></example>
<summary> Builds an <see cref="T:System.Collections.Immutable.ImmutableSortedSet`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let xs = immutableSortedSet { 1; 2; 3 } </code></example>
<summary> Builds an <see cref="T:System.Collections.Immutable.ImmutableDictionary`2" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let m = immutableDictionary { 1, "a"; 2, "b"; 3, "c" } </code></example>
<summary> Builds an <see cref="T:System.Collections.Immutable.ImmutableSortedDictionary`2" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let m = immutableSortedDictionary { 1, "a"; 2, "b"; 3, "c" } </code></example>
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
<summary> Computes a sum using computation expression syntax. </summary>
<example><code lang="fsharp"> let s = sum { 1; 2; 3 } </code><code lang="fsharp"> let xs = [1..100] let s = sum { for x in xs -> x } </code><code lang="fsharp"> let xs = [4..100] let s = sum { 1; 2; 3; yield! xs } </code></example>
<summary> Computes a sum using computation expression syntax. </summary>
<example><code lang="fsharp"> let s = Σ { 1; 2; 3 } </code><code lang="fsharp"> let xs = [1..100] let s = Σ { for x in xs -> x } </code><code lang="fsharp"> let xs = [4..100] let s = Σ { 1; 2; 3; yield! xs } </code></example>
<summary> Contains specialized overloads of <c>for</c> and <c>yield!</c> that give increased iteration performance for common collection types at the cost of requiring that the type of the collection being iterated be statically known. </summary>
<example> The type of <c>xs</c> must be annotated or otherwise known, but once it is, the appropriate specialized iteration technique will be used. <code lang="fsharp"> let f (xs : int list) = resizeArray { for x in xs -> x * x } </code><c>xs</c> is known to be a list because of the call to <c>List.length</c>. <code lang="fsharp"> let g xs = let len = List.length xs resizeArray { for x in xs -> x * len } </code></example>
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
<summary> Builds a <see cref="T:System.Collections.Generic.List`1" /> using computation expression syntax. </summary>
<example><code lang="fsharp"> let f (xs : int list) = resizeArray { for x in xs -> x * x } </code><code lang="fsharp"> let a = 1 let xs = [|2..100|] let ys = resizeArray { 0; 1; yield! xs } </code></example>