Clojure equality of collections with sequences

2019-05-01 09:59发布

I noticed that Clojure (1.4) seems to be happy to consider vectors equal to the seq of the same vector, but that the same does not apply for maps:

(= [1 2] (seq [1 2]))
=> true

(= {1 2} (seq {1 2}))
=> false

Why should the behaviour of = be different in this way?

5条回答
你好瞎i
2楼-- · 2019-05-01 10:24

Clojure's = can be thought of as performing its comparisons in two steps:

  1. Check if the types of the things being compared belong to the same "equality partition", that is a class of types whose members might potentially be equal (depending on things like the exact members of a given data structure, but not the particular type in the partition);

  2. If so, check if the things being compared actually are equal.

One such equality partition is that of "sequential" things. Vectors are considered sequential:

(instance? clojure.lang.Sequential [])
;= true

As are seqs of various types:

(instance? clojure.lang.Sequential (seq {1 2}))
;= true

Therefore a vector is considered equal to a seq if (and only if) their corresponding elements are equal.

(Note that (seq {}) produces nil, which is not sequential and compares "not equal" to (), [] etc.)

On the other hand, maps constitute an equality partition of their own, so while a hash map might be considered equal to a sorted map, it will never be considered equal to a seq. In particular, it is not equal to the seq of its entries, which is what (seq some-map) produces.

查看更多
趁早两清
3楼-- · 2019-05-01 10:32

I guess this is because in sequences order as well as value at particular position matters where as in map the order of key/value doesn't matter and this difference between semantics causes this to work as shown by your sample code.

For more details have a look at mapEquals in file https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/APersistentMap.java

It checks if the other object is not map then return false.

查看更多
一夜七次
4楼-- · 2019-05-01 10:38

It seems to me that this example points out a slight inconsistency in the notion of equality of values in clojure for this case where they are different types derived from the same type (by the seq function). It could well be argued that this is not inconsistent because it is comparing a derived type to the type it is derived from and I can understand that if the same logic was applied to this same example using vectors (note at the bottom)

the contents are the same type:

user> (type (first (seq {1 2})))
clojure.lang.MapEntry
user> (type (first {1 2}))
clojure.lang.MapEntry

user> (= (type (first {1 2})) (type (first (seq {1 2}))))
true
user> (= (first {1 2}) (first (seq {1 2})))
true

the sequences have the same values

user> (map = (seq {1 2}) {1 2})
(true)

but they are not considered equal user> (= {1 2} (seq {1 2})) false

this is true for longer maps as well:

user> (map = (seq {1 2 3 4}) {1 2 3 4})
(true true)
user> (map = (seq {1 2 3 4 5 6}) {1 2 3 4 5 6})
(true true true)
user> (map = (seq {9 10 1 2 3 4 5 6}) {9 10 1 2 3 4 5 6})
(true true true true)    

even if they are not in the same order:

user> (map = (seq {9 10 1 2 3 4 5 6}) {1 2 3 4 5 6 9 10})
(true true true true)

but again not if the containing types differ :-(

user> (= {1 2 3 4} (seq {1 2 3 4}))
false

EDIT: this is not always true see below: to work around this you can convert everything to a seq before comparison, which is (I presume) safe because the seq function always iterates the whole data structure the same way and the structures are immutable values and a seq of a seq is a seq

user> (= (seq {9 10 1 2 3 4 5 6}) {1 2 3 4 5 6 9 10})
false
user> (= (seq {9 10 1 2 3 4 5 6}) (seq {1 2 3 4 5 6 9 10}))
true


vectors are treated differently:

user> (= [1 2 3 4] (seq [1 2 3 4]))
true

Perhaps understanding the minor inconsistencies is part of learning a language or someday this could be changed (though I would not hold my breath)


EDIT:

I found two maps that produce different sequences for the same value so just calling seq on the maps will not give you proper map equality:

user> (seq (zipmap  [3 1 5 9][4 2 6 10]))
([9 10] [5 6] [1 2] [3 4])
user> (seq {9 10 5 6 1 2 3 4})
([1 2] [3 4] [5 6] [9 10])
user> 

here is an example of what I'm calling proper map equality:

user> (def a (zipmap  [3 1 5 9][4 2 6 10]))
#'user/a
user> (def b {9 10 5 6 1 2 3 4})
#'user/b
user> (every? true? (map #(= (a %) (b %)) (keys a)))
true
查看更多
三岁会撩人
5楼-- · 2019-05-01 10:43

(seq some-hash-map) gives you a sequence of entries (key/value pairs).

For example:

foo.core=> (seq {:a 1 :b 2 :c 3})
([:a 1] [:c 3] [:b 2])

which is not the same as [:a 1 :b 2 :c 3].

查看更多
放我归山
6楼-- · 2019-05-01 10:44
user=> (seq {1 2})
([1 2])
user=> (type {1 2})
clojure.lang.PersistentArrayMap
查看更多
登录 后发表回答