-
Notifications
You must be signed in to change notification settings - Fork 53
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
Allow infinite count of items #656
base: main
Are you sure you want to change the base?
Conversation
I don't understand why |
Random thought: what if the Swarm |
Let me see if I can write down a coherent explanation of what's going on with the hashing. The basic issue is with hashing inventories. We want our hashes to satisfy two main criteria:
Note that inventories are considered unequal if there is at least one entity that the two inventories have different numbers of. However, in this case the "number of" an entity can include any nonnegative value but ALSO a special "Nothing" value meaning "I have never heard of this entity". We are now proposing to add yet another special value, positive infinity. Criterion (2) above is what makes things interesting. Without that criterion everything becomes easy, in fact, we could auto-generate the hashing code for an inventory. But the auto-generated code iterates through the whole inventory and combines hashes for entities in a linearly chained way, so inserting or removing an entity means iterating through the entire inventory to recompute the hash from scratch. #294 fixed this by switching to a homomorphic hashing scheme for inventories. Instead of combining hashes by concatenating them and running the hash function again (say), we simply add them. This is less cryptographically secure but we don't care about that, and it gives us criterion (2): given the existing hash for an inventory, if we insert an entity, just add the entity's hash to the inventory hash. Also, if we add 100 copies of a given entity, we can just multiply the hash by 100 and then add that, rather than adding it 100 times. Currently, if we have However, positive infinity throws a wrench into things. I think we can make it work but we need to check more cases. As a concrete proposal, let
So far, so good. But one problem is that this is not homomorphic with respect to adding and removing. Suppose we have infinity copies of an entity, and we insert one more. Now we should still have infinity, but that means we can't just add a copy of the hash every time we insert. We have to first check to see how many copies we have before the insert.
Taking a union of inventories is also more complex. Honestly it seems like when unioning two inventories we might as well go ahead and recompute the hash of the result from scratch, since it is not worth the trouble of trying to update incrementally. |
I am starting to like that idea. It would make it easier to use code written with the expectation that |
Well, that is annoying. Maybe we can use |
Yeah, I think something like that could work. |
Rebased and changed numbers to use negative and positive infinity. 🙂 Btw. this works now in creative mode: l <- count "Linux"; return $ l == 1/0 |
This approach is buggy in two ways: - it can crash because Natural is too strict - it breaks the fragile hashing laws for inventory A better approach would be to either do without a Num instance and handle every usage carefully or add negative infinity too and use integers. In that case we might as well add them to the number values in game, though that increases the risk of the infinities being added together. But that can be handled with a safeAdd function.
Rebased again to see if the VS Code will be fixed 🙂 |
E.PosInfinity -> replicate 42 | ||
E.NegInfinity -> replicate (-42) |
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.
Ooops, this is a problem. How should we salvage infinite inventory? 🤔
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.
On IRC we agreed that it should take 1 tick. The idea is that an infinite item is like an item generator so we move it like it had count 1.
There is still an issue with how to do that. I think some "game state update" on the stack of the reprogrammed salvaged robot could work. An internal giveInfinite
function also makes sense.
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.
The only code I haven't yet gone over carefully is the inventory manipulation code in Game.Entity
.
@@ -263,7 +263,7 @@ data CESK | |||
Up Exn Store Cont | |||
| -- | The machine is waiting for the game to reach a certain time | |||
-- to resume its execution. | |||
Waiting Integer CESK | |||
Waiting Number CESK |
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.
Why should this be a Number
? It's not useful for this to be infinity, is it? Number
is used to represent counts of things, but I don't see why it should also be used to represent times.
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.
Oh, wait, I think I get it --- Number
is the Haskell type that is used internally to represent Swarm values of type int
. So since wait : int -> cmd unit
, therefore Waiting
has to take a Number
because wait infinity
is now valid Swarm code. Did I understand it right?
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, it might be less confusing for us to rename int
to number
.
Besides, it will eventually be useful to wait indefinitely and get woken up by some other robot.
@@ -450,9 +452,7 @@ entityInventory = hashedLens _entityInventory (\e x -> e {_entityInventory = x}) | |||
-- Inventory | |||
------------------------------------------------------------ | |||
|
|||
-- | A convenient synonym to remind us when an 'Int' is supposed to | |||
-- represent /how many/ of something we have. | |||
type Count = Int |
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.
We could still keep this comment but just change Int
to Number
.
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.
Ups, I should have kept that. 👍
hashCount :: Num p => Number -> p | ||
hashCount c = case c of | ||
PosInfinity -> 1 | ||
Integer a | a >= 0 -> 1 + fromIntegral a |
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.
Doesn't this mean PosInfinity
and Integer 0
both turn into 1
? Maybe I'm not understanding what this function is for.
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.
Ah, I think I originally had 2 +
and then mistakenly changed that. That will be a pain to adjust again. 🙁
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.
To be clear, I don't yet fully understand how this is used so it might be correct.
fromElems = foldl' (flip (uncurry insertCount)) empty | ||
fromElems = insertInventoryList empty | ||
|
||
insertInventoryList :: Inventory -> [(Count, Entity)] -> Inventory |
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.
Let's add a Haddock comment.
src/Swarm/Game/Step.hs
Outdated
In (TRequireDevice {}) e s k -> return $ In (TConst Noop) e s k | ||
In (TRequire {}) e s k -> return $ In (TConst Noop) e s k | ||
In TRequireDevice {} e s k -> return $ In (TConst Noop) e s k | ||
In TRequire {} e s k -> return $ In (TConst Noop) e s k |
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.
Did fourmolu insist on getting rid of these parens? I understand intellectually why they are not needed, but I kinda hate it. In TRequire {} e s k
really looks like In
is taking 5 parameters.
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.
My HLint does not like it and I got tired of it mentioning it. I will change it to (TRequire _num _ent)
.
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.
We can also turn off specific hlint hints in .hlint.yaml
.
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.
In general, I think it is useful to remove extra parenthesis, but this is an edge case.
Presumably, it is different between some HLint versions because CI did not warn about it.
@@ -1627,13 +1641,13 @@ execConst c vs s k = do | |||
missingParentInv = neededParentInv `E.difference` parentInventory | |||
missingMap = | |||
M.fromList | |||
. filter ((> 0) . snd) | |||
. filter (\(n, x) -> x > 0 && (x /= PosInfinity || countByName n parentInventory /= PosInfinity)) |
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.
Can you walk me through the logic here? I'm not sure I follow.
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.
To be safe removing an infinite count of items from an infinite count of items is still an infinite count of items.
But here we were being smart by removing the robot inventory from required items.
So the infinity stays and is not 0.
It is a hack to keep the changes minimal, but we should eventually clean up the code by changing the data structures.
I will write my thoughts on that in a separate comment.
src/Swarm/Game/Step.hs
Outdated
safeExp :: Has (Throw Exn) sig m => Number -> Number -> m Number | ||
safeExp = \case | ||
PosInfinity -> \b -> return $ PosInfinity * signum b | ||
NegInfinity -> \b -> return $ NegInfinity * signum b |
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.
infinity ^ 0
ought to be 1, not 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.
Well spotted. 👍
. map (swap . second (^. entityName)) | ||
. E.elems | ||
$ missingParentInv | ||
|
||
-- If we're missing anything, throw an error | ||
E.isEmpty missingParentInv | ||
null missingMap |
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.
Why did this change from checking missingParentInv
to checking missingMap
? Was this a bug before?
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 am not sure. But if I hack in the infinity check above, I also need to change this.
The inventory version is likely smart and will ignore 0. See my future comment about the overall data structure.
My comments on the inventory representationdata Inventory = Inventory
{ counts :: IntMap (Count, Entity)
, -- ... While updating the code to work with infinities, I came to the conclusion that this is a problem. In the infamous words of every programmer: "It worked." But there are issues with this representation:
Doubtful PROs of current representationKeeping this machinery working is beyond my mental capabilities and I am not sure what are its benefits. Distributed entity knowledgeFrom the look of the code, I guess that it tries to solve the future issue of robots having entities that are not known globally. Global knowledge of all entities is easier to reason about. In particular, it would allow us to detect hash collisions that might go unnoticed with distributed knowledge of entities between robots. PerformanceI hope that this was never a serious factor in the design. We do not measure the performance, so this could easily be slower and less memory efficient than using different representations designed to be easier to understand. It fit the code somewhereAt some time there was probably a place in the code where the current representation made it easier to write. That reason sounds good to me. Is it true still? The hacks that were required for KISS representationI did not want to change the representation here originally, but updating the hashing once more sounds worse. I propose a new representation that my simple mind could handle:
Hashing will stay homomorphic, but it will separately hash the infinities, the known items and the item counts. BONUS: Containers should not have the ability to know items. That does not make sense and this will make it impossible. BONUS2: It should be simple to write hashing function for a list of entity counts, so we can construct inventory using standard Hash functionI also have a small problem with Ideally, I would like the behaviour to be the same and use
|
@xsebek thanks for writing that up! I love it, and I think that will make a lot of code simpler and solve many of our issues with this PR. |
@byorgey, I would like to revive this next, but I might need help. 😄 As far as I know, the points in my final summary are still valid. I will see if they can be split into smaller issues. |
Yes, please do! 🎉 According to my current thinking this is a prerequisite for #231 . I will try to read through this again soon and see how I might be able to help. |
infinity
constant