replikativ / hasch

Cross-platform (JVM and JS atm.) edn data structure hashing for Clojure.
Eclipse Public License 1.0
111 stars 13 forks source link

Thoughts on adding support for ratios #4

Closed jcf closed 5 years ago

jcf commented 7 years ago

Hello there,

I was recently running some generative tests and noticed ratios can't be encoded. Do you think it's okay to add support?

Happy to put together a PR when I get time if you're interested.

All the best,

James

whilo commented 7 years ago

It is reasonable to do so, but one must keep in mind, that the same expression from Clojure and ClojureScript have to be hashed the same. While there might be issues with floating point rounding, I have decided to cast all numbers to floating point and treat them the same. I suggest to use the same approach for ratios as they are not supported in ClojureScript yet:

https://github.com/clojure/clojurescript/wiki/Differences-from-Clojure

This is a bit unfortunate, because dedicated type information and more important the ensured perfect precision of ratio (without floating point problems) make a dedicated approach more sound. But this would break cross-platform hash equality. Alternatively one could make an explicit exception along the lines that some standard types (ratio) are not supported cross-platform, but this would be surprising and even worse than getting strange hashing errors because of floating point dissimilarities, I think. (1)

So I am willing to convert ratio to floating point and hash it the same for pragmatic reasons, what do you think?

(1) Note that I am doing a lot of numerical stuff, so I find it especially troubling that JavaScript has these restrictions. I just don't ensure hashes of floating point numbers across runtimes as much, so this limitation has not hit me yet, but I fear it will....

whilo commented 7 years ago

Hey! :) Are you not interested in adding ratio support in Clojure then? Floating point precision is actually a general pain in modelling continuous spaces with concrete digital abstractions, but I think it is better than not support ratios.

jcf commented 7 years ago

@whilo I am interested. Sorry, just haven't gotten around to putting something together. Hopefully later this week.

whilo commented 7 years ago

@jcf no problem. If you do not find the time, let me know and I can also add it :).

jcf commented 7 years ago

@whilo I've gotten as far as this: https://github.com/jcf/hasch/commit/1c17570978c684e928fe85ad2e1e3c4f4beb01f7

My thinking was I could encode the type of data in the same way you can tag a piece of data in EDN etc, but I've not yet tracked down the "tagging" equivalent in Hasch.

I was hoping I could add some 'abstract' type like a hasch.Ratio with both numerator and denominator in order to defer producing the less accurate float. I'm not sure this is possible, and won't have much time this evening to dig deeper. Hence, I'm sharing my progress so far. Sorry!

whilo commented 7 years ago

I see, the approach is reasonable so far. I am not sure though what you would gain by introducing a hasch.Ratio type. The problem is that in cljs the ratio will never reach hasch, but will become a float immediately. The best fix (to be honest), would be to add ratio support to cljs. This would solve all ratio problems and circumvent floating point messiness. I guess the problem here is the lack of integer arithmetic on js, but there are BigInteger libs out there and I think it should be doable, but we need to do some research why it has not happened so far.

jcf commented 7 years ago

It looks like Gary Fredericks has written a Ratio implementation for Clojurescript that may be interesting: https://github.com/gfredericks/cljs-numbers

whilo commented 7 years ago

This is cool, are there plans to integrate it (since it build on gclosure, it should be fairly practical)? Maybe we should talk to @gfredericks. I am just preparing my :clojureD talk, but I will have time next week...

gfredericks commented 7 years ago

More recently I made a library for cross-platform bigints+ratios: https://github.com/gfredericks/exact

I'll add a note to the cljs-numbers readme to point there, since it's entirely superseded.

gfredericks commented 7 years ago

Regarding ratios and integers in cljs in general:

I spoke to david nolen about this repeatedly several years back, and the idea of first-class support for any kind of rich numerics in cljs was always a non-starter, primarily to preserve performance in the core arithmetic functions (which compile directly to js operators).

Unfortunately this severely limits the possibilities for libraries (such as hasch and exact) that aim to be cross-platform and also support rich numerics. Anything's possible if users are willing to bend over backwards to adapt their codebase, but it can be pretty bad.

jcf commented 7 years ago

Thanks for the info, @gfredericks!

@whilo do you think adding some form of opt-in Ratio support with the clear caveat that you need to modify your ClojureScript environment to ensure hashes match is reasonable? Something like the user has to require hasch.clojure-numerics to include Ratio support, knowing full well that this will 'break' ClojureScript?

In my use case I'm not targeting JS so maybe I'm being overzealous in taking on the pain of adding support for these numeric types to a JS runtime… I suppose it could even be an external library with a big warning message that this is only suitable for JVM use?

whilo commented 7 years ago

@jcf I would recommend that you just extend the hash protocol in your project as this is a reasonable approach with protocols. I tend to add a code snippet for this in the README, if you don't mind. I might also add it for the JVM and in ClojureScript you will still get hashed floats then, which is somewhat unavoidable. I have to think about it.

@gfredericks Thanks a lot for the information! JavaScript keeps disappointing me, especially with numerics and all the caveats that come from it. I also tried to fix cljs dynamic binding in an asynchronous context, which bid me and is also due to the lack of an efficient execution context. Maybe some things can still be adressed or we have to wait for WebAssembly to inject byte codes where needed...

whilo commented 7 years ago

@jcf Does this approach work for you?

jcf commented 7 years ago

@whilo works for me. :+1:

Sorry for not getting back to you sooner. Super busy at the mo.