Skip to content

unify()

Eugene Lazutkin edited this page Apr 6, 2026 · 11 revisions

This is the most important algorithm of this package. It implements the first-order unification algorithm using environments and variables.

The unification can be imagined as an advanced version of deep equivalence. It works like that when no variables are used. With variables, it allows ignoring parts of objects or saving them to be retrieved later.

The function can deal with incomplete (open) objects and circular references. See Wrapping for details on open/soft matching modes.

Out of the box it can compare all builtin JavaScript types (numbers, strings, boolean values, symbols, bigints, undefined values), arrays, common objects, and objects of the following builtin classes: Date, RegExp, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array, DataView, ArrayBuffer, Set, Map, URL. This list can be extended for any custom class.

It is defined in unify and can be accessed like that:

import unify from 'deep6/unify.js';

The main module provides handy helpers based on unify():

Introduction

import unify, {open, variable, any} from 'deep6/unify.js';

const x = {a: 1, b: 2, c: ['hi!', 42, null, {}]};

// deep equivalence
!!unify(x, {b: 2, a: 1, c: ['hi!', 42, null, {}]}); // true

// pattern matching
!!unify(x, open({a: 1})); // true
!!unify(x, open({b: 2})); // true
!!unify(x, open({z: 1})); // false
!!unify(x, open({a: 2})); // false

const z = {},
  w = {};
z.z = z;
w.z = w;

// circular dependencies
!!unify(z, w, {circular: true}); // true

const v1 = variable(),
  v2 = variable(),
  v3 = variable();

// variables
const env = unify(x, {a: v1, b: v2, c: v3}); // truth
v1.get(env); // 1
v2.get(env); // 2
v3.get(env); // ['hi!', 42, null, {}]

// anyvar
!!unify(x, {b: any, a: 1, c: any}); // true

API

unify(l, r [, env] [, options])

Arguments:

  • l — a required left value. It can be anything.
  • r — a required right value. It can be anything.
  • env — an optional Env object with existing variable bindings, or an options object.
    • When an options object is passed here, it is used as options and no existing environment is used.
  • options — an optional object. The following optional properties are recognized:
    • circular — a boolean flag. When true, circular references are detected and handled correctly. Without it, circular structures will cause an infinite loop.
    • symbols — a boolean flag. When true, symbol properties are compared.
    • loose — a boolean flag. When true, primitives are compared with == rather than ===.
    • ignoreFunctions — a boolean flag. When true, function values are treated as equivalent.
    • signedZero — a boolean flag. When true, +0 and -0 are treated as different values.
    • openObjects — a boolean flag. When true, plain objects are compared in "open" mode (extra properties allowed).
    • openArrays — a boolean flag. When true, arrays are compared in "open" mode (extra elements allowed).
    • openMaps — a boolean flag. When true, Maps are compared in "open" mode (extra entries allowed).
    • openSets — a boolean flag. When true, Sets are compared in "open" mode (extra elements allowed).

Returns an Env object with variable bindings on success, or null on failure.

unify.registry

A flat array of Constructor/handler pairs used to unify registered types: [Type1, handler1, Type2, handler2, ...].

Each handler has the signature (l, r, ls, rs, env) => boolean. It should return true on success and false on failure. To delegate deep comparison of sub-values, push them to ls and rs.

Built-in entries handle: Date, RegExp, typed arrays, DataView, ArrayBuffer, Set, Map, URL.

Plain objects (prototype Object.prototype or null) and arrays are fast-pathed and skip the registry. To match plain objects by a custom criterion, use unify.filters instead.

Extend with:

unify.registry.push(MyClass, (l, r, ls, rs, env) => {
  if (!(l instanceof MyClass) || !(r instanceof MyClass)) return false;
  ls.push(l.value);
  rs.push(r.value);
  return true;
});

unify.filters

A flat array of predicate/handler pairs: [test1, handler1, test2, handler2, ...].

Each test has the signature (l, r) => boolean and determines whether the handler should run. Each handler has the signature (l, r, ls, rs, env) => boolean.

Filters are checked after the registry. They are useful when matching is not based on instanceof but on some other property.

unify.filters.push(
  (l, r) => l.flag || r.flag, // test
  (l, r, ls, rs, env) => {
    // handler
    ls.push(l.name);
    rs.push(r.name);
    return true;
  }
);

Clone this wiki locally