This library offers a set of computation expressions for conveniently and efficiently constructing common collections.
Get it on NuGet: FSharp.Collections.Builders.
dotnet add package FSharp.Collections.BuildersOr try it in an F# script file (.fsx):
#r "nuget: FSharp.Collections.Builders"openFSharp.Collections.BuildersSee the API documentation for the full set of supported collections and operations.
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
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.
openFSharp.Collections.BuildersThe 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.letf xs =
resizeArray {for x in xs -> string x
}Enables efficiently constructing and transforming instances of ResizeArray<'T> (System.Collections.Generic.List<'T>).
letxs= resizeArray {1;2;3}Compiles to:
letxs= ResizeArray () xs.Add 1 xs.Add 2 xs.Add 3
letys= resizeArray {for x in xs -> float x *2.0}Compiles to:
letys= ResizeArray ()for x in xs do ys.Add (float x *2.0)
letf xs = resizeArray {for x in xs doif x %3=0then x }
xsisint seq.
letg xs = resizeArray<float>{for x in xs -> x * x }
xsisfloat seq.
Enables efficiently constructing and transforming instances of System.Collections.Generic.HashSet<'T>.
letxs= hashSet {1;2;3}
hashSet [1; 2; 3]
letys= hashSet {yield! xs;yield! xs }
hashSet [1; 2; 3]
Enables efficiently constructing and transforming instances of System.Collections.Generic.SortedSet<'T>.
letxs= sortedSet {3;2;1;2}
sortedSet [1; 2; 3]
Enables efficiently constructing and transforming instances of System.Collections.Generic.Dictionary<'TKey, 'TValue>.
letkvs= dictionary {1,"a";2,"b";3,"c"}letfiltered= dictionary {for k, v in kvs doif k >1then k, v }letlist=[1..100]letstringMap= dictionary {for i in list -> string i, i }Enables efficiently constructing and transforming instances of System.Collections.Generic.SortedDictionary<'TKey, 'TValue>.
letm= sortedDictionary {2,"b";3,"c";1,"a";3,"d"}
sortedDictionary [1, "a"; 2, "b"; 3, "d"]
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.
letxs= set' {1;2;3}
set [1; 2; 3]
letys= set' {1;2;3;3}
set [1; 2; 3]
letxs=[5;1;1;1;3;3;2;2;5;5;5]letys= set' {for x in xs doif x &&&1<>0then x }
set [1; 3; 5]
letm= map {2,"b";3,"c";1,"a";3,"d"}
map [1, "a"; 2, "b"; 3, "d"]
letm= map {for x in1..100-> x, x * x }Equivalent to
letm= Map.ofSeq (seq{for x in1..100-> x, x * x })or
letm=(Map.empty,{1..100})||> Seq.fold (fun m x -> m.Add (x, x * x))
Enables efficiently constructing and transforming instances of System.Collections.Immutable.ImmutableArray<'T>.
letxs= immutableArray {1;2;3}Compiles to:
letxs=letbuilder= ImmutableArray.CreateBuilder () builder.Add 1 builder.Add 2 builder.Add 3 builder.ToImmutable ()
letys= immutableArray {for x in xs -> string x }letxs= immutableList {1;2;3}
immutableList [1; 2; 3]
letxs= immutableHashSet {3;1;2;3}
immutableHashSet [3; 1; 2]
letxs= immutableSortedSet {3;1;2;3}
immutableSortedSet [1; 2; 3]
letkvs= immutableDictionary {1,"a";2,"b";3,"c"}
immutableDictionary [1, "a"; 2, "b"; 3, "c"]
letkvs= immutableSortedDictionary {2,"b";3,"c";1,"a";3,"d"}
immutableSortedDictionary [1, "a"; 2, "b"; 3, "d"]
Given
letxs=[1..100]Instead of
lets=
xs
|> List.filter (fun x -> x %3=0)|> List.sumsum enables:
lets= sum {for x in xs doif x %3=0then x }The Greek capital letter sigma Σ may read better in certain domains:
Σ {for item in items -> item.Subtotal }<=0.10* totalTo 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:
openFSharp.Collections.Builders.Specializedletf(xs :int list)=
resizeArray {for x in xs -> x * x
}Compiles down to a fast
'T listiteration.
letg xs =letlen= Array.length xs
resizeArray {for x in xs -> x * len
}Compiles down to a fast integer
for-loop.
F#'s built-in list,
array,
and sequence expressions
make initializing and transforming 'T list, 'T array, and 'T seq quite nice:
letnums=[1;2;3]letdoubled=[for num in nums -> float num *2.0]letdoubledArr=[|for num in nums -> float num *2.0|]letdoubledSeq=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:
letnums= ResizeArray ()
nums.Add 1
nums.Add 2
nums.Add 3Or, to keep the ergonomics of sequence expressions, you must instantiate and iterate over intermediate collections:
letnums= ResizeArray [1;2;3]letnums= ResizeArray (seq{1;2;3})C# offers collection initialization syntax for types that implement IEnumerable<T> and have a public Add method:
varnums=newList<int>{1,2,3};varnums=newHashSet<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.
There are a couple language suggestions that might someday (happily!) make parts of this library obsolete:
Additional potential development directions:
- Add versions that take initial capacities, equality comparers, etc.
- Add a vectorized version of the
sumexpression.
Footnotes
It is not yet possible to provide custom implementations of the range operator
(..)for computation expression builders—although there is an approved language suggestion for it. ↩Alas, unlike
seq, which is special-cased by the compiler and can act as both a function and computation expression builder, it is impossible to do the same with the built-insetfunction (without it being similarly special-cased in the compiler). ↩