Identity

PFCL language reference · composure.systems

Every function in PFCL has an identity, a name, and — separately — a place in an equivalence relation to other functions. Most systems hold at most two of these apart. PFCL holds all three apart deliberately. This page specifies identity and naming. Equivalence is specified at /catalog/equivalence.

1.The three relations

  1. Identity is a function's SHA-256 hash of its own canonical source text. It answers one question: is this the same body, byte for byte, modulo nothing.
  2. Naming is a mutable, partial mapping from a human-facing name to an identity. It answers: what does this name currently point to.
  3. Equivalence is a separate, graded relation recording that two distinct identities behave the same way, to some documented degree of confidence. It never changes what either identity is.

Most languages and package systems conflate at least two of these. A version number conflates identity with a mutable name — "my-lib 2.1.0" names something that changes underneath the label. A file path conflates name with identity — the path is how the thing is found, and moving the file changes its identity as far as anything importing it is concerned. An informal claim that "these two libraries are basically equivalent" is an unrecorded equivalence judgment, made by a human, never checked, never retracted when it stops being true. Keeping the three apart is what lets each answer its own question without contaminating the others.

2.Why identity is intensional

Identity is the SHA-256 of the canonical printed form of a function's typed source body — canonical meaning whitespace and comments are discarded, and nothing else is. Two functions that compute the same result via different code have different identities. This is a deliberate refusal, not an oversight, and it holds for a specific reason.

  1. The ladder argument. Any admission of a semantic quotient into identity has no principled stopping point short of undecidability. Alpha-equivalence looks like the safest possible first step — surely renaming a bound variable shouldn't change what a function is. But once alpha is admitted, there's no principled place to stop before beta reduction, then extensionally observed behavior, then full semantic equality — which is undecidable in general. Identity stays a syntactic decision procedure specifically to avoid stepping onto this ladder at all. Every rung looks reasonable in isolation; the only defensible place to stop is before the first one.
  2. A finer relation can never accidentally smuggle in a coarser one. Intensional identity — syntactic sameness — is strictly finer than function equality. That strictness is the point: a relation that only ever says "these are the same" when they are byte-identical can never silently claim two different functions are interchangeable. Any claim of sameness beyond that has to go through the equivalence relation explicitly, where it is graded and never trusted blindly (see /catalog/equivalence).
  3. Verification cost. Because identity is the hash of canonical text, not an encoded AST or a compiled artifact, identity claims are checkable with sha256sum alone. No trusted builder, no opaque toolchain, no compiler version to pin — anyone with the source tree and a standard hash utility can re-derive every identity and confirm it.

3.What "canonical" means, precisely

Canonicalization is the minimal transformation from source text to the text whose hash becomes the identity. It removes exactly two things: whitespace and comments — neither of which is represented in the parsed structure to begin with. It performs no other transformation and computes no quotient.

The grammar itself does most of the work here. PFCL has no infix operators and no syntactic sugar, so there is exactly one way to write any given program's structure — "parse and reprint" is a complete canonicalization procedure, because there is no alternate surface form to normalize away. Consider a hypothetical pipe operator, a |> f |> g, sugar for g(f(a)): if the grammar admitted both forms, canonicalization would have to decide which one is "the" canonical spelling, and that decision would itself become a quotient — exactly the kind of small, reasonable-looking step the ladder argument warns against. By not admitting the sugar in the first place, the question never arises.

4.Naming as the one mutable layer

A name is metadata attached to an identity — a mutable, partial function from name to hash, resolved at load time. It is the only place in the system where mutation happens at all; identity and equivalence are both immutable once established.

  1. Evolution is rebind, not rewrite. Fixing a bug produces a new identity (a new hash, because the body changed). The name is rebound to point at the new identity. Callers referencing the name pick up the fix on their next load. No existing identity is altered.
  2. No avalanche. Because the fix is a new identity plus a name rebind, not a mutation of the old identity, nothing that pinned the old identity by hash is affected. There is no propagation step that can fail partway through a dependency graph — the mechanism that causes cascading breakage in traditional package upgrades does not exist here, because there is nothing to cascade through.
  3. Pinning is a per-reference choice. A caller can reference a callee by name (follow the name wherever it currently points) or by hash (pin the exact identity, regardless of what the name later points to). Both are first-class; the choice belongs to the caller, not the callee.
  4. Diamond dependencies are structurally unstatable. The pathology where two paths through a dependency graph require different versions of the same package requires there to be versions of a thing — mutable identities under a shared name. Since identity never changes and naming is orthogonal to it, there are no two versions of one hash to conflict over. The problem doesn't get solved; it has no way to arise.

5.What this costs

Holding the three relations apart is not free.

  1. The equivalence gradient replaces a single yes/no answer. A system that folds equivalence into identity gets to say "these are the same" or "these are different," full stop. PFCL instead has to say "these are the same to grade N of confidence" — a claim that carries its own uncertainty, and takes more words to communicate.
  2. Discovery burden. Two functions that a human would recognize as "basically the same thing" may have entirely different identities and names, discoverable as related only through the equivalence relation — which depends on that relation actually having been computed.
  3. The grammar freeze is a bet. Refusing syntactic sugar to keep canonicalization trivial means language ergonomics some users will want are permanently off the table, or have to be solved some other way (e.g. tooling that renders sugar without it ever touching the identity-bearing text).

References

  1. PFCL. Catalog: content addressing, admission, layer stratification, retirement. /catalog.
  2. PFCL. Equivalence: the graded relation layered on top of identity. /catalog/equivalence.
  3. PFCL. Purity: the property every admitted function has. /purity.