Warning: Effeck is v0. The language will change in major, breaking ways. It is not suitable for production use.

HashSet

HashSet and MutableHashSet work in Effeck exactly as in Effect. They store unique, unordered values using Effect's equality and hashing rules; choose them for frequent membership checks and set operations.

Choosing immutable or mutable sets

const snapshot = HashSet.make("green", "blue")
const revised = snapshot.add("red")
snapshot.has("red") // false
revised.has("red") // true

function buildLocal() {
  const working = MutableHashSet.empty<string>()
  MutableHashSet.add(working, "green")
  MutableHashSet.add(working, "blue")
  return HashSet.fromIterable(working)
}

Immutable updates preserve earlier versions, making snapshots suitable for sharing between fibers. Mutable sets update in place and suit local accumulation with one owner. Use an array for indexed access or required insertion order, and a hash map for key-value associations.

Equality and uniqueness

const a = { region: "west", active: true }
const b = { region: "west", active: true }
Equal.equals(a, b) // true
HashSet.make(a, b).size() // 1
new Set([a, b]).size // 2

Plain objects compare structurally in Effect 4.0.5, so separately allocated records can count as one member. All compared fields matter: adding different request identifiers makes two records distinct. Custom equality uses Equal.Equal and a consistent Hash.Hash implementation; keep members immutable after insertion so their hashes remain valid.

Constructing and changing immutable sets

const empty = HashSet.empty<number>()
const initial = HashSet.fromIterable([2, 4, 4, 6])
const changed = initial.add(8).remove(2)
const other = HashSet.make(6, 10)
const together = changed.union(other)
const common = changed.intersection(other)
const exclusive = changed.difference(other)
const large = together.filter((n) => n > 5)
const labels = large.map((n) => `item-${n}`)
const sum = initial.reduce(0, (total, n) => total + n)

empty, make, and fromIterable all construct sets. add and remove return updated sets; union, intersection, and difference return the corresponding set operation. Method calls chain immutable updates without changing the original.

Querying membership and size

const allowed = HashSet.make("read", "write", "audit")
allowed.has("write") // true
allowed.size() // 3
allowed.some((name) => name.startsWith("a")) // true
allowed.every((name) => name.length > 0) // true
HashSet.make("read").isSubset(allowed) // true

has tests one member and size counts unique members. some and every run predicates over members; isSubset tests containment of one set in another. Traversal order is not part of the contract.

Updating mutable sets

function uniqueRegions(regions: ReadonlyArray<string>) {
  const seen = MutableHashSet.fromIterable(regions)
  MutableHashSet.add(seen, "unknown")
  MutableHashSet.remove(seen, "")
  const count = MutableHashSet.size(seen)
  const containsUnknown = MutableHashSet.has(seen, "unknown")
  const values = Array.from(seen)
  MutableHashSet.clear(seen)
  return { count, containsUnknown, values }
}

make, empty, and fromIterable also exist on MutableHashSet. Its add, remove, and clear change the original object. Keep mutation within an owned operation; a mutable collection alone does not coordinate concurrent workflows.

Balancing performance and persistence

function snapshots(values: ReadonlyArray<number>) {
  let current = HashSet.empty<number>()
  const history = [current]
  for (const value of values) {
    current = current.add(value)
    history.push(current)
  }
  return history
}

Persistent sets share storage between versions; mutable sets avoid persistent-update allocation when previous versions are unnecessary. With well-distributed hashes, lookup, insertion, and removal have near-constant average cost; traversals and set-wide operations scale with the members visited. Poor custom hashes increase collision work, so measure the actual workload rather than assuming every operation is constant time.

Converting to JavaScript collections

const regions = HashSet.make("west", "east")
const array = Array.from(regions)
const copy = [...regions]
const native = new Set(regions)

function report() {
  for (const region of regions) log(region)
}

Both variants implement Iterable, supporting spread, Array.from, and for...of. Native sets use JavaScript equality for future operations, so a conversion does not transfer Effect's equality behavior. Avoid repeated array conversions in hot loops.