Finally, a post not filed under Basics! I’m in a BOOL development mode (they come and go over the years), so more advanced topics are likely. I’m making what feels like excellent progress. Maybe once this phase completes, I can set down to the business of really documenting BOOL here. (Assuming the development doesn’t end in frustration and tears and I put the whole mess on the shelf for another few years.)
I’ve been mucking about with my Python implementation trying to work my way through an implementation aspect I’ve never been able to see clearly. It has to do with how BOOL invokes code sub-units. We’re talking about “calling” what are variously called: sub-routines, routines, functions, procedures, methods or sub-programs. At least the term “call” is fairly global; in fact, the generic term for these things is “callable unit.”
We’re talking about a chunk of code, and that part is easy. At least it’s as easy as chunks of code anywhere in a given system, because at some level, call code is a callable unit. If it wasn’t callable, it would have no use (other than just being bits, data).
The hard part is the two-fold process of — from the outside — calling the code and likewise — from inside — receiving the call. There’s more to the picture than merely passing the thread of control to the sub-unit and then getting it back successfully when the sub-unit is done.
A big issue has to do with recursion. It also occurs in multi-threaded systems. The generic term is “re-entry” or “multiple-entry.” A sub-unit has its own variables, both those passed in by the caller and those it creates doing whatever it does. If it can be re-entered in mid-process, it needs a way to keep straight which variables belong to which call.
A common way uses stack frames, but a key BOOL design goal is finding new and/or weird (preferably both) ways to do normal things. So no stack frames for BOOL. The problem is I can’t settle on an idea I like for how else to do it!
And I still haven’t. But I have clarified an issue that’s been a nag for a while.
There are two types of Actions (sub-routines, methods, etc.) in BOOL. There are Generic Actions and Model Actions. The former are basically the usual sub-routines found any procedural language. You call them, maybe pass them parameters, they return something (or not) when done.
Although, keep in mind that, in BOOL, “pass them parameters” means ‘push object references onto the Parameter Stack,’ and “return something” means ‘pop some off the Parameter Stack.’ BOOL Generic Actions can thus return multiple objects in addition to taking them as parameters (as all sub-routines do).
BOOL Model Actions are what many object-oriented languages call methods (which is just the OO term for sub-routine). These Actions are invoked by the Model (aka class) when an Instance object of that Model receives a Message.
The nag point involves the two completely different mechanisms for invoking the same kind of callable sub-unit. The main problem lying in how BOOL uses a Parameter Stack for passing data. Actions are designed to pop input parameters from the Stack and to Push return values. (The signature of an Action is available to the outside world, so it’s possible to know what an Action requires and what it returns. In fact BOOL uses this to enforce calling correctness. (Thinking along the Ada or Eiffel “contract” lines here.))
The crux of the nut is the Actor object that calls the (Generic) Action. In order to support the primary design goal of allowing Actions to create complex call signatures (specifically to allow an if-elif-else Action or switch-case Action), the Actor has to be directly tied to the Action.
In particular, the Actor can’t push its parameters onto the Stack. (The main reason being the potential for, say, an unknown multiple of ‘elif’ clauses, including none.)
For a while I thought I’d have no choice but to have two distinctly different types of Actions (ugh), or that I’d have to put complex structures on the Stack (also ugh).
But then I realized that the Model can call the Model Action. Rather than try to shoehorn the Generic Action into a message-passing situation that just didn’t fit, the better solution is to call the Model Action exactly the same as a Generic one.
That’s a huge, long-standing block point solved!
Here’s the process of how a Model Action is invoked:
Message object: pushes params, sends mesesage to target. Target object: pushes self, pushes message, sends "action:" message to its model. Model object: pops message, finds action (which provides arity and call info), pops into self, pops into params, builds & calls @action self params. Actor object: calls Action object. Action object: switches to new frame, pops arguments into new frame, executes code, returns (pushes) value(s). Model object: releases params, releases self.