Dive C4.04.3
Sets
A set answers one question — is this in it? Hold each value once, ask in O(log n), and the dedup comes for free.
F#'s Set<'T> is the same balanced search tree as Map,
holding keys without values — so it is immutable, ordered, and O(log n). The Elixir course's
E4.04 teaches this as MapSet; F#'s
Set.contains is its MapSet.member?. Three properties carry the
dive. Membership: Set.contains is a boolean in O(log n).
Dedup: adding an element already present is a no-op, so uniqueness and membership are the same
property — the set is the dedup. And order: because the set is comparison-ordered,
Set.minElement is the smallest element, reached in O(log n), not an O(n) scan. Every
echo below was produced by dotnet fsi.
Membership is the question; dedup is the answer
A set is built by adding elements. Set.add takes an element and a set and returns a
set with the element present; Set.contains tests membership as a boolean. The key
property: adding an element already in the set changes nothing — a set holds each value once. Membership and
uniqueness are not two features; they are the same one.
let s0 = Set.empty<string> let s1 = s0 |> Set.add "JOB0a" |> Set.add "JOB0b" Set.contains "JOB0a" s1 // val it : bool = true Set.contains "JOB0z" s1 // val it : bool = false Set.count s1 // val it : int = 2 let s2 = s1 |> Set.add "JOB0a" // already present Set.count s2 // val it : int = 2 (no duplicate — the set is the dedup)
- Set.empty
- the empty set; the seed every set grows from.
- Set.add x
- returns a new set with
xpresent; a no-op if it already is. O(log n). - Set.contains x
- a boolean membership test in O(log n) — the typed
MapSet.member?. - Set.count
- the number of distinct elements; equal values are held once.
Test an id against the pending set
The fixed set is a queue's pending ids: { "JOB02"; "JOB05"; "JOB07" }, held in sorted
order. Pick an id and the diagram rings its position if present; the readout gives the
Set.contains result. The last button adds an id already in the set, to show the dedup —
the count does not change.
pending = { "JOB02"; "JOB05"; "JOB07" } · sorted
Membership is a yes/no, answered in a few comparisons down the tree. Adding what is already there is a no-op — the set keeps each id once, which is exactly the dedup a queue needs.
Ordered — and composable
Because a Set is comparison-ordered, the minimum is reachable in O(log n) —
Set.minElement does not scan. And sets compose: union,
intersect, and difference build new sets from two, the way the
Elixir course composes built and planned route sets.
let pend = Set.empty |> Set.add "JOB0c" |> Set.add "JOB0a" |> Set.add "JOB0b" Set.minElement pend // val it : string = "JOB0a" Set.toList pend // val it : string list = ["JOB0a"; "JOB0b"; "JOB0c"] let built = set [ "/lists"; "/trees"; "/maps" ] let planned = set [ "/maps"; "/hamt" ] Set.intersect built planned // val it : Set<string> = set ["/maps"] Set.difference built planned // val it : Set<string> = set ["/lists"; "/trees"]
Elixir E4.04 — MapSet.member?
# Elixir — MapSet membership, the link checker MapSet.member?(built, route) # true / false
F# — Set.contains (verified in FSI)
// F# — the typed equivalent, O(log n), ordered Set.contains route built // val it : bool = true
MapSet is a hash set (O(1)
average; a HAMT underneath), while F#'s Set is an O(log n) balanced tree. F#'s set
buys ordering — Set.minElement in O(log n) is the property the queue's pending set
leans on; a hash set has no ordered minimum.EchoMQ in F#
Status — echo_fs is an in-memory model. The EchoMQ protocol — the Lua transitions executing on a Valkey wire — is not implemented (there is no Lua and no Valkey client in the code); that is the coming-soon transport (EchoMQ.Connector). This block shows how the model uses the structure, not a running bus.
The F# EchoMQ port (echo_fs) keeps each queue's not-yet-claimed ids in a
Set<string>. The set is both the membership index and the dedup, and its ordered
minimum is the next job to serve — the three properties this dive teaches, in the live bus.
EchoMQ in F#
The pending field is a Set — quoted verbatim from
echo_fs/EchoMQ/Store.fs:
/// One queue's state. type QueueState = { // … Rows: Map<string, Job> Pending: Set<string> // … Schedule, Active, Dead: Set<string>, Paused, Completed, Failed }
Two functions lean on the set. claim serves the oldest id —
the set's minimum — and pendingSize reports the depth, both quoted verbatim from
echo_fs/EchoMQ/Store.fs:
// claim the oldest pending job — the set minimum (mint order) let id = Set.minElement st.Pending /// Pending depth. let pendingSize (st: QueueState) : int = Set.count st.Pending
Because branded-id bytes sort as mint order, Set.minElement is
the oldest id — the "no second index" property. The dedup is the rows check in
enqueue: a duplicate id is refused, so the set never holds one twice (verified in FSI
over a fixed set):
let pending = Set.empty |> Set.add "JOB07" |> Set.add "JOB02" |> Set.add "JOB05" Set.minElement pending // val it : string = "JOB02" (oldest = next to serve) Set.count pending // val it : int = 3 (pending |> Set.add "JOB02") |> Set.count // val it : int = 3 (dedup)
The in-memory Store keeps its pending ids in a Set
today (echo_fs/EchoMQ/Store.fs, Job.fs — built and tested). The
Valkey transport (EchoMQ.Connector) lands next; until then Connector.eval
raises "EchoMQ Valkey connector: coming soon".
The set
A Set is the same tree without values: membership in O(log n), dedup for free, and an ordered minimum.
Where C4 goes next
The Map and Set are O(log n) trees. The next modules build the O(1) hash structure underneath Elixir's — the HAMT, then the CHAMP.
Where this leads
The set closes the module: membership, dedup, and an ordered minimum, all from the same balanced tree as the
map. The C4 chapter goes on to the hash structure underneath Elixir's Map and
MapSet — the Hash Array Mapped Trie, then the CHAMP — which trade the tree's order for
an O(1)-average constant.
References
Sources
- FSharp.Core API reference — the
Setmodule (add,contains,minElement,union). - Microsoft — F# documentation — F# collection types and the set.
- The F# Software Foundation — F#'s home on the .NET runtime.
Related in this course
- Elixir — Maps, sets & hashing (E4.04) —
MapSet.member?on the BEAM. - C4.04 — Maps, sets & hashing — the module hub.
- C4.04.2 — Immutable maps — the previous dive.
- C1.07 — Collections — list, array, seq, Map & Set in F#.