-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
WIP: Deprecate undefined == comparisons #18856
Conversation
7abc029
to
930d683
Compare
@@ -9,6 +9,9 @@ abstract Enum | |||
|
|||
Base.convert{T<:Integer}(::Type{T}, x::Enum) = convert(T, box(Int32, x)) | |||
|
|||
Base.:(==)(x::Enum, y::Integer) = Int32(x) == y | |||
Base.:(==)(x::Integer, y::Enum) = x == Int32(y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these shouldnt exist. Enum and Integer aren't compatible types. Why was this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. These lines were broken from the start, so that's the first bug uncovered by the new strict rules.
@@ -992,7 +993,7 @@ julia> findnext(A,3) | |||
""" | |||
function findnext(A, start::Integer) | |||
for i = start:length(A) | |||
if A[i] != 0 | |||
if !isequal(A[i], 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a recent proposal to make this 'iszero(x)' or zero(T), since == 0 doesn't really work right for some custom types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would fit well with this PR. Then we would need have the fallback iszero
calll isequal
(rather than ==
). And since isequal(0, -0.0) == false
, we should define iszero
on floats so that iszero(-0.0) == true
So the new semantics will be: == numeric equality, isequal general equality, and === egality? |
930d683
to
3a740f8
Compare
|
That makes sense. Could we preserve a subset of useful comparisons between noncomparable types?
I would prefer not to use |
Without conversions, these comparisons always returned false.
3a740f8
to
e062942
Compare
Yeah, these exceptions should definitely be considered. |
Use isequal() or type checks in all places where the types might not be comparable.
9ec527e
to
424d391
Compare
I really want to avoid having a long complicated list saying when to use +1 to the first commit though. |
Maybe we should add some more equality predicates. We have a countable list of operators Who could've thought that equality was such a hard problem? |
Anyone who paid attention to decades of Lisp debates on the subject :) |
@JeffBezanson: I think there's something very telling about the fact that the rest of this change forces the part that you like – and that without the errors that a stricter
We've tried to "sweep |
Jeff's point about negative zeroes still worries me. Why is it that type-permissive equality comes with additional stuff, like NaN and zero behaviour? It seems that two orthogonal concepts are being combined. |
They are theoretically orthogonal, but in practice there's one strong reason to have them linked: the fact that one wants to be able to use |
Yes, but it also forces changes that I don't like, namely randomly changing several comparisons from I agree that if |
isequal(::Void, ::Void) = true | ||
isequal(::Void, ::Any) = false | ||
isequal(::Any, ::Void) = false | ||
isequal(x::Union{Method, TypeName}, y::Union{Method, TypeName}) = x === y |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where did this come up? Would it be possible to address by changing some more calls to use ===
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, it's hard to track since the failure happens during bootstrap, and the backtrace doesn't mention the function name (even with julia-debug
). Is there anything I can do to improve that?
I opened #9381 specifically because it's probably better to use |
There is some room for debate here, but it's clear that some comparisons don't make any sense like |
I thought of a more formal way to express what I'm thinking. This change breaks a type embedding property: when you move from domain A to a super-domain B (e.g. moving from real to complex), elements in A should behave the same. So moving from a comparison that works on |
Are you talking about collections? If we make |
I'm mostly talking about moving to wider types. For example I start with Having a function that both expands the domain within which equality works, and also changes how some previously-comparable elements compare is a gotcha. Of course, one solution is to use |
I'm not sure this is relevant here, so please feel free to ignore if this is off base. But for Query.jl it would be really key that |
@davidanthoff But as far as I can tell, that doesn't do what is expected. julia> 1 == Nullable(1)
false |
@TotalVerb Yes, my hope is that the definition will be changed in base so that Right now I overwrite that definition in the Query package, but that of course is terrible (type piracy and all of that). |
The current fallback on But let's not add
@JeffBezanson I guess one can see it that way, but it can also indicate that we need
I wonder whether the core of the issue is that we don't have an easy to type operator for FWIW, Go panics when comparing "not comparable" values. Of course it's a static language so it's a bit different. |
I have to say that I don't find the |
@StefanKarpinski Concrete cases of EDIT: perhaps more to the point, cases of |
Sure, you would not use
I agree it would be great to have some sort of infix for |
Heh, I just noticed that |
Doesn't look like this PR has a chance of being merged. |
This PR is a first stab at deprecating cross-type comparisons for which we previously fell back on
===
(#15983).The first commit uses
===
where it makes sense and can be merged separately (#18853).The second commit is more controversial, and could also be discussed in a separate PR: it changes all
in
andfind*
methods to useisequal
consistently (until know onlyDict
did this forin
). Relevant discussions are #9381 (aboutin
), and #16269 and #18668 (aboutfind
). This change allows making==
an error for arbitrary comparisons without breaking too much code. Indeed, it is relatively common to store heterogeneous non-comparable types in arrays or non-standard dicts (which use==
contrary toDict
): in particular, strings and chars are stored together in the LineEdit/REPL code, but that's also the case of other high-level objects likeBase.Multimedia.display
. Note that this change is not strictly needed to make==
throw an error for not-comparable types: the code could be made more careful instead. But I started with this minimally invasive strategy (in terms of lines of code to change) to be able to go to the next step without wasting time.The third commit changes more comparisons to be robust against the change in behavior of
==
. It doesn't make much sense in isolation, but it doesn't break the tests either.The fourth commit is the most interesting and challenging. Indeed,
isequal
falls back to==
, yet we don't want the former to print deprecation warnings nor throw errors (after the next release). So we need a way forisequal
to change the behavior of==
for its needs. So far I've found two possible approaches:try... catch
block to catch theNotComparableError
thrown by==
. This is unacceptable for performance unless the compiler is able to determine statically that the exception is thrown reliably by a method. Also, it doesn't work to silence deprecation warnings (for the first phase).@inbounds
/@boundscheck
to tell==
that no exception should be thrown.isequal
would set this flag when calling==
. This is the one I've retained here, temporarily abusing@inbounds
; if we choose it, a separate macro would be warranted (e.g.@comparable
).Actually, this second approach is quite powerful, and it would offer a alternative behavior: instead of having
==
throw andisequal
returnfalse
, both methods could throw by default, unless they are marked with@comparable
.in
andfind*
would use it by default to allow comparing arbitrary types. The advantage of this solution is that we wouldn't have to conflate two meanings intoisequal
: IEEE floating point semantics and arbitrary comparisons don't necessarily go together. The downside is that it could be more complex for users.Anyway, this is obviously quite rough. Comments and advice welcome.