New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
R.equals()'s treatment of -0 creates unpleasant corner cases #2415
Comments
That, I'm afraid, was done very intentionally. We try to take serious the idea that
for any So we follow the SameValue algorithm of Obviously there are work-arounds for this, but they are definitely less convenient. For instance, in the example above: const mirrorX = R.evolve({ x: R.negate });
const pointEquals = R.curry((p1, p2) => p1.x === p2.x && p1.y === p2.y);
const containsXMirror = (p, ps) => R.any(pointEquals(mirrorX(p)), ps); This is the first time in the more than two years since we got rid of |
In that case, do we want to introduce an operator named differently (perhaps
Then we created a unit test that checked if the negation of the zero vector is equal to itself, which makes intuitive sense. |
Feel free to create a PR for such a function if you find it important. But do note that my own feelings are mixed. This has very rarely come up as an issue. Is this something that you expect to bite you in production code? Is your unit test realistic? And how would you document that function? Would you say anything more than the equivalent of "like |
I would just say that it behaves like the === operator in the
documentation.
…On Mon, Jul 22, 2019, 17:57 Scott Sauyet ***@***.***> wrote:
@dionyziz <https://github.com/dionyziz>:
Feel free to create a PR for such a function if you find it important. My
feelings are mixed. This has very rarely come up as an issue. Is this
something that you expect to bite you in production code? Is your unit test
realistic?
And how would you document that function? Would you say anything more than
the equivalent of "like equals except that 0 and -0 compare as equal"?
Would you point out that 1 / n would generate different results?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2415?email_source=notifications&email_token=AAEE6PAW2JJNW5EAMUTYUKLQAXDGTA5CNFSM4EI2HG62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2QGIKY#issuecomment-513827883>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAEE6PCVFR5EENLTI4TCTXLQAXDGTANCNFSM4EI2HG6Q>
.
|
It most definitely does not behave like the === operator though. We have a separate function, |
@Bradcomp I think you misunderstood. Scott asked "How would you document [the new function that you are proposing]?" And Dionysis replied with "Document it as behaving exactly like 1.5 years after filing this case, I understand the reasoning behind |
What I said earlier is still a very important principle here. Although we don't achieve it 100% yet, I really don't want to make changes that moves us away from
I see this behavior of JS numbers as unfortunate, but as one of the important tradeoffs when trying to represent mathematical objects in code. We've dealt with this before. #186 and #672 talked about adding a mathematical Maybe because this has never bitten me, I don't find it a high priority. If I'm expecting numbers, these days I would probably use the obvious lambda expression in place of |
@CrossEye I just unsuspectedly ran into this and wanted to report this as a bug, only to find that this is by design. Seriously uncool. The problem is not so much with the behavior as such, it is that you don't suspect it. In any algebraic sense
is unfortunate, but IMO is heavily outweighed by the intuitiveness of I also feel that prioritizing the principle over the practicality violates what Ramda wants to be:
is elegant, yes. But API (which I understand to include behavior) is king. |
Is this something that you think better documentation would help? If so, do you have a suggestion?
I guess it depends on what you mean by "algebraic" In the general sense, the fact that they have clearly distinguishable reciprocals means that they are different. If you mean something more like arithmetic, then there are not two distinct values Our decision was not made in a vacuum. It was heavily influenced by I would not object to a PR that added a mathematical version of |
@CrossEye Please do not muddy the waters here.
No, I don't think "it depends". It's a plain mathematical fact that the whole numbers are a ring and form the groups As a side note, I find it surprising that in Ramda large parts like Functor, Foldable, Applicative, Traversable, ... are motivated and by category theory and only make sense if one understands category theory (Which itself generalises algebras). On the other hand however, Ramda takes algebraic nonsense like |
Adding a note to the documentation of The potential solution I suggested back then was to have some of the mathematical functions ( |
I doubt anybody expresses their math in code using these functions. I think |
I'm not a mathematician either, although my degrees are in mathematics. You're absolutely right that it is a quirk with little or no benefit; it's not specific to JS, as the same underlying floating point number system is used in a great number of programming languages. The problem is that JS numbers do not form a ring (and therefore not a field either), even if we exclude But the JS definition, for some good reasons, make But, if we were to be able to define a field on the JS numbers (or really on IEEE-754 numbers), then it could not conflate
I can't see this adding much. First of all, it's likely just papering over the problem. It doesn't change what happens when you use the native operators. So const x = 1 * 0
const y = -1 * 0
Object.is(x, y) //=> false and suddenly we'd have complaints that Ramda's functions weren't properly doing JS math. But more importantly, Ramda is meant to work with anybody's functions. We don't expect you to pass only I'm not dead-set against changing this. This is a very important principle for Ramda:
But we will never be able to achieve it entirely. (Think Perhaps this should be one of those places where we let it go. It might match some other parts of the language that we now miss. ( But we need to think through the implications very carefully. This is a fairly subtle change, and might make for subtle bugs. |
@CrossEye Thanks for the clarification!
I am curious; Did you ever use +/-Number.Infinity in a meaningful way? I mean isn’t it better to work with MAX/MIN_SAFE_INTEGER and Number.MAX/MIN_VALUE instead of Number.Infinity? Should JS not rather throw exceptions instead of returning the errors NaN and Infinity disguised as numbers? |
I only use them the same way that I think might be the only way JS itself really uses them, as the defaults for This is useful, but it's not enough of a rationale to inform our call on |
With real numbers const
cotangent = a => Math.atan(1 / a);
cotangent(-0) / Math.PI // -0.5
cotangent(0) / Math.PI // 0.5 So if
then what does the word same mean in that definition? If same means In conclusion I think that w.r.t.
Indeed, the functor composition: After reading good pieces of Prof. Frisby's Mostly Adequate Guide and recently James Sinclair's (@jrsinclair) nice Introduction to Static Land I was just about to fall into that trap! Incredible that still today so many tutorials on that topic present hand-waving implementations of algebraic data types. I still think ramda-fantasy did the best job in being lawful, but also approachable for beginners. Thank you for your (again) very enlightening comments! 💝 |
@semmel: That's a great example. I'm going to have to remember it next time this comes up! |
It seems intentional that
R.equals(0, -0)
evaluates tofalse
, given that it calls off to a function specifically designed to treat0
, and-0
as unequal:I can appreciate that
0
and-0
are not the "same" in the strictest sense of the word, but this can have undesirable consequences in situations where one (doesn't/shouldn't have to) care about the distinction between0
and-0
and the equality comparison is several layers removed from what one is actually doing.For (a slightly contrived) example:
Is there any straightforward way to guard against this, or something that can be done about it?
If
R.equals(0, -0) === false
really is desired behavior, perhapsR.negate
,R.multiply
, andR.divide
could be made to return0
when the underlying math operation evaluates to-0
? At least that way we could guard against these edge cases by ensuring that we useR.negate
,R.multiply
, andR.divide
instead of the built-in operators.The text was updated successfully, but these errors were encountered: