Rebus

Identity without a name

Rebus is a tiny PureScript library that gives a visually mnemonic identity to data that is otherwise hard to confirm, by eye, as being the same data — while keeping a typeable text form and a speakable one, so the identity survives a filename, a search box and a screen reader.

What it is for

A thing worth keeping arrives mid-flow, when stopping to name it is exactly the wrong demand.

But a pile of anonymous captures is unusable, because nothing in it can be recognised, referred to, or typed. So every capture gets an identity derived from its own content, immediately and for free: an ordered run of coloured icons — red cow, blue star — spelled by a hyphenated alias, cow-ambulance. Naming becomes a later, optional promotion rather than a toll on the way in.

Identical content lands on the identical run, which is deduplication for nothing; a real edit avalanches to a visibly different picture. Runs rather than single icons because an absurd juxtaposition is far easier to hold than any lone shape — and because 64 icons are only 64 identities, which is nothing.

The problem, stated

Three captured chord progressions. Two of them are the same take.

Canonical text, as a sequencer prints it: MIDI note numbers, comma-separated within a chord, space-separated between them. Find the pair by reading. Then look at the right-hand column, which is the same question answered in one glance.

One identity, three bodies

A picture to recognise it by, a string to type, a phrase to say out loud.

Each icon's colour is a hash of its own name, never of the content — so cow is always red and star always blue. That is what makes the alias lossless: a rebus rebuilt from the thirteen typed characters of cow-ambulance draws identically to one computed live from the content.

Width

Alias
Spoken
How wide

Width is arithmetic, not taste: count the things you must tell apart.

A rebus is only an identity if no two of the things it distinguishes collide. The right-hand column is measured live, in this page, by minting a rebus for each of 289 project ids and counting the groups that came out sharing one alias.

Collisions over 289 ids — the size of Marginalia's live project set.
Width Identities available Colliding groups Rebus of 160

Width two is not a bad hash: even a perfect one expects about ten collisions putting 289 items into 4,032 buckets. That is the birthday problem, and it arrives far earlier than intuition says. Dozens of things → two icons. Hundreds to a few thousand → three. Five is the ceiling, deliberately: past four the mnemonic stops being one absurd image and becomes a list to recite.

The deck

Sixty-four concrete, drawable nouns — and the order of them is the identity.

An index into this list is what a content hash actually produces, so the deck is fixed and append-only: reordering an entry would silently re-identify every rebus ever minted. The colours are not like that — colour is a memorability accent, and recolouring is a visual change rather than a migration.

Using it

Renderer-free: it yields an icon name and a colour string, and draws nothing.

rebusOf (chordsOf [[42,61,66,70,78],[46,58,65,72,74,79]])
  == { icons: [ { icon: "bug", color: "hsl(188, 62%, 32%)" }
              , { icon: "dog", color: "hsl(214, 62%, 44%)" } ]
     , alias: "bug-dog"
     }

rebusOfTextWidth Three "marginalia:160"   -- three icons, for a set of hundreds
rebusFromAnyAlias "bug-dog"             -- back from the typed characters
sessionAliasOf 41                       -- a container: seeded, and a triple

Mapping those names onto FontAwesome classes, SVG symbols, ANSI escapes or a plotter is the consumer's business. Dependencies are prelude, arrays, strings, maybe. Nothing else.