-
Notifications
You must be signed in to change notification settings - Fork 13
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
Two equal priority queues with keys can compare as unequal #78
Comments
Hrmmmm..... Which operations don't respect |
I'm not sure what you mean, the issue is that entries with equal keys (but different values) may be in any order. |
The usual expectation is that if f :: A -> B
x, y :: A
x == y = True then f x == f y = True We say that Typically, "safe" API functions should respect We should start by looking at how insertion and view interact with |
Similarly for binary operations. Given a == a' = True
b == b' = True users will want to be able to rely on merge a b == merge a' b' = True My bet is that the only ways to make sensible and abstract a == b = sort (toList a) == sort (toList b) I believe we currently consider only |
So I did a bunch of (10000) QuickCheck tests and it seems that |
Try merges? |
With a better testing method, I actually found an example where ghci> import qualified Data.PQueue.Prio.Min as P
ghci> a = P.fromList [(0, 0), (0, 1), (-1, 0), (-1, 0)]
ghci> b = P.fromList [(-1, 0), (0, 0), (0, 1), (-1, 0)]
ghci> a == b
True
ghci> P.insert 0 0 a == P.insert 0 0 b
False |
Ouch. That's really nasty. I propose either:
Can you think of any situation where it's essential to have an |
Hmm, not really, though I feel a bit uneasy about removing an instance, as that would be a breaking change (which would be fine with a major version bump ig). I took a look at how other languages (C++, Java, Rust, Scala) handle this problem and it seems none of those implement custom equality for priority queues. |
Those languages are presumably using pointer equality. I believe the correct thing is the sorting |
At least Rust and C++ don't, Scala 3 doesn't either afaict, Java always has a default implementation, so that's kinda unfair. |
So you prefer to remove the instance rather than implement the sorting one? |
I definitely prefer removing the instance over requiring The alternative would be to fix the instance. We could compare the sizes and then iterate through the first argument and for each key-value pair look up if it also exists in the second argument. That would be slower, but it should work. |
That would give a pretty awful user experience, I think. We'd have |
The |
Right, but you won't object to |
Right. I just benched a (naive) implementation of my proposal and it's a lot slower. So I'm fine with removing the instance, though perhaps let's first add a deprecation comment (unfortunately, instances cannot be deprecated). |
Why don't we just do the sorting thing? It has clear semantics, which are almost obvious from the |
I explained why I don't like that option in #106. |
So you don't want a strong constraint because it won't work for some things, and you'd rather remove the instance and have it work for nothing. I want the strong constraint. Whom should we ask to settle this disagreement? |
If we use the slowSameMinPQueue :: (Ord k, Eq a) => MinPQueue k a -> MinPQueue k a -> Bool And we can even provide a newtype to use it: newtype SlowEqMinPQueue k a = SlowEqMinPQueue { unSlowEqMinPQueue :: MinPQueue k a }
instance (Ord k, Eq a) => Eq (SlowEqMinPQueue k a) where
(==) = slowSameMinPQueue This can be used as a newtype Floop k a = Floop (Either Int (MinPQueue k a))
deriving Eq via (Either Int (SlowEqMinPQueue k a)) |
in my opinion these aren't equal queues. I believe a priority queue encodes a queue with a priority, if the priorities are all the same I'd expect it to behave like a queue, (also known as a list). if you'd enforce these as equal you'd end up with something akin to a poset. |
I wanted to disagree at first, but now I think you're actually making a good point. If two priority queues produce (when calling |
@jappeace that's not the traditional definition. It's possible to implement such a structure (see the |
@konsumlamm that's why I think it's best to view extraction and traversal operations as nondeterministic. They can take equals to non-equals. |
The only way to test "equal for all purposes, deterministically" is plain structural equality. But that's flipping useless. |
I started a discussion about this on Discourse: https://discourse.haskell.org/t/equality-for-priority-queues/6355. |
That's been good so far. It hasn't changed my extreme negative opinion of the quadratic instance, but I have warmed somewhat to the idea of removing the instance altogether (and adding separate functions to test equality and compare). |
Two equal queues may compare as unequal, since duplicate keys are allowed and the
Eq
implementation takes only care of comparing the keys in order.Example:
This should be documented, as I don't think this can be fixed without making the implementation significantly slower.
The text was updated successfully, but these errors were encountered: