Just to get it down on (digital) paper: I’ve been noodling around with one potential concept for how to make object compilation more straightforward / safer, and potentially alleviate the concern around whether a given resource is used to satisfy “too many” constraints. For now I will give this concept the temporary name of “per-object actions”.
Note that I haven’t thought about performance yet here, and we’ll need to investigate this - I’m just starting by trying to think about the architecture.
Per-object actions
Functionally, objects are supposed to have a “scope” of visibility: an object sees messages sent to it, messages sent from it, its own state (potentially multiple versions), and nothing else. The general idea here is to represent the scope that a single object is supposed to “see” in the course of a transaction, namely (a) the previous state (version), (b) the next state (version), (c) messages sent, and (d) messages received, in the scope of a single action.
Conceptually, at least, it seems to me like we should be able to achieve this roughly by:
- Representing all messages as ephemeral resources, where creating a message-resource represents sending a message and consuming a message-resource represents receiving a message.
- For each object involved in a transaction, putting together an action with resources representing that object’s old (pre-transaction) and new (post-transaction) state versions, (created) message-resources representing any messages sent by that object, and (consumed) message-resources representing any messages received by that object.
- The resource logic associated with an object’s state should then just check that the transition is valid, given the messages and other object state version(s) in the action (there should be at most two state versions). No other resources should be present in the action.
- We can rely on the linearity check to enforce that any ephemeral message-resources created in the transaction (representing a message being sent) are consumed (representing a message being received). Somehow, however, we must check that these messages are sent and received by the right object(s) – so the message-resource must encode the appropriate notion of object-identity and check when being consumed that the object involved in the consumption-action is indeed the object that the sender meant to send the message to.
Voila? Action-scopes correspond to object-scopes, correctness seems straightforward to check, and no miscellaneous “extra resources” need to be allowed or reasoned about. I even think it’s relatively straightforward to represent “stateless” / “virtual” objects (e.g. SimpleKudos) here – we can just represent them with ephemeral resources. I guess we need to allow an unbalanced “top-level incoming message-resource” for the whole transaction, but this seems doable. Probably the primary question to think about here is performance – obviously, representing every message as a resource is gonna get expensive, we might want to batch messages together / performance-optimize specifically ephemeral resources / even batch objects.
This could also be extended fairly straightforwardly to “read-only methods” by relaxing the action-exclusive resource usage check and allowing resources to be used in multiple actions if a flag is marked / potentially allowing resources to be read without being consumed if the methods in question are read-only (see relevant prior discussions here).
Thoughts? @vveiln @graphomath @Lukasz especially. Maybe I’m missing something obvious here and this actually doesn’t work…