///|
/// Assign unique 64-bit identifiers using upstream's FNV hash and iterative
/// collision resolution. Records compare by MoonBit Compare; custom Rust-ordered
/// records should provide matching Compare and RustHash implementations.
pub fn[D : Compare + RustHash, T : Lattice, R : Abelian, S : Compare] Scope::identifiers(
  self : Scope[T, S],
  source : Collection[D, T, R],
) -> Collection[(D, UInt64), T, R] {
  self.identifiers_with(source, fn(round, data) { rust_hash((round, data)) })
}

///|
/// Custom collision proposals. The hash must eventually admit distinct IDs as
/// rounds increase; otherwise the fixed point need not converge.
pub fn[D : Compare, T : Lattice, R : Abelian, S : Compare] Scope::identifiers_with(
  self : Scope[T, S],
  source : Collection[D, T, R],
  hash : (Int32, D) -> UInt64,
) -> Collection[(D, UInt64), T, R] {
  let initial = source.map(fn(data) { (0, data) })
  let nested = self.nested(0L, SummaryDomain::int64())
  let entered = nested.enter(initial)
  let (variable, changes) = nested
    .scope()
    .variable(Product(self.domain.identity, 1L))
  let result = entered
    .concat(changes)
    .map(fn(pair) { (hash(pair.0, pair.1), pair) })
    .reduce(fn(_, values) {
      let output = []
      let ((round, data), count) = values[0]
      if round > 0 {
        output.push(((0, data), count.negate()))
        output.push(((round, data), count))
      }
      for index in 1..