A recommended idiom involves defining Actions to implement Singleton Messages. This cleans up source code by removing the need to use the semi-colon, and shifts possible “Unknown Message” errors to data type errors, some of which can be detected at compile time.
BOOL Messages assume an arity of two: a target object (which is always required) and an optional parameter object. The syntax for Messages assumes the parameter exists and provides an alternate form for when it doesn’t.
add:A B nz:X;
add: Message has a target,
A, and a parameter,
B. The singleton
nz: (not zero) Message has only a target. In a singleton Message, the semi-colon replaces the expected parameter. (Of course, spaces are allowed after the colon and before the semi-colon.)
The semi-colon can sometimes occur far enough from its Message to be confusing:
set: H sqrt: add: mul: X X mul: Y Y ;
In the expression above, the semi-colon belongs to the
sqtr: (square root) Message!
A recommended way around this potential confusion is to define generic Actions for common singleton Messages. To make the expression above cleaner, we can define:
@@sqrt >> *float N << *float R = sqrt:N;
Now we can write the original expression like this:
set: H @sqrt add: mul: X X mul: Y Y
One possible advantage (difference, anyway) is that the
@sqrt Action requires an input object that is an Instance of the
*float Model (or an object that can be treated as such — see the recent article about data type casting). In some cases, the compiler may be able to detect when an invalid object is passed to, or received from, an Action. (Because Actions declare their inputs and outputs, and this information is available to the compiler.)
Such Actions also allow programmers to add run-time checks to the data or to add aspects (as in Aspect-Oriented Programming).
Two common singleton behaviors worth wrapping in generic Actions are the “increment” and “decrement” functions used in many loops. Assuming that an
incr: Message exists, one can define:
@@incr >> *int N << (N) . incr:N;
In this case, we want to return the original object, so the Message is sent in the Action’s execution list. If there is no
incr: Message, an
@incr Action is still a great idea:
@@incr >> *int N << (N) . set:N add:N 1
(The same logic applies to the decrement function and any other similar Messages.)
Another obvious candidate is
abs:, the “absolute value” function. If the Message exists, it can be wrapped exactly like the
sqrt: Message above, and if the Message does not exist, it can be implemented:
@@abs #inline >> *float N << *float R = N . @if ltz:N; . . set:R neg:N;
#inline tag allows the compiler to treat the Action as a code template and insert the code rather than creating an Actor-Action link. (However, given the current nature of how Actor objects work with Action objects, the
#inline concept may be slightly moot. It’s also turned out to be tricky to implement, so is a future feature in any event.)
One irony of this is that Model Actions implement Message behaviors, so wrapping a Message in a generic Action means an Action sends a Message that ultimately invokes another Action.
As such, something worth investigating is allowing the compiler to somehow tap into or leverage the Model Action that actually implements the behavior. The trick would involve finding a way to directly bind the Action wrapping the Message to the Model Action implementing that Message.
The bottom line is that this:
*int ix = 10 @while gt:0 ix . // …stuff… . set:ix sub:ix 1
Or even (using singleton Messages) this:
*int ix = 10 @while gtz:ix; . // …stuff… . decr:ix;
Becomes the rather cleaner and clearer:
*int ix = 10 @while @gtz ix . // …stuff… . @decr ix
And this furthers the BOOL goal of defining and using Actions (such as