Header menu logo FSharp.Collections.Builders

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.

dotnet add package FSharp.Collections.Builders

Or try it in an F# script file (.fsx):

#r "nuget: FSharp.Collections.Builders"

open FSharp.Collections.Builders

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 is int seq.

let g xs = resizeArray<float> { for x in xs -> x * x }

xs is float 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:

var nums = new List<int> { 1, 2, 3 };
var nums = new HashSet<int> { 1, 2, 3 };

Or, with target-typed new():

List<int> nums = new() { 1, 2, 3 };
HashSet<int> nums = new() { 1, 2, 3 };

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:

Additional potential development directions:

Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Collections

--------------------
namespace Microsoft.FSharp.Collections
namespace FSharp.Collections.Builders
val f: xs: 'a seq -> ResizeArray<string>
val xs: 'a seq
val resizeArray<'T> : ResizeArrayBuilder<'T>
<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 -&gt; x * x } </code><code lang="fsharp"> let a = 1 let xs = [|2..100|] let ys = resizeArray { 0; 1; yield! xs } </code></example>
val x: 'a
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
val xs: ResizeArray<int>
type ResizeArray<'T> = System.Collections.Generic.List<'T>
System.Collections.Generic.List.Add(item: int) : unit
val ys: ResizeArray<float>
val x: int
Multiple items
val float: value: 'T -> float (requires member op_Explicit)

--------------------
type float = System.Double

--------------------
type float<'Measure> = float
System.Collections.Generic.List.Add(item: float) : unit
val f: xs: int seq -> ResizeArray<int>
val xs: int seq
val g: xs: float seq -> ResizeArray<float>
val xs: float seq
val x: float
val xs: System.Collections.Generic.HashSet<int>
val hashSet<'T> : HashSetBuilder<'T>
<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>
val ys: System.Collections.Generic.HashSet<int>
val xs: System.Collections.Generic.SortedSet<int>
val sortedSet<'T> : SortedSetBuilder<'T>
<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>
val kvs: System.Collections.Generic.Dictionary<int,string>
val dictionary<'Key,'Value (requires equality)> : DictionaryBuilder<'Key,'Value> (requires equality)
<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 filtered: System.Collections.Generic.Dictionary<int,obj>
val k: int
val v: obj
Multiple items
val list: int list

--------------------
type 'T list = List<'T>
val stringMap: System.Collections.Generic.Dictionary<string,int>
val i: int
val m: System.Collections.Generic.SortedDictionary<int,string>
val sortedDictionary<'Key,'Value (requires equality)> : SortedDictionaryBuilder<'Key,'Value> (requires equality)
<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>
val xs: Set<int>
val set'<'T (requires comparison)> : SetBuilder<'T> (requires comparison)
<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>
val ys: Set<int>
val xs: int list
val m: Map<int,string>
val map<'Key,'Value (requires comparison)> : MapBuilder<'Key,'Value> (requires comparison)
<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>
val m: Map<int,int>
Multiple items
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 ofSeq: elements: ('Key * 'T) seq -> Map<'Key,'T> (requires comparison)
Multiple items
val seq: sequence: 'T seq -> 'T seq

--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>
val empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)
module Seq from Microsoft.FSharp.Collections
val fold<'T,'State> : folder: ('State -> 'T -> 'State) -> state: 'State -> source: 'T seq -> 'State
member Map.Add: key: 'Key * value: 'Value -> Map<'Key,'Value>
val xs: System.Collections.Immutable.ImmutableArray<int>
val immutableArray<'T> : ImmutableArrayBuilder<'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>
val xs: obj seq
val builder: obj
val ys: System.Collections.Immutable.ImmutableArray<string>
val x: obj
val xs: System.Collections.Immutable.ImmutableList<int>
val immutableList<'T> : ImmutableListBuilder<'T>
<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>
val xs: System.Collections.Immutable.ImmutableHashSet<int>
val immutableHashSet<'T> : ImmutableHashSetBuilder<'T>
<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>
val xs: System.Collections.Immutable.ImmutableSortedSet<int>
val immutableSortedSet<'T> : ImmutableSortedSetBuilder<'T>
<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>
val kvs: System.Collections.Immutable.ImmutableDictionary<int,string>
val immutableDictionary<'Key,'Value> : ImmutableDictionaryBuilder<'Key,'Value>
<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>
val kvs: System.Collections.Immutable.ImmutableSortedDictionary<int,string>
val immutableSortedDictionary<'Key,'Value> : ImmutableSortedDictionaryBuilder<'Key,'Value>
<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>
val s: int
Multiple items
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 ...
val filter: predicate: ('T -> bool) -> list: 'T list -> 'T list
val sum: list: 'T list -> 'T (requires member (+) and member Zero)
val sum: SumBuilder
<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 -&gt; x } </code><code lang="fsharp"> let xs = [4..100] let s = sum { 1; 2; 3; yield! xs } </code></example>
val Σ: SumBuilder
<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 -&gt; x } </code><code lang="fsharp"> let xs = [4..100] let s = Σ { 1; 2; 3; yield! xs } </code></example>
val item: obj
module Specialized from FSharp.Collections.Builders
<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 -&gt; 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 -&gt; x * len } </code></example>
val f: xs: int list -> ResizeArray<int>
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val resizeArray<'T> : ResizeArrayBuilder<'T>
<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 -&gt; x * x } </code><code lang="fsharp"> let a = 1 let xs = [|2..100|] let ys = resizeArray { 0; 1; yield! xs } </code></example>
val g: xs: int array -> ResizeArray<int>
val xs: int array
val len: int
module Array from Microsoft.FSharp.Collections
val length: array: 'T array -> int
val nums: int list
val doubled: float list
val num: int
val doubledArr: float array
val doubledSeq: float seq
val nums: ResizeArray<int>

Type something to start searching.