screeps-game-api
screeps-game-api copied to clipboard
Thought: implement PartialOrd/Ord for things?
Right now, almost nothing in the library has a "natural" ordering, and thus nothing implements Ord
.
While this is reasonable, it means nothing can be stored as a key in a BTreeSet
or BTreeMap
, which might be desireable.
@ASalvail What do you think of having Position
, RoomName
, Resource
, and all other constants which implement Hash
also implement PartialOrd
and Ord
, using an arbitrary but consistent-within-releases ordering?
I'd say that's why Hash
versions were invented: to give an unrelated mapping to integers that can be used for sorting? One could make an argument for RoomName
or Position
, but not Resource
.
Any use cases for having an ordering on them that is all but arbitrary? Maybe you have something @PatrickVdWillik ?
Thanks for tagging me, I've been toying around with this myself but the biggest question I have is: Ordering in relation to what? Because while I got the stuff to work now, using a BTreeMap for my specific purpose, the calculation for the key value is offloaded into a separate function because the ordering has to make sense(in my case, a specific point on the map and the ordering is travel distance).
If you're going to order based on an arbitrary property, how useful will the ordering be? There's a pretty select set of use cases for rooms ordered by travel distance from origin (0, 0) (for instance). Wouldn't a set of structs that allow custom ordering via a closure (or similar mechanism) perhaps be more useful?
In that case, I'd vote to make sure Hash
is derived, but let the end user figure out their own implementation for PartialOrd
/Ord
.
I went through the structs that we define. The only one I can see where it would make sense to add Ord
is with screeps::constants::Density
.
I'd say that's why Hash versions were invented: to give an unrelated mapping to integers that can be used for sorting? One could make an argument for RoomName or Position, but not Resource.
Hash
is definitely good for this. My only problem with it is how it doesn't really integrate well with things expecting something naturally ordered, like [T]::binary_search
or BTreeMap. It's possible to use together, but it requires coming up with a hashing algorithm since Hash
is agnostic to how the combination is done, and then writing that glue code to turn the Hash
impl into an Ord
impl.
Any use cases for having an ordering on them that is all but arbitrary?
My main motivation is to be able to "just use" these things in BTreeMap
and binary search, and other ordered structures, without having to care about how the ordering happens.
Maybe that's not a very good reason to do this though? Ord
does kind of imply that one proper ordering exists, and it wouldn't be the worst thing to just use an OrdAsHash
wrapper for stuff...
What if we added an ordering function (that returns a std::cmp::Ordering
) to quickly implement PartialOrd
with a wrapper type and that is efficient (like using the packedPos
for Position
)?
I'm not comfortable breaking the PartialOrd
contract by implementing it directly.
I think an ordering function could be good, but it could still be pretty annoying to used compared to just being able to #[derive(PartialOrd, Ord)]
. I mean a wrapper in my own code could just call .packed()
on Position
or use x as u32
for Resource/etc, though.
Would we really be breaking the PartialOrd
contract if we implemented it? As long as the ordering is self-consistent, I don't think anything requires it to make sense. If we did something like literally sorting positions by their packed values, it would still satisfy antisymmetry and transitivity as described in PartialOrd
's docs...
It does feel a bit against the spirit of PartialOrd, but I don't think that these implementations would be explicitly breaking it?
With that said, I'm OK with not adding these. Does feel like lying to say that Position
and Resource
can be ordered...
I've posted about this issue, but not really pitched it. I think I can do better, after thinking about it. Here's an actual use case?
Right now, all constants, and things #[derive(Hash, PartialEq, Eq)]
containing constants can be used as HashMap
keys. However, they cannot be used as BTreeMap
.
I'd really like to enable using constants in BTreeMap
. For example, struct X(u32, ResourceType)
could be a meaningful key in a BTreeMap
, and could be sorted up to the resource type. The current best way I know to implement this is a BTreeMap<u32, HashMap<ResourceType, V>>
- this works, but requires extra code, and extra indirection in memory.
The second best way is to manually implement Ord
, using a cast.
impl Ord for X {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(other.0).then_with(|| (self.1 as u32).cmp(other.1 as u32))
}
}
Not too painful, but has boilerplate. I believe using a custom method which returns Ordering
would result in very similar code.
If we #[derive(PartialOrd, Ord)]
for constants, then the above behavior could be achieved with #[derive(ParitalOrd, Ord)] struct X(u32, ResourceType);
.
As I understand it, the trait contract of Ord
requires is that types "form a total order". This is trivially true for enums which form finite sets with ordering based on declaration order. I believe this is also true for the integers, and Position
, RoomName
and ObjectId
would defer their ordering to the integers contained within.
From a perspective ignoring sensibility of implemented traits, I would see this as a positive change.
I don't want to ignore the sensibility of ordering, though. If we just stick #[derive(PartialOrd, Ord)]
on the enums, the ordering would be declaration order. That wouldn't have any reason for existing.
I'm not comfortable breaking the
PartialOrd
contract by implementing it directly.
What would you think if instead, we introduce arbitrary but rational orderings?
We could order:
-
enums representing strings in screeps by the lexicographical ordering of the strings.
-
enums representing integers in screeps by the ordering present in the integers.
-
Position
first byworld_x
, and then byworld_y
.0,a < 1,b
for anya
,b
, andc,0 < c,1
for anyc
. -
RoomName
first byworld_x
and then byworld_y
.The total ordering would be
127N127W
,N127W126
,N127W125
, ...,N127W0
,N127E0
, ...,N127E127
,N126W127
, ...,S127E126
,S127E127
. -
ObjectId
by inner object id.For servers backed by MongoDB, the first 4 bytes in an ID represent seconds since the Unix epoch. Thus ordering would be roughly sorted by object creation time.
These orderings are not particularly useful. However, they are logical, and reproducible in other programming languages. The string constants, the integer constants and ObjectId
ordering will directly agree with JavaScript's natural ordering. Position and room name ordering should be reasonably reproducible with effort
Anyways, that's my pitch. It should be at least somewhat reasonable, and if not, at least the idea is now well documented.
@ASalvail I'm hoping this address some of the reasonability of implementing PartialOrd / Ord on things which don't have a single expected ordering?
Clearly you want this implemented more than I want it not implemented 😛
I'm good with the orderings you suggested, with one exception: for Position
and RoomName
, sort first by y
, then by x
(which I think is that you meant, as it follows reading order).
Plus, make sure to document the ordering!
Do you need help for any of it?
Clearly you want this implemented more than I want it not implemented stuck_out_tongue
I mean that's fair, but I'd hope to convince you too :p.
I'm glad to have gone through and thought of logical ways of doing this, in any case. Much better than my original idea of just slapping #[derive(PartialOrd, Ord)]
on everything.
I'm good with the orderings you suggested, with one exception: for
Position
andRoomName
, sort first byy
, then byx
(which I think is that you meant, as it follows reading order).
This... is what I meant. Thanks!
Plus, make sure to document the ordering!
Do you need help for any of it?
Will do! I think I should be good to just implementing this, though if you'd like to I can hand off some to you?
As you say though, I'm kind of the one driving this - so it seems reasonable for me to do the implementation work.
You go ahead then. And don't worry, I am mostly convinced by the implementations you suggested. It's better than object ids in python in any case!
I suppose it's my background in math that pushed me to push back on any kind of ordering on R^2.
On Thu, Aug 22, 2019, 22:35 David Ross [email protected] wrote:
Clearly you want this implemented more than I want it not implemented stuck_out_tongue
I mean that's fair, but I'd hope to convince you too :p.
I'm glad to have gone through and thought of logical ways of doing this, in any case. Much better than my original idea of just slapping #[derive(PartialOrd, Ord)] on everything.
I'm good with the orderings you suggested, with one exception: for Position and RoomName, sort first by y, then by x (which I think is that you meant, as it follows reading order).
This... is what I meant. Thanks!
Plus, make sure to document the ordering!
Do you need help for any of it?
Will do! I think I should be good to just implementing this, though if you'd like to I can hand off some to you?
As you say though, I'm kind of the one driving this - so it seems reasonable for me to do the implementation work.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rustyscreeps/screeps-game-api/issues/226?email_source=notifications&email_token=ABBF5FDA7GLOHC5LWMP6XYDQF5EHNA5CNFSM4IMRLLGKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD466EPI#issuecomment-524149309, or mute the thread https://github.com/notifications/unsubscribe-auth/ABBF5FD3F6BYZT5XQZQYZTLQF5EHNANCNFSM4IMRLLGA .