SodaFlow.FSharp.Collections 2.0.0

dotnet add package SodaFlow.FSharp.Collections --version 2.0.0
                    
NuGet\Install-Package SodaFlow.FSharp.Collections -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SodaFlow.FSharp.Collections" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SodaFlow.FSharp.Collections" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SodaFlow.FSharp.Collections" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SodaFlow.FSharp.Collections --version 2.0.0
                    
#r "nuget: SodaFlow.FSharp.Collections, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SodaFlow.FSharp.Collections@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SodaFlow.FSharp.Collections&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SodaFlow.FSharp.Collections&version=2.0.0
                    
Install as a Cake Tool

SodaFlow Reactive Collections library for F#.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 is compatible.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 71 9/28/2026
1.0.1 89 9/18/2026
1.0.0 89 9/18/2026

2.0.0

Adds fold, for a total, a count, or an average that follows the collection. It
takes select, zero, add, and subtract, and then the collection, thus it composes
with the pipe operator. Each change carries the states before it and the states
after it, thus fold removes the previous value of each key that the change names
and adds the new one. The cost of an edit is the count of the keys in that edit,
and not the count of the items.

 let total = accounts |> fold (fun _ state -> state.Balance) 0L (+) (-)

select takes the identity and the state of an item, as the predicate of filter
and the selector of sortBy do.

Adds foldByIdentity, to match filterByIdentity and sortByIdentity. Its select
takes the identity alone. A state edit cannot change an identity, thus this fold
reads only the changes that add or remove an item. The cell sends no value at a
state edit, and that edit costs one test here. A count of the items is the usual
example:

 let count = accounts |> foldByIdentity (fun _ -> 1) 0 (+) (-)

A group, and not one combine function, because only an invertible operation can
answer an edit with no read of each item. A sum and a count are groups. A maximum
is not one: sort the view and read its first key. The first value is a read of
the store, thus a caller gives no seed. A view folds its own items.

BREAKING: sortByKey and orderByKey no longer take a comparer. sortByKey sorts
by the default comparer, and orderByKey takes unit, as orderByArrival does.
The comparer and the direction go to the new sortByKeyWith and
orderByKeyWith, as the other With forms take them. A call that passed
Comparer.Default becomes sortByKey or orderByKey (); a call with any other
comparer becomes sortByKeyWith comparer false or orderByKeyWith comparer
false.

BREAKING: map answers with a MappedItems<'TResult> that is a sealed class,
where it was a readonly struct. Source that reads Items and calls Dispose needs
no edit; a recompile does. The notes of SodaFlow.Collections.Core give the
details.

Adds sortByKeyDescending and orderByKeyDescending. A cell that moves between
two key orders with the same comparer turns the list that the stage holds and
does not sort each key again.

Adds itemCell, which gives the two parts of one item as one optional value. It
follows the key as stateCell does, and it answers for the collection or the
view that a caller asks, with the same cache for each key.

Use it for a value that reads the identity and the state together. A lift of
identityCell against stateCell does the same work, and it gives two optional
values, thus four combinations, of which the store cannot give two: an
identity with no state, and a state with no identity. One cell gives one
optional value and removes the two branches that no code can reach. It also
costs one cell and not two cells with a lift above them.

It is not the correct selection for each row. An item holds the state, thus
itemCell sends a value at each edit to the state of its key. Each value that
comes from it is made again at that moment, and that includes a part which
reads the identity alone. Where one binding reads the identity and a different
binding reads the state, take the two cells: identityCell sleeps through an
edit to the state, and that is what makes it almost free to hold.

Adds filterByIdentityC, which is filterByIdentity with a predicate in a cell,
as filterC is to filter. A change to the predicate tests each item again and
names what entered and what left. A state edit still does not test the
predicate.

Fixed: the documentation of filterC said that a change to the predicate builds
the stage again and reports a reset. It reports the keys that entered and
left as inserts and removals, and it builds again and resets only when more
keys move than a list of them is worth.

Fixed: take and slice lost an edit to a key that kept its position in the
window. A slice compares the window before a change with the window after it,
and it gave only an update to a key that the comparison found in both. A sort
above it reports a key that it files again as a move, and an item that one edit
removes and adds again as a removal and an insert. The slice gave neither of the
two, thus a stateCell, an identityCell, or a bound row on the window kept the
previous value. A move is now an update below the slice. A replacement is a
removal and an insert at the same position, as the root reports it, because the
identity can change.

Requires SodaFlow.FSharp 5.x and SodaFlow.Collections.Core 2.x. SodaFlow.FSharp
5.0.0 ships in the same release, and its own notes list what changed there.

1.0.1

Adds the package icon that nuget.org shows beside this package. No source file
changed since 1.0.0.

Every package here ships this release together, so the dependency versions
move with it.

1.0.0

First release.

Requires SodaFlow.FSharp 4.x and SodaFlow.Collections.Core 1.x. Installing this
brings the F# API it extends, so one install gives you the whole surface. This
one reaches the core's internals as well, so the SodaFlow.Core it runs against
has to be 4.0.1 or later; SodaFlow.Collections.Core carries that floor, and
NuGet resolves it from there.

Optionality is option, not Maybe, and it brings no more with it than
SodaFlow.FSharp does. The core carries no optional type at all - its lookups
are TryGets - so the projection into option happens inside the map a per-item
cell already had, costing no extra graph node.

---

About this package

A large keyed collection for cases where the number of items being actively
observed is a small fraction of the total. Per-item observation costs one hash
lookup per observer per transaction, independent of collection size.

The same library as SodaFlow.Collections, with the arguments ordered for the
pipeline - the collection comes last, so a chain reads in the order it runs -
and F# functions rather than Func for predicates and selectors.

 let accounts = create keyOf initial [ fromAdds opened; fromRemoves closed ]

 let topTen =
     accounts
     |> sortByDescending (fun _ state -> state.Balance)
     |> filter (fun _ state -> not state.IsFrozen)
     |> take 10

 let selected = accounts |> stateCell selectedKey

sortByOrderC takes a cell of orders rather than a selector, which is how a
clickable column header is written: an order carries its own sort value type
inside itself, so one cell holds orders sorting by an int and by a string
alike. orderBy, orderByDescending, orderByIdentity, orderByIdentityDescending,
orderByKey and the two With forms build them, mirroring the sorts one for one.

thenBy, thenByDescending, thenByIdentity, thenByIdentityDescending and their
With forms add a level to an order, deciding only between keys it ranks equal,
and sortByOrder sorts by an order that does not change - which is how a
multi-level sort that never changes is written.

A collection keeps its items in the order they arrived rather than by key, and
sortByArrival and orderByArrival take a sorted view back to that order.

slice offset limit is the paging window, and take is the case of it that starts
at zero; sliceC takes cells for either end, so turning the page is one send.
There is no skip - a window with both ends is bounded, which is what keeps the
stage at O(limit) per transaction.

map ends a chain: one object per key, in order, kept so the same key gives back
the same object - which is what a list binds to. Build each one from stateCell
and identityCell and it follows its own item, so one edit moves one row rather
than rebuilding the list. mapWith chooses how much to keep and hears about what
is dropped; disposing the result releases what is still held.

createByIdentity and createByIdentityWith drop the key selector when the identity
implements IIdentity<'TKey> and so carries its own key.

Everything that can change the collection is declared at construction: create
takes the initial contents and every edit stream, lifted from domain streams by
fromAdds, fromRemoves, fromUpdates and fromStates. There is no imperative entry
point.

Full notes: https://github.com/MorseCode-Software/SodaFlow/releases