Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ASeq require a collection to implement Sequential to allow equivalence?

Tags:

clojure

I'm writing my own range that implements ISeq. My original implementation for equiv was just calling seq on my range and the other list, and comparing using =:

(defn equals? [this-range other-range]
  (= (seq this-range) (seq other-range)))

This seemed fine, but then I ran into some odd behavior:

(= (new-range 5 10)
   (range 5 10))
=> true

(= (range 5 10)
   (new-range 5 10))
=> false ; Uh oh

Where new-range is my custom constructor.

To see how LongRange handles equivalence, I checked its source. It delegates to ASeq, and ASeq's equiv method starts with the lines:

public boolean equiv(Object obj) {
    if (!(obj instanceof Sequential) && !(obj instanceof List)) {
        return false;
. . .

Since my range doesn't implement Sequential or List, this check fails. It doesn't even try to iterate my range to do a value comparison.

What's the reasoning here? Sequential is just an empty interface. It seems to just exist to "mark" classes as being sequential without requiring any methods.

I could just have my range implement Sequential to allow the check, but I'm wondering if my equivalence function should include the same check as ASeq as well. It seems like an unnecessary check though, as seq will already fail on a bad argument via clojure.lang.RT/seqFrom.

What is the purpose for the Sequential check, should I implement Sequential to appease such methods, and should I do such a check as well in similar methods?

like image 760
Carcigenicate Avatar asked Nov 07 '22 23:11

Carcigenicate


1 Answers

What return value would you like to get from

(= [1 2] #{1 2})

or from

(= '([1 2]) {1 2})

? In both cases, the two collections are indistinguishable after you seq them (depending on how #{1 2} gets hashed, anyway). But they clearly are not equal collections: maps and sets behave very differently from lists and vectors. The main thing distinguishing each pair is that one of them is Sequential (intended for use in a sequential manner) and the other is not. That's what this tagging interface is for.

So, yes, you should check Sequential before declaring your sequential object to be equal to some other object: it cannot reasonably be equal to anything non-sequential.

like image 164
amalloy Avatar answered Nov 15 '22 06:11

amalloy