Skip to content

feat(data): port clojure.data's protocols, and diff IR functions structurally - #711

Open
nnunley wants to merge 1 commit into
nooga:mainfrom
nnunley:data-diff-protocols
Open

feat(data): port clojure.data's protocols, and diff IR functions structurally#711
nnunley wants to merge 1 commit into
nooga:mainfrom
nnunley:data-diff-protocols

Conversation

@nnunley

@nnunley nnunley commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

data.lg carried clojure.data's public diff but not the two protocols it is built on, so the signature matched Clojure while the contract underneath did not — nothing outside map?/set?/sequential? could take part. An ir Function, being an atom, fell through to atom-diff and reported two functions as wholly different rather than structurally so.

What changed

EqualityPartition and Diff are now defined and diff dispatches through them, matching upstream's shape: compare partitions, diff-similar when they agree, atom-diff when they do not.

One deliberate divergence, documented in the code: upstream reaches the collection families through Java interfaces, but let-go's families are defined by predicates with no shared supertype, so the Object default consults map?/set?/sequential? before falling back to :atom. A new built-in collection therefore classifies correctly without enumerating its concrete type, while any type that extends EqualityPartition still takes precedence.

Behaviour is unchanged. Every pre-existing case returns exactly what it did before — I ran all eleven against a build of the old file first, then against the new one.

The extension point, which is the point

(extend-type DiffBox
  data/EqualityPartition (equality-partition [_] :map)
  data/Diff              (diff-similar [a b] (data/diff (.-m a) (.-m b))))

(data/diff (DiffBox. {:a 1 :b 2}) (DiffBox. {:a 1 :b 3}))
;; before: [whole-a whole-b nil]      after: [{:b 2} {:b 3} {:a 1}]

ir.diff

ir.diff projects a Function into comparable data and diffs it, so a difference reports as data rather than as a failed substring match over ir.dump text:

;; two fns differing only in + vs -
only in a: {:blocks [{:insts [nil nil {:op :add}]}]}
only in b: {:blocks [{:insts [nil nil {:op :sub}]}]}

versus the 43 strings.Contains(dump, "Add v0 v1 : int")-style assertions in pkg/ir today.

Three things it deliberately normalizes, each because including them reports differences that are not differences: the :uses-cache/:uses-dirty? memo (two identical fns otherwise disagree on whether anything has asked for uses yet — pinned by a test), the :types transient (read through ir/type-of instead), and the flat :insts/:source-info side tables (blocks already name the nids they own).

The projection is explicit rather than a Diff impl because a Function is a bare let-go.lang.Atom — the same type as every other atom — so extending that type would hijack diffing for all of them. Giving Functions their own deftype would fix that, and is tracked separately against #665/EPIC-017.

Verification

test/data_diff_test.lg 9 tests / 26 assertions, test/ir_diff_test.lg 6 / 22, both green. Full .lg suite, make generate and make check-generated green. One pre-existing divergence from Clojure is pinned in the test with a comment rather than silently changed: (diff [1 2] [1 2 3]) reports both as [1 2 nil] here versus Clojure's [1 2], because upstream rebuilds each side from an index map while let-go builds fixed-length vectors.

@nnunley
nnunley force-pushed the data-diff-protocols branch from c686813 to 53653b2 Compare August 11, 2026 01:50

@mparrett mparrett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings.

Checked the protocol rewrite against the old predicate cond (nil, strings vs sequentials, cross-partition), the DiffBox extension path, and fn->data normalization (uses-cache, types via type-of, const values in :aux). Behavior matches what was already there; the Object-default divergence is documented and matches let-go's predicate-shaped collections. CI green on 53653b22.

@mparrett

Copy link
Copy Markdown
Collaborator

@nnunley Agreed on the literal; same shape as the TinyGo pin, and I’ll leave both as-is at this size. If you’re good with the rest, could you drop an approval on this one? Thanks!

Port clojure.data's EqualityPartition and Diff extension points while preserving the existing diff behavior. Keep domain-neutral comparison in the canonical clojure.data/data namespace.

Add ir.data/fn->data beside the Function representation. It projects headers, blocks, and named inst fields; excludes derived uses state and transient side tables; and reads inferred types through type-of. Consumers compare two projections with clojure.data/diff rather than through a separate ir.diff namespace.

Tests cover the canonical clojure.data require/API, foreign protocol extensions, structural IR differences, and projection normalization.
@nnunley
nnunley force-pushed the data-diff-protocols branch from 53653b2 to 20bd9ea Compare August 11, 2026 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants