clojure 2026-04-01

It took me one hour to figure out why a certain unit test failed to compare two maps:

(is (= (-> call-result vals first)
       ;;=> {:schema [:enum :de :fr :it], :annotations [{:item :doi-1, :rater :r-1, :annotation :de}]}
       (-> fixed vals first class)
       ;;=> {:schema [:enum :de :fr :it], :annotations [{:item :doi-1, :rater :r-1, :annotation :de}]}
       ))
;;=> false

(-> call-result vals first class)
;;=> clojure.lang.PersistentHashMap
(-> fixed vals first class)
;;=> clojure.lang.PersistentArrayMap
The "print" identical. I don't think this is new, but I experience it the first time, and I do a lot of unit testing in clojure and having a habit of comparing function results and "fixed maps in test code".

I even don't know what I do in my code that goes from one clojure.lang.PersistentHashMap to clojure.lang.PersistentArrayMap. I am pretty sure, I only use standard assoc, select-keys, get, get-in

it’s when you hit 8 or more items and you are depending on the order of an unordered collection

but what i’ve done in the past is that using sets is correct semantically but bad for error reporting

(let [x (zipmap (range 10) (repeat :value))
      y (dissoc x 3)]
  (clojure.test/is (= (keys x) (keys y))))

FAIL in () (NO_SOURCE_FILE:38)
expected: (0 7 1 4 6 3 2 9 5 8)
  actual: (0 7 1 4 6 2 9 5 8)
    diff: - [nil nil nil nil nil 3 2 9 5 8]
          + [nil nil nil nil nil 2 9 5 8]
false
here’s the output of just unordered

user=> (let [x (zipmap (range 10) (repeat :value))
      y (dissoc x 3)]
  (clojure.test/is (= (set (keys x)) (set (keys y)))))

FAIL in () (NO_SOURCE_FILE:3)
expected: (= (set (keys x)) (set (keys y)))
  actual: (not (= #{0 7 1 4 6 3 2 9 5 8} #{0 7 1 4 6 2 9 5 8}))
false
when you use sets

user=> (= (array-map :a 1) (hash-map :a 1))
true
user=>
it is best not to depend on the concrete types of things

➕ 1

although we seem to have a test output that is nice

(let [x (zipmap (range 10) (repeat :value))
      y (dissoc x 3)]
  (clojure.test/is (= (set (keys x)) (set (keys y)))))

FAIL in () (NO_SOURCE_FILE:44)
expected: #{0 7 1 4 6 3 2 9 5 8}
  actual: #{0 7 1 4 6 2 9 5 8}
    diff: - #{3}

There is something else going on. Even when haing two different types of maps, the content is identical. (as printing show) and this is "true": (= {:a 1} (hash-map :a 1) ) ;;=> true

clojure.lang.PersistentArrayMap
clojure.lang.PersistentHashMap

yes, = treats maps of different types as equal if they contain the same thing

also seqs that contain the same values in the same order are treated as equal to vectors

That what I think... So how can a PersistentArrayMap and a PersistentHashMap which "print the same in repl" not be "=" ?

the code you showed above is not comparing two maps, is that what you are actually running?

it looks like maybe you are calling first on a map? which, the iteration order of items in a map is not really strongly defined

(def call-result
  (llma/schematise [{:item :doi-1
                     :rater :r-1
                     :annotation {:domain :de, :capital "Berlin", :climate :cool}}]
                   Country)
  )

(def fixed 
  {[:domain] {:schema [:enum :de :fr :it]
              :annotations [{:item :doi-1 :rater :r-1 :annotation :de}]}
   [:capital] {:schema :string
               :annotations [{:item :doi-1, :rater :r-1, :annotation "Berlin"}]}
   [:climate] {:schema [:enum :cold :hot :warm :cool]
               :annotations [{:item :doi-1 :rater :r-1 :annotation :cool}]}
   [] {:schema [:map [:domain [:enum :de :fr :it]] [:capital :string] [:climate [:enum :cold :hot :warm :cool]]]
       :annotations [{:item :doi-1 :rater :r-1 :annotation {:domain :de :capital "Berlin" :climate :cool}}]}}
  )

(is (= call-result fixed))

for array maps it is something like insertion order, for hash maps it depends on the hash values of keys

and he "fixed" is a copy/paste from call-result from a prevoius call.

its very simple data, maps of :keywords

what is call-results?

the same as fixed, printed in repl....

they print equal....

given it is output from llm I would not trust printing

are you using println or prn?

; {[:domain] {:schema [:enum :de :fr :it], :annotations [{:item :doi-1, :rater :r-1, :annotation :de}]}, [:capital] {:schema :string, :annotations [{:item :doi-1, :rater :r-1, :annotation Berlin}]}, [:climate] {:schema [:enum :cold :hot :warm :cool], :annotations [{:item :doi-1, :rater :r-1, :annotation :cool}]}, [] {:schema [:map [:domain [:enum :de :fr :it]] [:capital :string] [:climate [:enum :cold :hot :warm :cool]]], :annotations [{:item :doi-1, :rater :r-1, :annotation {:domain :de, :capital Berlin, :climate :cool}}]}} nil clj꞉llmagreement.api-test꞉> ; {[:domain] {:schema [:enum :de :fr :it], :annotations [{:item :doi-1, :rater :r-1, :annotation :de}]}, [:capital] {:schema :string, :annotations [{:item :doi-1, :rater :r-1, :annotation Berlin}]}, [:climate] {:schema [:enum :cold :hot :warm :cool], :annotations [{:item :doi-1, :rater :r-1, :annotation :cool}]}, [] {:schema [:map [:domain [:enum :de :fr :it]] [:capital :string] [:climate [:enum :cold :hot :warm :cool]]], :annotations [{:item :doi-1, :rater :r-1, :annotation {:domain :de, :capital Berlin, :climate :cool}}]}}

could the llm be generating strings like ":foo" that print like :foo when using println

similar issues can happen with symbols, etc

it's not yet from an LLM, is mocked.

mocked how? what is the ';' in front of the values from?

The have different hash-codes, which is very weired, as tey look equal.

you can try using something like clojure.data.diff

ok, different classes, so hash-code is different.

user=> (hash (array-map :a 1))
1772842048
user=> (hash (hash-map :a 1))
1772842048
user=>

(although hash is strictly speaking not hashCode)

I compared char-by-char in editor, they are identical.

user=> (.hashCode (array-map :a 1))
1013910568
user=> (.hashCode (hash-map :a 1))
1013910568
user=>

hmm, different hash-code...

you are comparing character by character of the print out, a serialized representation

but you are comparing data structures in memory

and there are ways to construct structures that are different in memory that print the same

true...But I never saw this with "simple" clojure data structure like "maps of keywords".

depending on how you are printing exactly or even using things like 0 width characters

or things like unicode dashes

But what can I do to fix it.... ?

As I have no "clue" how they differ.

depends, you can use something like clojure.data.diff

sorry clojure.data/diff

user=> (require 'clojure.data)
nil
user=> (doc clojure.data/diff)
-------------------------
clojure.data/diff
([a b])
  Recursively compares a and b, returning a tuple of
  [things-only-in-a things-only-in-b things-in-both].
  Comparison rules:

  * For equal a and b, return [nil nil a].
  * Maps are subdiffed where keys match and values differ.
  * Sets are never subdiffed.
  * All sequential things are treated as associative collections
    by their indexes, with results returned as vectors.
  * Everything else (including strings!) is treated as
    an atom and compared for equality.
nil
user=>

I see know that I have empty-vector as "key" in my map ....

can that be the problem ?

maps equality is determined by the contents of the maps, so you need to not just compare the top level maps for =, you need to start comparing the contents of the maps recursively looking for (hopefully) some scalar element (a keyword or a symbol or string or something) that will print the same and compare as not equal

and then you can investigate the parts of that "scalar" (basically the characeters/bytes) to discover why they print the same but are unequal, my guess is due to copy and pasting through slack or something you have unicode dashes in some of the keywords

I found the explanation: (-> call-result (get [:domain] :annotation) :schema class) ;;=> malli.core$enumschema$reify$reify__13444 (-> fixed (get [:domain] :annotation) :schema class) ;;=> clojure.lang.PersistentVector

One things looking like a vector, is not a vector, but a "malli" refeided thing.

I had a fun example of the unicode dash thing all tee'ed up too 😕

user=> (def a :foo‐bar)
#'user/a
user=> (def b :foo-bar)
#'user/b
user=> (= a b)
false
user=> (= (name a) (name b))
false
user=> (vec (.getBytes (name a)))
[102 111 111 -30 -128 -112 98 97 114]
user=> (vec (.getBytes (name b)))
[102 111 111 45 98 97 114]
user=>

so that "thing" does not implement equal....

thats kno... dash vs dash.....

i’m quite interested in the result here. we cache malli schema. and if there are subtleties around equality because the reify instances are all instance equality that would be good to know

I asked in #malli

👍 1
Alex Miller (Clojure team) 2026-04-01T18:48:16.073859Z

probably not equal but equiv fyi

👍 1

I had a(probbaly wrong) assumption, that this shoudl be true:

I think I was assuming, that this is true, but's its not:

(= 
 [:enum :a]
 (m/schema [:enum :a]))

-> false
as "they print equal"....

These are equal:

(= 
 [:enum :a]
 (m/form (m/schema [:enum :a])))

Even "same schemas" are not equal...

(=
 (m/schema :string)
 (m/schema :string))
;;=> false

something to keep in mind