More about “new”

Picking up the topic of Model implementation two years later, some further thoughts about the new method as well as about the set and get methods.

The summary here is that all three are virtual methods built into the implementation. They have to be to handle the under-the-hood aspects of Instances. They can be “extended” by user code, but not over-ridden.

The abstract idea of any state variable implies at least four necessary methods bound to the data type:

  1. A new method that allocates and initializes the object.
  2. A get method to access the object’s value.
  3. A set method to change the object’s value.
  4. A destroy method to deallocate the object.

In BOOL, because Instances have a code object, a data object, and a value object — only the first of which is reflected by source code — these methods must access system-level constructs. As such, they really cannot be implemented in BOOL code.

There are two basic types of Models: Native Models and User-Defined Models. The former implement new, set, and get, as required for the Model (but may rely on common code). The latter use the common code along with parameters provided by the user-defined Model.

For example, the *int Model implementation looks as if the source code looked like this:

**int
.   *array <*byte>  data,4
.   {{math & logic methods}}

(Native Models allocate space for their data objects as if it was an array of bytes.)

In source code, an *int Instance…

.   *int  i = 0
.   print: lt: 0 i
.   set: i 42
.   print: lt: 0 i

The declaration implicitly invokes the built in new method (which, in this case, also calls the set method).

The set: Message explicitly invokes the set method.

Both lt: Messages implicitly send Q: Messages to the Instance object, which implicitly invokes the get methods.

In general, other methods always use the native set and get methods to interact with the Instance’s value.

Note that, because of the way native Models define their instance objects, the following is legal:

.   *int  i = 0
.   set: i,2 1

An attempt to access a native Instance object as an array of bytes succeeds! (And, in case it isn’t obvious, an indexed object returns part of itself, not a copy of that part. Writing to the object part returned by index does write to the object!)

User-defined Models all use the same common new, set, and get, methods, because they all define their structure as a list of (existing) BOOL Instances. For example:

**point
.   *int  x
.   *int  y
.   {{methods}}

A *point Instance is here defined as a list of two *int Instances. The Model implementation code defers allocation and access related to those Instances to those Instances and just handles any user-defined Model as such a list.


A related note about Models and Instances…

Because a Message to an Instance goes through a fairly involved process before ultimately invoking the Model Action, it would be nice to be able to define a direct binding to that Action.

For example:

.   *int  foo = 42
.   print: gt:foo 0

The set: Message object sends the gt message to the foo Instance object, which in turn invokes the *int Model (passing the gt message and foo Instance object).

The *int Model finds the gt Action, sets up an Actor stub to call it, and (finally) invokes that Action.

It would be nice if something like this:

@@gt-int
>>  *int  self
>>  *any  value
<<  self
<**int@@gt>

Which would create a virtual @@gt-int Action bound directly to the *int gt Action (or throw an exception if there was no such Action found in the Model — failing to match that Action’s signature would also throw an exception).

Then you could write:

.   *int  foo = 42
.   print: @gt-int foo 0

Which creates an Actor object in the source code that directly invokes the Action.

Advertisements
%d bloggers like this: