This is part three of a series mourning the death of a computer language I birthed around 1990. Now it’s turning 30, and I’ve decided it’s too old for this sort of thing. I’ve retired and now I’m retiring it (in the “sleeps with fishes” permanent retirement sense). These posts are part of a retirement party. BOOL might not be here to celebrate, but I’ll raise glasses in its honor.
First I introduced BOOL, a deliberate grotesquery, an exercise in “and now for something completely different!” Then I illustrated basic procedural programming in BOOL. This time I’ll get into the object-oriented side.
This aspect of BOOL is one of several that changed repeatedly over the years.
In fact, to be honest, one reason BOOL languished in development hell for three decades, is that I kept changing my mind about things. (It’s really hard to design something when you’re not entirely sure precisely what it is you’re designing.)
Parts of BOOL have been locked in since the beginning, the dotted list idea, for example. (And, in general, the overuse of punctuation symbols. Blame that on years of using Perl.)
But other parts,…
Well, for instance, I went back and forth for years over whether arrays should be a distinct datatype all their own, or an aspect of any datatype. There are, as always, pros and cons either way.
I finally locked in the idea that any datatype can offer an array of that type, which is common in many languages. (It took me even longer to decide on the array and indexing syntax.)
Figuring out how to handle class design (creating data Models) was another one of those things that I went back and forth on a lot.
One question was whether a Model could be extended. That is, could a user add methods (Model Actions) and properties to an already created model? In some languages, that’s trivial, in other languages it’s a bigger deal or not possible.
Often a design can be inherited (or sub-classed) into a derived object the programmer can modify or extend. This invokes the opposite side of the question: should there be Models that cannot be extended even by sub-classing?
There are pros and cons either way, so it comes down dealer’s choice. Sometimes overall design goals speak to which choices are best, but there remain many choices that work either way. It all depends on what one wants.
Thing is, picking one locks in that choice, which allows forward movement, but which can also deeply embed what is later recognized as a less than ideal choice.
It all really made me feel for Buridan’s ass.
That said, I eventually realized that my conception of Models was more complicated than the reality required (a classic case of lusting after unnecessary complexity).
The overall mechanism of Models and their Actions turned out to be very simple. What remains an unresolved issue to this day is the complicated and (worse) dual mechanisms required to invoke generic Actions versus Model Actions.
Exploring that takes us deep into the weedy internals of BOOL, so I’ll leave that for either later or never.
Here’s how one might start defining a simple X-Y point class in BOOL:
**2d-point %final . *int x =0 . *int y =0 . . @@zero: . << ! -- return "self" (!=self) . . set:!/x 0 . . set:!/y 0 . . @@move: . >> *int x . >> *int y . << ! . . set:!/x x . . set:!/y y . . -- ...more methods...
Note that BOOL is fine with a name like “2d-point” — the leading punctuation symbols remove the prohibition of a name starting with a digit (not to mention including hyphens and whatnot). Only spaces and the punctuation symbols BOOL uses (* @ = . , : ; / ! # % & <> “) are prohibited in names.
As always, the asterisk (*) indicates a Model (data) and two of them indicates this is a definition of a (presumably new) Model.
We’ve defined the *2d-point model as having two members, both *int objects, which are named x and y. We’ve provided default values of zero. Note that their order is significant with regard to any initialization lists.
Reference to member objects (or array members) uses slash notation:
*2d-point pt set:pt/x 42 set:pt/y 21
That creates a new instance of a *2d-point Model, named pt, and assigns values to its members. Note that, because we defined a move: message, we could also write:
*2d-point pt move:pt 42,21
Or we could have just provided those values as default values (in which case the order of x and y is significant):
*2d-point pt =42,21
All three code fragments do the same thing.
You might notice what seems a possible contradiction here.
Last time I said messages always have a target object (to receive the message) and a parameter object — which can be the semi-colon (;) to signify null when necessary.
But the above move: message seems to take two parameters.
With Model Actions, BOOL interprets any and all input parameters as a single list — the parameter object of the Message object. (Here, again, the order of input parameters is significant.)
The two numbers for the move: message parameter are joined with a comma, which creates an implicit list object.
A leading equals sign would make it explicit (as in the default value), but it’s not necessary here. It’s not actually necessary for initialization lists with multiple items and commas, but single values need the equals sign to create a list object.
(The BOOL style guide suggests using the equals sign in all initialization lists for consistency.)
In contrast, note how the zero: message takes no parameters. Using it requires the semi-colon null object:
That sends the zero: message to the pt object, which passes it to its Model, *2d-point. The Model looks for a match to the message among its Actions. In this case, it finds and invokes the @zero Action.
If the Model can’t find a match, it defers to any parent Models. If none of them find a match, BOOL generates a run-time error:
No Action for Message.
Incidentally, this message sending business, along with the use of the colon to indicate a message, is a borrowing from Smalltalk, a language I dated briefly. We never really built anything together, but she taught me some cool under-the-hood tricks.
(And yes, that is a bit like under-the-covers tricks. Fun, fascinating, exciting, sometimes slightly dangerous.)
((While we’re on the subject, advice to young lovers: Unless you really enjoy sleeping on ant-infested sticky sheets, skip the honey, peanut butter, or cheeze whiz. Even whipped cream turns out to be more bother than it’s worth; you have no idea how quickly it melts on a hot human body.))
(((If you’re determined, just stay in the kitchen where you probably have linoleum floors. And in the kitchen, you can pretend you’re in a porn flick. One of you is delivering pizza,… cue the music.)))
((((One more piece of advice for beginners: A nice big clean towel by the bed. Always. You’ll thank me later. Even Douglas Adams knew the value of a good towel.))))
Where was I? Oh, yeah, Smalltalk, the second sexiest computer programming language I ever did know. What was the sexiest? I’ll give you a hint: You may have noticed I’m still addicted to Lots of Intriguing Sexy Parentheses.
The same message mechanism exists in Smalltalk. Send any message to any object. If the object doesn’t “understand” the message, it throws an error. It’s a model I’ve always liked. (The ultimate in duck typing.)
(Seriously, the whole parentheses thing? It’s the way my mind actually works. Most of my thoughts come with a dozen or so tangential riders.)
Speaking of under-the-hood, just to say a few words about BOOL internals.
What I stole from Lisp is a fundamental architecture where every object starts with a pointer (reference) to its “handler” — in BOOL terms, the handler of any object is the Model that object is an instance of.
The general idea is that the run-time engine, to execute an object, invokes the handler, who does the actual work. This way the engine doesn’t have to know anything about the objects other than the first thing they have is a reference to a handler.
It makes for a simple run-time engine, a key design goal, which is something I borrowed from Forth, one of the world’s odder languages (it was mainly used by astronomers to program their telescopes).
It’s a handy architecture, one I’ve used in other developments. Since the run-time just has to invoke the object’s handler, it’s easy to add new capabilities without modifying the engine.
I’m halfway through this eulogy now. The last two posts get more into what went wrong, why the design goals ended up conflicting so much.
Since there’s no baseball so far this year (wah!) you’ve been spared those, but now you’re stuck attending a funeral! (Which, ironically, contains the word “fun.”)
Ah, well. Into every life…
Stay boolean, my friends!