The following is a review of Goose v0.3. It is not yet a proposal of what to do next, just some thoughts and questions.
I’d like to thank @Lukasz for an excellent summary which made the current state easy to understand.
Major design notes
Per-object action compilation
The basic structure of per-object actions with resources representing object versions, consumed resources representing messages received, and created resources representing messages sent makes sense to me. I think an important avenue for future work will be to figure out how we can optimize this compilation in different ways, e.g. erasing intermediate ephemeral objects and messages.
Multi-methods
I am not convinced that multi-methods are the right abstraction. From an object-theory perspective (e.g. @graphomath’s AVM paper), they seem unusual, in that they involve an execution context which does not “live within” one object, and from an object-practice perspective, I don’t know of any object-oriented languages which implement such a thing. It seems like we could instead achieve the desired semantics (for example, object destruction only being able to happen in conjunction with other object creations) by limiting permissions of which object(s) are allowed to call which method(s). @Lukasz and I quickly discussed the bank example in this context, which I copy here for reference:
Why can’t a “task” just be an (ephemeral) object, with a single method which fetches the objects, generates IDs, and performs the appropriate calls (translated into actions)? I think that right now there are two reasons:
We haven’t integrated transaction functions into the AVM/Goose yet – we’re just statically generating transactions.
There are no clear standards for what APIs would be exposed to generate new identifiers and look up objects from within transaction functions.
Given that context, it’s understandable that we have such a separation, but I think the additional overhead of such a separation is good reason to move towards a more proper design as soon as possible.
Minor design notes
Calls and destructors should be able to return a value.
Regarding “genRand” – strictly speaking, I don’t think we need random numbers (for object IDs), right? We should just need unique numbers (an incrementing counter, for example, would work just fine).
For the generation of a new object identifier (OID), I think we should first figure out how we want to incorporate principals into the picture (each object should also be associated with a principal, very much as per usual in IFC / access control land).
For the creation of new objects, to me the most natural way to do this is having a creating principal, and each principal has the ability to create new objects (that they may choose to own, but principals may transfer ownership from the first moment).
Now, turning to the API, I think, each principal should be able to create a new object of any type. The new object ID would not be exposed to any user or developer (by default); the OID should be some kind of (pseudo-)random value I think.
This seems fine, but keep in mind that objects can also create new objects, so – in general – this would just mean that the object/principal which creates an object owns it by default – does that match your intuition?
Yes, I think the only guarantee we need is some newIdentifier function which is guaranteed not to return the same value twice. In practice this can even be implemented by a counter resource which we just look up each time.
Concerning intuition: kids live with their parents until they move out, which can be earlier or later.
Concerning newIdentifier: roughly, choice of identifiers is up to alpha conversion (cf. renaming of bound variables); concretely, the identifier choice is relative to the creating principal, which may be a combination of principals representing several parties. The cleanest approach (in the mid term) is to create an object on a local controller and then “migrate” it to a replicated controller.
As for the generation of new Object IDs, we ensure uniqueness by making the id equal to the nonce of the ephemeral consumed resource in the action which creates the object. I didn’t write this explicitly in the summary. So we generate random numbers for nonces, some of which also end up being Object IDs.
Why can’t a “task” just be an (ephemeral) object, with a single method which fetches the objects, generates IDs, and performs the appropriate calls (translated into actions)?
A Task is just an intermediate data structure used in the translation to implement it compositionally. Logically, it is a list of Actions and a message parameterized by fetched objects and generated ids.
Note that in the summary Task does not appear among AVM data structures, because it isn’t one. It only appears in the section about AVM → RM translation, only to explain the translation. The user of AVM never interacts with tasks directly - they are essentially a translation implementation detail (but an important one).
To put it differently, Task is at an intermediate level of abstraction between AVM and RM. It can’t be an object, because that’s what object methods compile to as an intermediate step.
Yes, that all makes sense – and Task makes sense in the current compilation, I agree with you there – I was just trying to think about what this would look like when we also want to compile to transaction functions (which could perform lookups dynamically in post-ordering execution), and what we want to do in a transaction function (perform some lookups, generate some identifiers, and return a transaction) is roughly what a Task does right now, as I understand it. If this is not sufficiently clear I’ll try to write it up in more detail later.