CLJS error in sorting vectors

After spending an aggravatingly long time trying to do a 2-step sort maps in cljs, as per this example with juxt, I reduced my errors to the following minimal example. It appears to be a fairly fundamental bug in Clojurescript, but I’m not sure where to report it. Another set of eyes would be nice to make sure I’m not hallucinating after fighting this all afternoon.

(sort [1 22 3])
;; => (1 3 22) ;; CORRECT

(sort [[0 1] [0 22] [0 3]])
;; => ([0 1] [0 3] [0 22]) ;; STILL CORRECT

 (sort < [[0 1] [0 22] [0 3]])
  ;; => ([0 1] [0 22] [0 3])  ;; STRING-ORDER (WRONG)

I do not know the origin or background here, but if you try this at a ClojureScript REPL, you will see a warning if you try to compare two vectors using <, but not if you call sort using < with vectors. I do not know why, but suspect that the warning is probably a kind of static analysis kind of thing done by the ClojureScript compiler, and it does not detect it when < is called as a higher-order function, as it is when called from sort:

$ clojure -Sdeps "{:deps {org.clojure/clojurescript {:mvn/version \"1.10.597\"}}}" -m cljs.main --repl-env node
ClojureScript 1.10.597
cljs.user=> (< [0 1] [0 2])
WARNING: cljs.core/<, all arguments must be numbers, got [cljs.core/IVector cljs.core/IVector] instead at line 1 <cljs repl>
true
cljs.user=> (sort < [[0 1] [0 22] [0 3]])
([0 1] [0 22] [0 3])

It seems that the correct thing to do is not to use < in this case, just use sort without an explicit comparison function, or if you want to supply an explicit one for some reason, use compare instead of <.

I’m moderately confident that you want to read this thread. It’s not clear what your particular use case is but if (sort-by (juxt first second #(nth % 2)) [...]) isn’t sufficient for your purposes [1] then that thread has some more general comparators that might be what you’re looking for. But it also contains an explanation of what’s going on under the hood (albeit in Clojure, not CLJS). Basically, sort by itself will rely on compare, which orders vectors like this:

  • vectors are sorted from fewest elements to most elements, with lexicographic ordering among equal length vectors.

Supplying < as a non-default comparator will break this behavior as it tries to interpret the vectors as numbers, which as Andy points out will give you a warning in the CLJS REPL. It may be worthwhile to note that in JVM Clojure it throws java.lang.ClassCastException because class clojure.lang.PersistentVector cannot be cast to class java.lang.Number. Simply put, less-than expects numbers.

[1]: It’s unclear from your clojuredocs/sort-by link what you wanted to do with juxt

My original goal was to get a two-level sort: sort by value and, when that’s equal, sort by alpha of key. But in general, the ability to do layered sorts.

Perfectly helpful. Thanks! That link is exactly appropo. Including usages of juxt to get secondary orderings. My personal mistake seems to have been thinking that (sort < coll) or > were, in fact, what sort[-by] does automatically with no function specified. Apparently not!

1 Like