How To
The Mops Programming Language—Part 2
Part 1 of this article, which appeared in last month’s issue, described the Forth aspect of Mops, including stack usage, postfix notation (RPN), and the definition of new Mops words. Unless you are already familiar with a Forth language, it is likely you will benefit from reading Part 1 first.
Mops’ Object Orientation
Now we come to the unique object-oriented nature of Mops, a feature not found in many other freeware languages to my knowledge. According to Mike Hore, sole proprietor of Mops, Object-Oriented Programming (OOP) “is the language’s real power,” and also, I would add, its main appeal. Since object orientation has become so popular nowadays, whether in full or partial form, there is no need to sell its benefits and virtues in this article.
The following three subsections present a somewhat abstract description of the ideas underlying OOP. If you are already familiar with OOP concepts you can skip down to the section entitled “Example of a Class Declaration.” The concept description follows the view of Adele Goldberg, sometime member of Xerox PARC and developer of Smalltalk, who, if anyone, can be called OOP’s inventor. In any case she was certainly one of the PARC staff members who “dealt lightning with both hands,” in the memorable words of Alan Kay.
Objective
The primary objective of OOP is better management of program complexity. An OOP implementation contributes to the initial reliability of a program or system but also, of equal importance, enhances the maintainability of the code.
Model
The model for OOP is that of independent communicating objects. According to this model, an object sends a message to another object requesting an operation which the receiving object knows how to handle. The receiving object alone decides how this operation is carried out; the sender expects only the result to be satisfactory. Thus, “computing is viewed as an intrinsic capability of objects that can be uniformly invoked by sending messages.” (A. Goldberg.)
The above quotation implies a programming paradigm of a higher level than those provided by Forth, Modula, Algol, and Pascal—one that is able to accommodate the lesser paradigms without distortion, as witnessed by the seamless combination of Forth and OOP in Mops.
Basic Concepts
The object oriented approach requires only five well-defined concepts: Object, Message, Class, Method, and Instance.
Let’s start out with a practical distinction: A class can be thought of as a source-program entity by which an object, a runtime entity, is created. A class definition describes a class of objects. One or many identical objects can be created from one class definition, although their data values will normally differ during execution. Static objects, compiled into the dictionary, must be uniquely named. Individual objects are said to be instances of their class.
An object consists of some amount of reserved memory used for data and a set of operations, or methods. The nature of the data and operations depends on what the object represents, which may range from an elementary data object to an on-screen window, up to a complex model of some entity in the problem space. (The Mops demo program, for example, produces fascinating patterns that model a set of mathematical expressions.) The object’s data is logically packaged together with its methods, i.e. with the code that implements the necessary operations on the data. This is known as logical data-code encapsulation.
Another object can request that a particular datum be stored, retrieved, modified, etc., by sending a message to the object containing the relevant data structure, which in this context is called the receiver. The receiver recognizes only those messages that correspond to its set of methods. The crucial property of an object is that its memory can only be accessed by its own methods. The crucial property of messages is that they are the only way to invoke an object’s methods.
The combination of these two properties ensures that the implementation of one object cannot depend on any feature of the implementation of any other object. This is not only a fundamental gain but it is also of practical benefit during program development and especially in program maintenance. While developing even a small program, the freedom to try different implementations of one class of object without affecting the others is a joy.
Note that a Mops object is in general a composite structure, in that it contains one or many lower-level objects that make up its data structure and, implicitly, add to its methods. Thus according to OOP rules, a composite object can and always does send “internal” messages to its contained objects. This internal messaging may occur to a number of levels. Thus in respect to methods, a high-level object may be thought of as a cascade of operations flowing from many objects in addition to the object itself. This is a quite significant point, however informal its description.
The sending of internal messages can be thought of as a chain that ultimately reaches back to methods formulated in primitive operations that are close to machine code. Internal messaging combined with inheritance provides a very high degree of information hiding. The programmer never sees the definitions for most of the methods invoked by his program; he need only understand and know the names of the methods he calls upon.
Finally, in Mops a message may be sent to an object from code that itself is not part of an object, that is, from an ordinary Mops word.
Example of a Class Declaration
Suppose that we are writing a Tic Tac Toe game, and we decide we need a sort of “gofer” object that keeps a running record of what is happening on the screen. The object needs a data record that models the playing field. It must be able to mark each cell with a “who played it” code (-1, 0, or +1) when requested. Beyond that, the object needs to supply all the information required by the high-level game playing, or decision, code. Examples of such information include, “Is there an unplayed space in a given path (row, column, or diagonal) on the board?” and “What is the arithmetic state (-1, 0, or +1) of a given path on the board?”
The following is an abbreviated version of a class definition used in an actual game implementation.
:class SCOREKEEPER super{ Object } \ Defines the class name \ and superclass 9 wArray BOARD_ARR \ Declares a 9-cell word-array Ivar :m CLEAR_BOARD: ( -- ) clearX: board_Arr \ For use by the post-game cleanup word ;m (* ------------- Elementary methods of the class -------------- *) :m FINDZCELL: ( -- idx | t ) \ Locate first empty (zero) cell. 9 0 DO i at: board_Arr \ Loop thru the array and, if a 0= IF i UNLOOP EXIT \ zero cell is found, push index or THEN LOOP true \ return a true flag for none found. ;m :m CHKZCELL: ( -- b ) \ Check cell location (celLoc) for celLoc at: board_Arr \ zero content. Return true if zero; 0= IF true ELSE false THEN \ false otherwise. ;m :m PUTOMK: ( -- ) \ Update state value of board array. Omark celLoc to: board_Arr \ Store O-mark value in cell celLoc. ;m :m PUTXMK: ( -- ) \ Update state value of board array. Xmark celLoc to: board_Arr \ Store X-mark value in cell celLoc. ;m :m @CELLVAL: ( idx -- n ) \ Fetch value at passed-in cell index at: board_Arr \ (idx) and return value on the stack. ;m (* A number of lengthy methods and words have been omitted. *) ;class ( scoreKeeper ) scoreKeeper BOARDRECORD \ Instantiate scoreKeeper-class object |
The second line is very significant. It declares a data object, a data structure of class wArray, of size 9. We need not define that class since, like so many others, the definition already exists and only the object declaration is required. (As you will see, methods specific to that object come along “for free” also.) So our instance variable, or ivar, is a 9-cell indexible word array.
The first method definition defines itself in terms of “clearX: board_Arr” which is actually a message to the internal data object board_Arr for which the method clearX: is already defined. (The “X” in “clearX:” has nothing to do with the X player in Tic Tac Toe; it’s just part of the built-in Mops method name.)
Note that method names must end in a colon (:), the only, if not one of the very few arbitrary lexical rules in Mops.
The next method definition, FINDZCELL:, shows a combination of “code” and an internal message to board_Arr. Objects of its class know how to execute an “at:” method, which takes an index value, supplied by the DO loop as input.
The PUTOMK: method, used when the computer makes a move, is a bit interesting in that it takes nothing off the stack but pushes the O-mark code value and the (global) cell location, celLoc, on the stack to accompany the to: message. The to: method in every wArray-class object’s repertoire requires these two values; what to store and where to store it. PUTXMK: is called when the player clicks on a square. (A different object, which owns the playing field, writes the corresponding X and O marks on the screen.)
The “;class” word terminates the definition (just as “;” terminates a word definition, and “;m” terminates a method definition). Thus the final line in the example is not part of the definition at all but is a sample declaration of an object instance of the scorekeeper class with the name BoardRecord. An object so created, i.e. by declaration, is static and is compiled into the Mops dictionary of the program it is part of. (Most, and often all, of the objects in a Mops program are static. Dynamic objects require a little extra work.)
Mops Pre-Defined Classes
Mops provides a very large number of pre-defined classes, defining objects ranging from a simple byte/word data object up to a Macintosh window, menu bar, or dialog. The latter kinds of object are of prime interest to the programmer of course since most programs need one or more windows, menus and dialogs; and such things are “horrendous” to program from scratch, so to speak. To create a window, say, through direct use of the Mac Toolbox interface is a torturous and error-prone business indeed. Worse, it also requires considerable internal Mac knowledge which doesn’t appeal to most people if they can avoid it.
Mops’ pre-defined classes for graphical user interface (GUI) objects hide the grimy, low-level coding required, largely by virtue of the OOP inheritance mechanism. The commonly used WINDOW+ class, for example, could be said to reach back to many other, simpler class definitions for parts of the complex data structures and the methods it needs. That is to say, an instance of the WINDOW+ class is a highly composite object. The programmer can however write a fairly simple WINDOW+ subclass definition (which inherits all of the WINDOW+ data and methods) in order to override, or modify, one or more features of its superclass.
It is often possible, however, to simply declare an object to be of the WINDOW+ class, for instance, and then send a few messages to it, for example one that causes it to become “alive” in the Toolbox. The class definitions for pre-defined objects need not appear in the source program since the definitions in question are normally pre-loaded transparently before the source program is read in. (Some are part of the core group in the dictionary and are always there.) The programmer must, however, be very familiar with the pre-defined class definitions for the objects he uses in order to understand the functionality provided and to know how to send proper messages.
Standard Macintosh window behavior, such as dragging, growing, and updating, is handled automatically by Mops’ window classes, freeing the programmer to focus on application-level problems rather than constantly rewriting system-level code. To an extent the same can be said of all the Mops Toolbox classes.
Mops also supports a View object that is widely used in conjunction with WINDOW+ class objects. A view basically defines a rectangular area within a window in which drawing can take place. A view can have child views and has other interesting properties. The View construct is supported also by most Macintosh APIs and frameworks, such as MacApp, TCL, PowerPlant, and Cocoa.
So, how are objects created in general? As we’ve seen, every object is an instance of a class of objects defined in the source program by a class definition. When it is appropriate that an object be statically allocated it can be created by a simple object declaration that we’ve seen above (BOARDRECORD) the declaration is said to instantiate an object of its class. The benefit of a static object is that everything about it is simple and trouble-free (relative to dynamic objects). Also, message-to-object binding is always early (fast). The potential downside is that it is compiled into the dictionary (“locked in”) and continues to occupy program space even though it may have outstayed its welcome, a possible concern for big programs.
The alternative is a dynamic object, which is created at runtime on the program’s heap. The picture here is a little mixed. The recent version of PowerMops (V. 4.0) provides very simple mechanisms called References to create and manage dynamic objects, with most interactions handled transparently. In Mops (68K) however the program must obtain a object handle from the Toolbox for the object-to-be (an objHandle declaration) and then issue an object creation statement (a newObj: message to the handle object). Objects so created are referenced by their handle name or index. In the latter, “traditional” case a good bit of care is needed in managing the relocatable blocks of heap storage normally allocated for dynamic objects.
Examples of Mops Pre-Defined Classes
The following is an example of an elementary data class definition for a 32-bit integer variable with class name VAR. It is not the most “primitive” in its lineage, however, as we shall see. Like all of the elementary data definitions, its methods are not defined in normal, high-level Mops words but in low-level words designed for efficiency. The reason for this is that the methods for elementary data objects are by far the most frequently executed methods in a program.
:class VAR super{ longword } :m +: inline{ obj +!} ^base +! ;m :m -: inline{ obj -!} ^base -! ;m ;class |
Note that there is no data-object declaration for the object’s ivar. It is not needed because the superclass Longword supplies it. The +: and -: methods, for incrementing and decrementing respectively, seem an insufficient repertoire of operations on a variable. Again, like the Lone Ranger, the superclass Longword comes to the rescue. Longword has already defined Get:, Put:, and Clear: among a few others. So VAR implicitly has those methods (inheritance). Longword is the generic superclass for many other elementary data classes and thus defines all of the methods common to those subclasses. The VAR class definition is characteristic of many of Mops’ basic classes in its reliance on inheritance from a “generic” superclass.
Near the other end of the spectrum is the Menu class definition. For practicality only a few of the 25 or so methods are shown below, including a few universally used ones.
:class MENU super{ x-array } 68k_record { int RESID \ Resource ID of this menu var MHNDL \ Handle to menu-heap storage } :m ID: inline{ get: resID} ;m :m PUTRESID: inline{ put: resID} ;m :m INIT: ( xt1 ... xtN N resID -- ) put: resID put: super ;m :m NEW: ( addr len -- ) \ Create menu with title. Nonresource based. str255 get: resID swap NewMenu put: Mhndl ;m :m GETNEW: \ Resource-based menu get: resid GetMenu dup 0= ?error 127 put: mHndl ;m :m INSERT: \ Inserts the menu in the menu bar. get: Mhndl 0 InsertMenu ;m :m GETITEM: ( item# -- addr len ) \ Gets string for item n get: mhndl swap 1+ buf255 GetMenuItemText buf255 count ;m :m PUTITEM: { item# addr len -- } \ Replaces menu item string get: mhndl item# 1+ addr len str255 SetMenuItemText ;m :m CHECK: ( item# -- ) \ Checkmark an item. get: Mhndl swap 1+ -1 CheckItem ;m :m UNCHECK: ( item# -- ) \ Remove checkmark. get: Mhndl swap 1+ 0 CheckItem ;m ;class |
The superclass x-Array (array of execution tokens) implies that the class definition inherits an x-Array in its data structure, but the class definition need not fix the size of that array. The size specification can be, and usually is, deferred to the object declaration that instantiates an object of the menu class. (This is not an intuitive feature of the array-type classes.) The size specification will determine the number of menu items for a given menu object, so the program obviously needs to set that value itself.
In normal practice, all programs will send a message to the Init: method. It associates actions with all items in a menu object—for example the program’s response to the user’s selection of the New item in the File menu. Also, the program will always call either New: for a non-resource-based menu or getNew: for a resource-based menu. Both methods pass a menu record to the Toolbox. Both methods are fully supported.
For a resource-based menu, the programmer literally draws pictures of each menu, as a ‘menu’ resource, using a resource editor such as Apple’s freeware ResEdit. It’s dead easy, and I can’t draw my way out of a paper bag. The resource-based approach is much simpler to program and is pretty generally used. The average Mops program will need a couple of other resources anyway, so adding the menu resources is no pain.
The getItem: and putItem: methods lets you play with menu item names on the fly, so to speak (and may completely disconcert your user).
Many programs will call the Check: and unCheck: methods; the former puts that neat little checkmark against the name of a clicked item and the latter undoes it. The “1+” word is a shorthand for adding 1 to the number at the top of the stack.
Note that the intelligible names in upper and lower case, such as GetMenu, InsertMenu, SetMenuItemText, and CheckItem are all system calls, or Syscalls. They invoke a Mac Toolbox service. It is important to know that, unlike names in the Mops domain, Syscall names are case sensitive. (Very much so. The Toolbox is quite particular.)
Subclasses and Superclasses
While all class definitions define one class of object, every such definition is at the same time a subclass of another class definition which, unless it is the proto-class Object, is itself also a subclass. That is to say, every user-defined and pre-defined class has an immediate superclass except for class Object. (For the moment, we are ignoring the possibility of multiple inheritance, wherein a class may have more than one immediate superclass.)
Thus, every user-defined class inherits both ivars and methods from its chain of superclasses. (See Inheritance and Overriding below.) This is particularly true of a user-defined class that interacts with the operating system (through the toolbox or otherwise) and typically has several Mops pre-defined Toolbox classes in its lineage. Such a class would normally be referred to as a subclass, but that is strictly a matter of viewpoint.
Inheritance and Overriding
A class definition inherits both the instance variables and methods of its superclass (and of its superclass, on up the line). A class definition must override the name of its superclass. A class definition cannot override the instance variables of its superclass; it inherits those unconditionally. A class definition may add methods to those of its superclass, and may override some methods of the superclass by defining methods with the same names as those of the superclass. (Note that class Object uniquely has no ivars so none are inherited from it.)
On a practical note, the greater the number of methods that are inherited rather than defined at the subclass level the smaller the resulting program (all else being equal).
Multiple Inheritance
In common with a few other object-oriented languages, Mops provides for multiple inheritance, wherein a class can have more than one immediate superclass and thus, two or more different lines of descent, much as a person has both a mother and a father and inherits characteristics from both. The possibility of multiple inheritance allows for a blending of functionality from two or more existing classes in a new class. (Cut your teeth on single-inheritance classes first.) When needed it is a very powerful feature and is often considered the touchstone of a fully object-oriented implementation. Several of Mops’ pre-defined classes employ multiple inheritance, primarily to obtain hybrid data structures.
Message Binding
Mops offers a variety of ways to achieve early or late message-to-object binding. Early vs. late binding is essentially a tradeoff between speed and programing flexibility. In late binding the receiver of a message is not known (or decided on) until runtime. It is a complex and somewhat esoteric subject and it is sufficient to say that Mops provides extensive support for the possible binding modes. Note that the well-known Smalltalk system provides only late binding in all cases.
Conclusions
Being a one-man effort, Mops is not the most stable of programming systems; on the other hand, Mops’ “tech support staff” is very knowledgeable indeed. Mike Hore, the developer, is known as being receptive to real problem reports and usually will get back to you quickly with either a fix or workaround. Beyond that there are a goodly number of expert Mops users who are pleased to offer advice and explanations of this or that obscure bit of the system. The big Mops Manual could do with a lot more organization, but for the diligent reader there is sufficient information there, clearly expressed for the most part.
When I first encountered Mops, the “backward” notation of its Forth heritage was an annoyance to say the least; I got over that fairly soon. The stack programming aspect, once I was pretty well acquainted with it, struck me as very economical, efficient, and so on. I remain however a fan of object-oriented programming, and that is where Mops appealed to me. I liked the clean way that OOP is implemented in Mops. Much of it is intuitive.
All in all, for someone who likes or would like to try OOP (or Forth for that matter), I think that Mops is a good “buy.”
Also in This Series
- Give Alert Sounds a Little Personality · March 2012
- Create Your Own iPhone Ringtones · February 2012
- Create Your Own Homemade Audio Book · December 2011
- Upgrade to Lion Painlessly · August 2011
- Make the Most of TextEdit · July 2011
- Using the Free Disk Utility on Your Mac · May 2011
- Making Use of QuickTime X · March 2011
- Making the Most of What’s Already on Your Mac · February 2011
- Making the Most of What’s Already on Your Mac · January 2011
- Complete Archive
Reader Comments (1)
Tosin
Add A Comment