Liking cljdoc? Tell your friends :D

Changelog

4.0.1

Fixed: stale namespaces in the jar

The 4.0.0 jar still contained me/lomin/sinho/a_star.cljc and me/lomin/sinho/search.cljc, which 4.0.0 had removed. The build copied the sources into target/classes without clearing it, so files from earlier builds went into the jar. Requiring me.lomin.sinho.search from the 4.0.0 jar fails, because it needs the priority-map libraries that 4.0.0 no longer depends on. Nothing in sinho requires either namespace. The build now deletes target before building the jar.

The 4.0.0 jar's CHANGELOG headed its newest section ## 3.0.0. It is now ## 4.0.0.

4.0.0

New feature: (pred f) predicate wrapper

The pred wrapper provides an unambiguous predicate escape hatch at the value level. Wrap a 1-arg function with pred to match against the actual value at that position:

(require '[me.lomin.sinho.pred :refer [pred named-pred]])

;; Type check
(=* {:name (pred string?)} {:name "Alice" :age 30})
;; => {:name #Pred{...}, :age 30}  (match)

;; Range check
(=* (pred #(< 0 % 100)) 50)
;; => #Pred{...}  (match)

;; Named predicate for readable failure diffs
(=* (named-pred "shape?" #(isa? % ::shape)) :not-a-shape)
;; => #Mismatch{:expected #Pred{:label "shape?"}, :actual :not-a-shape}

Important: bare functions in expected positions are compared by value equality, not interpreted as predicates. (=* string? string?) compares the function objects. Use (pred string?) to match by predicate.

Coinductive IR decision engine (internal rewrite)

The A*-based matcher from sinho 2.2 has been replaced with a coinductive IR decision engine that walks the expected structure directly:

Structuresinho 2.2sinho 4.0
Map with n keysO(n!) worst caseO(n) for atom keys; greedy bipartite fallback for complex keys
Set of m elements in n-element actualO(n!/(n-m)!)O(n^3) (Kuhn's augmenting-paths for verdicts; greedy O(n^2 log n) for diffs)
Sequence m vs nO(3^(m+n))O(m*n) edit-distance DP
Nested structure, depth d, width wO((w!)^d)O(w*d) amortised via seen-set
AtomsO(1)O(1)

Semantic compatibility with sinho 2.2

Every =* call that returned truthy in 2.2 returns truthy in 4.0, and every falsy in 2.2 returns falsy in 4.0. The diff shape may differ in two cases:

  1. Ambiguous sets of structurally-similar compound elements: The greedy bipartite matching (strategy 2) may produce a non-minimum diff on adversarial inputs. The diff is still correct (it accurately identifies structural differences), but may not be the smallest possible diff. Verdicts use exact bipartite matching (Kuhn's augmenting-paths, O(n^3)); only the diff generation uses greedy (O(n^2 log n)).

  2. Sequential elements with edit-distance alignment: The DP-based edit distance may choose a different alignment than the A* search when multiple alignments have the same cost.

Deterministic tie-breaking in set matching

Set matching breaks ties deterministically by (hash (str (pr-str l) (pr-str r))). This ensures golden-master tests do not flake across JVM runs.

specter and arrangement removed

sinho 4.0 no longer depends on com.rpl/specter or mvxcvi/arrangement.

  • A diff applies its path-tree directly instead of compiling it into specter navigators. An insertion gives the collection type that specter 1.1.4 gave on the JVM, Babashka and ClojureScript; the README's "How a diff is built" lists them.
  • The vars whose values were specter navigators are gone. From me.lomin.sinho.diff: tag-nav, children-nav, noop-terminal-nav, terminal-nav, upsert-navigator, seq-nav, navs, path-segment->navigator, node-diff-transformer, path->navigators, leaf-diff-transformer and path-tree->diff-transformer. From me.lomin.sinho.matcher: coll-walker+meta-nav.
  • Values are ordered by me.lomin.sinho.rank, sinho's own copy of arrangement 2.1.0's public-domain comparator with a ClojureDart branch. On the JVM it orders values exactly as arrangement does.

lambdaisland/deep-diff2, which 2.2 reached only through Kaocha, is now a direct dependency, so me.lomin.sinho.matcher/to-diff2 is defined on Clojure, ClojureScript and Babashka. sinho 4.0 depends on nubank/matcher-combinators, lambdaisland/deep-diff2 and lambdaisland/kaocha.

A public API of five vars

The README's "Public API" names sinho's public API: me.lomin.sinho.matcher/=*, me.lomin.sinho.matcher/to-diff2, me.lomin.sinho.matcher-combinator/=*, me.lomin.sinho.pred/pred and me.lomin.sinho.pred/named-pred. Every other var is internal.

  • =* matches and diffs the caller's values directly, instead of first rebuilding every collection to add count metadata that nothing read, left over from 2.2's A* search. Its diffs no longer carry :me.lomin.sinho.matcher/count-seq metadata, and a collection the diff does not rebuild keeps the caller's kind: a list, a lazy seq, a subvector and a map entry keep theirs, an insertion at the front of a list conses onto it, and on Babashka and ClojureScript an array map of more than eight entries stays an array map until the diff replaces one of its keys. Verdicts, costs and the caller's metadata are unchanged, and so are diffs, except on ClojureScript where a set or map holds an empty sorted set: the rebuild gave that set the hash 0, and the hash breaks ties between pairings of equal cost, so such a diff may now pair other entries at the same cost.
  • Removed, with the namespaces me.lomin.sinho.search and me.lomin.sinho.a-star, and with the dependencies org.clojure/data.priority-map and tailrecursion/cljs-priority-map that only search used: from me.lomin.sinho.matcher, compute-cost, compute-diff-paths, prepare, do-prepare, add-count-meta, meta-count-xf, atom-count and atom-count-seq; from me.lomin.sinho.diff, node-of?; from me.lomin.sinho.pred, pred-label-for-diff.
  • Made private: from me.lomin.sinho.matcher, unexpected-type, missing-type, mismatch-type, insertion?, deletion?, mismatch?, equality-partition, make-ctx, assume-pair, pair-assumed?, match-cost and coinductive-diff; from me.lomin.sinho.diff, root-node, tag-ranking, comparable-vector, compare-paths, grow-path-tree and none?; from me.lomin.sinho.matcher-combinator, calculate-weight and sinho->result; from me.lomin.sinho.timeout, now-us, Z95, ALPHA, SAFETY, DECAY, BURN-RATIO, KMAX, SAMPLE-MAX-SPACING-MS, TARGET-OVERSHOOT-FRACTION and TARGET-OVERSHOOT-CAP-MS.

Sorted collections

=* returns a diff into, or against, a sorted set or sorted map instead of throwing "Cannot compare" on ClojureScript, a ClassCastException on the JVM and Babashka, and a missing IComparable extension on ClojureDart.

An expected element or key that an actual sorted set or sorted map's comparator cannot order is not in it, so (=* #{{2 :s}} (sorted-set 1)) returns #{#matcher_combinators.model.Mismatch{:expected {2 :s}, :actual 1}}. An actual sorted collection whose comparator orders the expected value is still searched with that comparator, so the diffs of every other match stay the same.

When the diff replaces an element of a sorted set or a key of a sorted map with a value its comparator cannot order, such as a Missing or Mismatch record, that collection becomes a hash set or hash map with the same contents and metadata. One whose comparator orders the replacement stays sorted, and a diff that only replaces a sorted map's value keeps it sorted.

ClojureDart support

sinho compiles with ClojureDart, and =*, pred and the diff run on the Dart VM with the same verdicts as on the JVM and diffs that are = to the JVM's. A diff's collection types and the order of a large map can differ; the README's "How a diff is built" lists them. On ClojureDart:

  • (is (=* expected actual)) passes whatever the diff, because ClojureDart's is cannot be extended. Write (is (= expected (=* expected actual))).
  • Records print as plain maps, so a Mismatch prints as {:expected 2, :actual 3}.
  • to-diff2 and the match? adapter me.lomin.sinho.matcher-combinator are unavailable, because neither deep-diff2 nor matcher-combinators' core loads there.
  • ClojureDart itself has no queues and no isa? hierarchies.

Tests on every dialect

  • bb test:bb runs every test namespace under test/cljc, found by name, instead of a hand-kept list that had left out matcher-test and pred-test.
  • bb test:cljd runs the suite on the Dart VM.
  • CI runs the Babashka and ClojureDart suites next to Clojure and ClojureScript.

The README's "Running the tests" shows each dialect's command.

2.2.0

Previous release. See git history for details.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close