Picking up where I left off, after some consideration, I’ve decided to require Instance definitions on both inputs and outputs. This makes them symmetrical, for one thing, and that seems more sensible. It also helps with the issue of migrating a new Object Instance up a chain of Actor calls. As icing on the oatmeal cookie, it also insures a type signature on the return object(s).
I’m still wanting to allow multiple return objects for Generic Actions (just for the sheer weirdness of it), but if it gets too nightmarish, it can be tabled for deeper consideration.
So that means you can no longer do this:
@@foo >> *int a >> *int b << @bar a b
But you can do the same thing like this:
@@foo >> *int a >> *int b << *int c = @bar a b
Equally forbidden is this:
@@foo >> *int a >> *int b << bar: a b;
Which is really the same thing as the first forbidden example. Invoking the Generic Action,
@bar, with parameters
b has the same effect as invoking an essentially identical Model Action,
bar:, bound to the
*int Model (in fact it’s simpler because a Message references that essentially identical Model Action through the Object Instance and its Model).
A big difference, though, is that the return type is impossible to determine given the
bar: Message! The
@bar Action presumably has some return type information. (As it turns out, requiring an Instance as a return object insures Actions have exactly that information.)
Speaking of Action signatures, the
@foo Action’s signature is:
Which is to say that
@foo is a Generic Action that takes two
*int objects and returns one
*int object. If
@foo could take any two objects and might return any kind of object, we write the header like this:
@@foo >> *() a >> *() b << *() c
*() Model type indicates a generic Instance of an object of unknown (dynamic) type. At run time, when the type is known, the Instance is bound to the given Model and remains so bound. As an Action parameter, the binding occurs each time the Action is invoked and remains for the duration of the Action.
It’s also possible to specify that a parameter’s type is unknown until run-time, but that it must match some other object’s type. (That other object’s type is obviously also dynamic.) For example, to indicate dynamic — but matching — types for
@foo, we write the header like this:
@@foo >> *() a >> *(a) b << *(a) c
Regardless of whether the types are static (known at compile time) or forced to be matching dynamically, if a passed object does not match a required type at run-time, BOOL throws an exception. (The exception is allowed to be different for static versus dynamic mismatch, but is not required to be.)
Note that dynamic type matching is not indicated in the signature. The two previous examples both have the same signature:
The dynamic type-matching is a layer of run-time protection, not terribly dissimilar to type-checking code a programmer might write to insure matching parameters.
There is a very important distinction to be made between Action inputs and outputs. Here is our basic example:
@@foo >> *int a >> *int b << *int c
The input parameters,
b, are Reference Instances, and the output parameter,
c, is a standard Object Instance. Declaring Action input objects is the only place BOOL uses References.
The significant difference between a Reference and an Instance is that the latter binds to a Data Space containing the actual data, but the former binds to a reference to that data. (Obviously the inputs to an Action have to bind to different data every time the Action is invoked.)
Object Instances in an Action bind to data in the local Address & Data Space, which is part of the execution process. Actions have a new A&D every time invoked, which provides for “local” or “auto” variables in Actions.
The return object(s) of an Action must be either an explicitly declared Instance (as discussed above), or may be a reference to statically known objects (but not necessarily types). This means Actions can also return:
- One of the input parameters
- A constant or global object
- The “self” or “this” object,
The last one applies only to Model Actions, of course, since only those Actions (which play the role of methods) have the self object. (As an aid to clarity, compilers should recognize the keywords
this as aliases for the
! symbol. Just a little syntax sugar!)
The new rule about return parameters means the last example shown in the previous article is no longer legal. This rule eliminates most of the issues regarding getting returned objects into the calling Data Space.
A final issue has to do with a situation like this:
@@foo >> *list list << list
This Action takes a
*list object and returns the same object upon exit. A possible issue exists if this Action adds new objects to the passed list and those new objects exist in the local Data Space.
After the Action returns to the caller, the local Data Space no longer exists!
A possible way around this is to require additions to live in the same Data Space as the container does. That’s the direction I intend to try. The hope is that issues with regard to parameters and data are now resolved allowing me to move forward on an implementation!
The next part of this article series will consider multi-clause Actions, such as