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 unordereduser=> (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 setsuser=> (= (array-map :a 1) (hash-map :a 1))
true
user=>
it is best not to depend on the concrete types of thingsalthough 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.PersistentHashMapyes, = 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}}]}}
printing of both
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
will try.
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 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"....Even "same schemas" are not equal...
(=
(m/schema :string)
(m/schema :string))
;;=> falsesomething to keep in mind