Fork me on GitHub
#cljs-dev
<
2023-03-27
>
hifumi12306:03:01

Posting in here because I think it is relevant for CLJS core team to know: It looks like the default implementation of -hash will compute goog/getUid. This has led to two incidents in the recent past where people are using js/BigInt and this is causing unexpected results, especially in code that relies on reproducible hashes (e.g. PersistentHashMap). Why? Well according to the Closure library docs, getUid “mutates the object so that further calls with the same object as a parameter returns the same value.” It also mentions it is “unsafe to generate unique ID for function prototypes.” So I assume it is incorrect for goog/getUid to be computed on things like BigInts and other objects that do not “behave” like an ordinary JS object, in the sense that you cannot set a field. You can observe this in a REPL as follows:

cljs.user> (def x #js {})
[#js {}]
cljs.user> (goog/getUid x)
101
cljs.user> x
{"closure_uid_121240583" 101}
cljs.user> (def y (js/BigInt 42))
[#object[BigInt 42]]
cljs.user> (goog/getUid y)
104
cljs.user> y
#object[BigInt 42]
Pinging @p-himik since he discovered the hash-code issue a few weeks ago.

dnolen13:03:49

@hifumi123 you need to supply a -hash implementation for such types

Sam Ritchie15:03:19

(For example)

p-himik15:03:09

Which isn't a great thing to do on a built-in type, especially if it's done in a library.

Sam Ritchie15:03:39

is there a good alternative?

Sam Ritchie16:03:23

in this case users can supply BigInt coefficients to a polynomial, and that data structure stores them in a map

Sam Ritchie16:03:19

I’m not sure what else to do here, given the errors that can appear once those maps get more than 8 entries

p-himik16:03:37

Do you use bigints as map keys or set entries? > is there a good alternative? Document what needs to be done if a user wants to use bigints. Optionally, provide an additional namespace that isn't used by anything but that can be required by a user to extend js/BigInt. This way, a user has full control over what's going on.

p-himik16:03:03

And it's the same for CLJ as well. One shouldn't extend something unconditionally. It might be fine to do in an app, it's absolutely not fine to do in a library unless it's meant to be the center of the universe for any project that uses it.

Sam Ritchie16:03:28

bigints are also automatically created by the polynomial gcd process… basically the arithmetic forces a promotion outside of the user’s control

Sam Ritchie16:03:41

@p-himik yes, agree. in this case it kind of is meant to be the center of the universe!

Sam Ritchie16:03:09

but yeah I do agree that some way to at least opt-out of this would be a good affordance

hifumi12320:03:06

@p-himik I think supplying a hash code for js/BigInt is fine because there is already examples of native JS types being extended in cljs.core More importantly, BigInts as hash keys work in JVM Clojure, but not CLJS. So implementing IHash would help “converge” the behavior of Clojure and CLJS. Here is a quick demo in a Clojure REPL.

user> {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 (bigint 9) "I'm accessible!"}
{0 0, 7 7, 1 1, 4 4, 6 6, 3 3, 2 2, 9N "I'm accessible!", 5 5, 8 8}
user> (get *1 (bigint 9))
"I'm accessible!"

Sam Ritchie20:03:18

I wish the numeric tower were at parity with the JVM version, especially as BigInt is a JS primitive

p-himik21:03:41

> in cljs.core Because that ns is the center of the CLJS universe. A third-party library is not. > More importantly, BigInts as hash keys work in JVM Clojure, but not CLJS That's what CLJS should solve though, or GCL, or a maintainer of an app (so the scope is limited only to that app), not a library. In fact, all interested parties can already create issues for https://github.com/google/closure-library and vote for https://ask.clojure.org/index.php/10938/can-js-bigint-be-used-as-a-map-key-in-clojurescript and even sign a CA and write a patch. ;) (Scratched out GCL because goog.getUid explicitly states that it accepts only objects whereas bigints are primitives).

p-himik22:03:54

@dnolen What do you think of explicitly handling js/BigInt and js/Symbol (someone else has complained about it before) in the hash function, given that those create primitives and should not be passed down to goog.getUid? I couldn't find any previous discussion. While extending those types to IHash works, it's problematic because different libraries might do the same thing but use different hashing approaches. E.g. in the case of js/BigInt djblue/portal and mentat-collective/emmy use different radices for turning a bigint into a string and hashing that string.

👍 2