I'm taking a look at this test suite https://github.com/jank-lang/clojure-test-suite and trying to get bb compatible with this. One of the things that differed with JVM Clojure in bb was this:
(/ ##Inf 0) => ##Inf ;; in bb Divide by zero
Why do I get in JVM Clojure a divide by zero in this first example below but not in the second?
((fn
([x] (/ 1 x))
([x y] (. clojure.lang.Numbers (divide x y)))
([x y & more]
(reduce / (/ x y) more)))
##Inf 0) ;; divide by zero
(. clojure.lang.Numbers (divide ##Inf 0)) ;; ##Inf Sorry for the lag. Yea, @borkdude, it’s probably all related to boxing. I was finding all sorts of subtle things related to boxing. As you’re aware, Clojure JVM tries to keep things unboxed and relies on the underlying JVM to detect issues. When things are boxed, it often uses its own code paths to try to detect issues. I found some, ahem, nuances around #NaNs that generated some #clojure discussion a few months back when I was writing those tests. I won’t claim I covered every possible case as I wasn’t specifically trying to force both boxing and unboxing for every test, but I did try to capture differences when I found them, at least so they would be documented, even if never fixed. It looks like you guys worked this out a couple days ago. If there’s still more questions, I’m happy to answer with what I know, which is probably less than you already do.
Is this perhaps something that should be fixed in Clojure itself?
I guess it has something to do with boxing:
(defn f [x y]
(. clojure.lang.Numbers (divide x y)))
(f ##Inf 0) ;; throwsThis doesn't throw:
(defn f [x y]
(let [y (double y)]
(. clojure.lang.Numbers (divide x y))))
(f ##Inf 0)and this throws:
(defn f [x y]
(let [y (long y)]
(. clojure.lang.Numbers (divide x y))))
(f ##Inf 0)
Maybe a reasonable fix for SCI would be to use this replacement for inline division
f (condp identical? f
/ (let [div (fn [x y]
(if (double? x)
(let [x (double x)]
(/ x y))
(/ x y)))]
(fn
([x] (div 1 x))
([x y] (div x y))
([x y & more]
(reduce / (div x y) more))))
f)just digging around in the clojure code, it looks like double y calls doubleValue on x and then uses the division operator, so it's getting java's division. The long overload casts the long to an Object and then uses the (Object, Object) overload, which has the divide by zero check.
I assume that basically everything in bb will be boxed (??), so we'd always get the (Object, Object) overload, which has the zero check but not an ##Inf check
probably related: <https://ask.clojure.org/index.php/1143/double-division-by-zero-inconsistency?show=1143#q1143>
right. I can fix this to align with the jank/clojure-test-suite (I have a fix in a branch) but this would maybe cause other behavior for boxed arguments that before always threw. so maybe there isn't a good solution overall
Yeah, I haven't thought through all the possibilities, but I wonder if that fix would flip the tables, and maybe some permutations that would throw in JVM clj will start succeeding in bb. It feels like to make the behavior consistent with JVM, bb would need to decide whether or not the number would have been boxed.
> that would throw in JVM clj will start succeeding in bb yeah exactly > need to decide whether or not the number would have been boxed. indeed. not impossible. if the thing has a type tag (or inferred) or it's coming from a literal
but not sure if it's worth the trouble
for now I made some tweaks to the test suite for bb as it passes right now: https://github.com/jank-lang/clojure-test-suite/pull/94
btw the "fix" is in the SCI branch div-by-zero
if you're curious
I can maybe fix it for cases where SCI is absolutely sure the value would have been unboxed
I believe this can only be true for local values, not for function return values, right?
except when those function return values are inlined, like inc etc
which make it a bit more complex
but not impossible
I think so.. the wild world of boxing has never been a strong suit of mine.I assume literals would always be unboxed, and anything else is potentially indeterminate. The 'was this inlined' thing feels to me, at least superficially, like it's wandering toward trying to make a determination about 'boxedness', but that's just an opinion. 🙂
do you mean in SCI?
the "was this inlined" thing has been there for a long time to speed up things a little. you don't have to do through the var in that case, since clojure also inlines it
but it can also be used to determine if something was unboxed in clojure
maybe
oh... if there's already a point there, then that's cool - I made the (faulty) assumption that it would be a specialization for trying to handle this edge case, which made it feel like it might've been significant effort to make a net new determination to handle an edge case.
the specialization is just for / but this is inside the check for inlined vars
we could have other specialized functions but so far the ##Inf and ##NaN thing is the only thing that has come up (in an earlier issue as well)
Yeah, I'm with ya... I was missing the fact that "was this inlined" already existed. Is that the only test that failed? That test suite seems to have a number of "surprisingly, X happens" comments in the numeric functions, so it's maybe a testament to bb's parity if there's only one edge case identified where it's different. 🙂
there were some others like (byte 128.1) . the inlined version of clojure checks this (in the compiler I think), but bb always goes through vars and those don't check
@droberts3 For vis, since he cooked these up.