Object Oriented Programming and APL
Vector 7:3, page 109
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 109
It has been frequently suggested that software development lags behind the fast advancing technology of hardware. The chief reason for such statements is the difficulty in development of new, ever more complex applications. Our software tools often fail to take the fullest advantage of state of the art in our hardware.
To be sure, a great deal of effort is being placed on development of our software tools. Improvements in programming languages, as well as new techniques in programming are constantly being introduced. These often receive much publicity. Examples of improvements in software efficiency, or breakthroughs in problem solving include the following: "Structural programming" "Prolog and Artificial Intelligence", "Neural nets" and more recently "Object Oriented Programming (OOP)".
Object Oriented Programming in particular, has been of considerable interest to the APL community. This is attested by a number of published papers and by proposed conference themes. Before we turn our attention to OOP, it is useful to refresh our memory about the evolution of computer programming languages.
Most programming languages are invented by an individual or a group of individuals, who have some concept of how one should communicate problem solving to a computer. The commonly used name for this concept is "Paradigm". The table below is an incomplete list of distinct paradigms for a variety of languages.
| PARADIGM | CHARACTERISTICS | LANGUAGE |
|---|---|---|
| VonNeuman architecture | efficient translation to machine language | Fortran, C |
| Compact notation | array processing | APL |
| Object Oriented Programming | Class Hierarchy | Smalltalk |
| Tree structures | List processing | Lisp |
| Predicate Logic | Horn clause | Prolog |
When a programming language becomes established it seldom remains unchanged. As new demands demonstrate weaknesses, it begins to evolve. What originally may have been a simple concept, becomes more complex as more features are added to it. Compare, for example the first version of APL to the most recent releases of APL with nested arrays.
If there are those who feel that once a language has proven itself, it should become cast in concrete, they should realise that this is the path to oblivion. Programming languages have been invented to solve specific classes of problems. Today, many of these problems have been solved, as a visit to any PC software store will demonstrate. The problems that have not yet been solved, and these are many, pose a great deal of challenge. We need the sharpest possible tools for their solution. Perhaps the best way to improve our tools is to continue evolving the power and productivity of our programming languages.
Looking at the table of paradigms, one should note that each one tends to have its strength in one particular domain of problem solving. VonNeuman languages are good for efficient usage of existing hardware architectures. APL is good for compact expression of solutions to mathematical and business problems. Smalltalk excels in the handling of graphical displays. Lisp and Prolog are strong in problems dealing with logic.
A significant trend in recent times has been the merging of two or more paradigms into a single language. A good example of this is C++, which is a VonNeuman type language, that has been enhanced by the addition of Object Oriented Programming features. Even APL has not escaped this trend, as we could note by studying Nial.
If there is a weakness to APL, it would be its lack of expressiveness to display graphical images. This shortcoming as well as the need to extend features that ease development of AI applications has prompted me to explore the potential of Object Oriented Programming as a possible paradigm to augment current APL.
In this communication we will explore three questions. What is an Object Oriented language? Can APL be used in an Object Oriented programming style? What would it take to make APL an Object Oriented Programming language?
What is an Object Oriented Programming Language?¶
The concept of Object Oriented programming was developed by the creators of Smalltalk. This paradigm is rooted on four fundamental ideas:*
- Abstraction
- Encapsulation (information hiding)
- Inheritance
- Polymorphism (operator overloading)
*FOOTNOTE: Object Oriented Programming has its own vocabulary of technical words. We will use underlined text to emphasise these. Smalltalk, in turn also uses special conventions to describe its components. Classes, for example are expressed as proper names with upper case first letter. As an example, object and Object are related but distinct concepts.
We will try to explain each of these concepts with simple examples. However, these turn out to be only properties of a more fundamental concept, namely that of an object. The abstract concept of an object cannot exist in a vacuum. Every object must belong. Indeed the name of the item to which an object belongs is called a class.
The definition is circular. A class is also an object, and as such it must belong too. This approach leads to what is known as a "Class hierarchy". At the highest level of this hierarchy is the class known as Object. (To complete the circularity of the definition the class Object in Smalltalk belongs to a metaclass, but this principle can be ignored in elementary expositions.)
How does one develop programs in Smalltalk? Every object, or a class (there is a distinction between these two concepts, which we will for the time being ignore), can receive messages, and act upon them. Indeed messages are the ONLY means of communication with objects. A message can be in the form of a request, or can be a command to do something.
At this point it might be useful to pause in the abstract explanations, and to give specific examples. In APL the following expression is simple and straight forward:
We have a dyadic primitive function that takes a left and a right argument. The function then evaluates the expression producing the result (7). In Smalltalk, this same expression produces the same result, but the interpretation is completely different:
The object (integer 5) receives the message to add two to itself. The primitive function + is called a method (or selector in this special case), which the object called an integer understands, and knows how to process. Why is such a distinctive point of view so important? Because it leads to a totally alien (from the point of view of APL) way of problem solving. This is illustrated by the next two examples:
In APL the following expression is very similar to the first one:
We have the same primitive function in a dyadic expression. In Smalltalk, we are dealing with a different object, namely (real 5.2). The method has the same symbol, but is evaluated in a different way. The property of assigning the same symbol (name) to a method in different contexts is called polymorphism. As a Smalltalk user would put it, we are overloading the plus operator. (Overloading is but one example of polymorphism). The next example is even more revealing:
In APL we are dealing with another dyadic function, but in Smalltalk things are totally different. The object (integer) does not know how to evaluate >. The reason is that the symbol > is evaluated in exactly the same way for an integer as for a real number. So why should there be two copies of a function that does the same thing to objects in different classes (integer and real)?
The solution in Smalltalk is to recognise that both integer and real are subclasses of another class called Number. Therefore when the object 5 sees the method > and fails to recognise it, it seeks to find that method in its superclass namely Number. As it turns out, the class Number also fails to find this particular method. It seems that there are classes other than numbers (e.g. date) for which the > symbol has a meaning.
Not finding the method > in class Number, Smalltalk goes to the next superclass which is Magnitude, and discovers that this class knows how to proceed. This process of searching up the higher levels in the class hierarchy is called inheritance.
We should not leave this brief and admittedly sketchy description of Object Oriented paradigm without mentioning the other two attributes, namely Abstraction and Encapsulation.
In principle, at the most fundamental level, Smalltalk could be defined by only one class, namely that of an Object. But now if we want to solve arithmetic problems, we must define those methods (addition multiplication …) that are needed for this purpose. Since there are many types of problems where these primitive functions are not used, it does not make sense to define addition in the class Object. What Smalltalk allows is to define a new class, a subclass of Object.
This subclass is to contain all of the methods that may be needed to solve the problem. However, before doing that, it is important to evaluate the possibility of other similar problems that might not be of arithmetical nature, but which may need some of the same primitive methods. This is the rationale for defining Magnitude as a subclass of Object, Number as a subclass of Magnitude, and Integer as subclass of Number. This feature of segregating an classifying objects by common denominator methods (and sometimes even variables) is called abstraction.
The attribute called encapsulation is a natural consequence of abstraction and the usage of messages as the only means of communication. An object will respond only to a valid message. That message may request access to either a function (method) or data. Neither the method nor the data (there may be exceptions but this is not relevant for this elementary discussion) is available outside the object. This idea that functions as well as information is strictly local to each object is called encapsulation.
Earlier we treated objects (not to be confused with the class Object) and classes as the same. This is not quite true. When a new class is defined, it contains a special method, called the class method, that has the capability of generating specific instances of that class called objects (there are also class variables). Each instance is in some ways a copy of the class, in the sense that it can respond to messages, store data and produce results. Instances are like leaves on a tree. They exist, do a useful function, and fall off when no longer needed. Leaves, unlike branches (classes), however, cannot bud into new branches (subclasses).
Can APL Be Used in an Object Oriented Programming Style?¶
The brief answer is: certainly. It is always possible to develop a workspace (or a collection of workspaces) together with suitable files to simulate any particular paradigm. However, some characteristics of Object Oriented Programming might be more difficult to produce than others. Consider encapsulation: we would have to be able to generate arrays that contain local functions and variables. Clearly APL does not have such facility.
On the other hand, by allowing functions and variables to be stored in files, and by providing suitable cover functions (also to be stored in files) we can simulate that kind of an effect. If this approach is inefficient, we could release the requirement of encapsulation, by allowing class functions and variables to be global. However, the price of that would be a restriction on other aspects of OOP such as polymorphism. Consider the following example:
We define an APL (APL2 would be better) function called
PLUS. This function behaves in every way the same as the primitive +:
The last example shows that PLUS is overloaded in the sense that it also evaluates hexadecimal character strings. But now, because PLUS is a global function, and not local to the class hexadecimal, we are no longer able to use it to define addition of character strings in systems with base other than 16.
With true encapsulation, we could generate objects (number systems) to arbitrary base such that function PLUS would be evaluated correctly. Let us sketch briefly how true encapsulation might be implemented in APL. To illustrate the approach, let us continue with the example of function PLUS. Let us assume that we desire to also work with octal notation. We will do it by creating objects using only a current version of APL.
What would such numeric objects look like? Since objects must have characteristics of functions as well as data, we have to decide whether to use defined functions or arrays. For the numeric example, we will use defined functions, but later we will also show an example where a nested array can be used more efficiently. We start by defining the monadic function OCTAL:
We would like to use function PLUS to do addition in this number system:
Since function PLUS is global, the result of OCTAL 7, the character ‘7’ would be interpreted as hexadecimal ‘7’ which is the same as decimal 7. The answer we would actually get is ‘E’ which is incorrect. In fact the symbol ‘E’ is not even defined in the octal system.
The APL OOP solution is to create class Octal, and to transfer the expression as a message to that class. (Note that function OCTAL is in caps, but class Octal is a proper name).
So far so good, but how does one go about insuring that the correct class method (APL function) will evaluate this expression? It is done under a special function called a "driver". A simple driver called GO is illustrated below:
This niladic function is an infinite loop that simulates an APL prompt. Whatever is entered at the keyboard, becomes the character string (E) which is evaluated by function EVAL. The objective of function EVAL is threefold:
- It has to determine the "domain of expertise" of expression E.
- It has to create the object capable of solving the problem.
- It turns over expression E to that object as a message.
Note that if EVAL merely executes E, function GO is "invisible" to valid APL expressions. This means that as far as APL expressions go, GO does absolutely nothing, except use up extra computer time. (It would cause an error halt for any System Commands though.)
Let us take a closer look at function EVAL:
∇ EVAL E;D;DRIVER
[1] ⍝ Driver for evaluation of non-APL expressions
[2] D←GETCLASS E
[3] GETDRIVER D
[4] DRIVER E
∇
To see how this function works, consider the following expression:
Function GETCLASS extracts first the tokens from that expression. (Here we use the word "token" to mean any character strings not enclosed in quotes.) The token PLUS is recognised as a function, but the token OCTAL is not identified. With the help of a dictionary, EVAL determines that OCTAL is associated with class Octal. This label is returned by GETCLASS to local variable D.
Next, function GETDRIVER loads into the workspace function DRIVER, a class method in class Octal (a polymorphic function). This driver contains in its header the names of all methods as well as variables that are associated with that class. DRIVER is then given the string E for evaluation. The details of the algorithm in DRIVER vary from class to class. Here we show only a portion of the header that is pertinent to the current problem:
Function DRIVER first retrieves from component files whatever methods will be needed to solve the expression. At this point expression E becomes an executable APL expression. On completion of its task, DRIVER, which is local to EVAL disappears together with all associated methods and variables.
In practice, this example is much too simplistic. Our next expression may also belong to the same class. It would make sense than to write the code in such a
way that a series of messages default to the same class (or instance of a class) until a new "domain of expertise" is called for. The simplest way would be to invoke function GO within DRIVER. This approach may be adequate for specific situations, but does not address the general problem.
Let us now look at another problem. In this next example, the OOP solution involves dealing with arrays. Consider the property of inheritance and the following nested arrays:
JOHN
'NAME' 'John'
'CLASS' 'Relative'
...
'ISA' 'PERSON'
...
Person
...
'ISA' 'Human'
...
Human
...
'HANDS' 'two'
...
These arrays have been generated in a previous session when a particular family tree was stored under class Relatives. The arrays do not reside in the workspace, but will be loaded (from appropriate component files) in response to the message described below by function DRIVER which is specific to class Relatives.
To borrow terminology from the field of Artificial Intelligence, these arrays are called frames. The array called JOHN (an instance), has many rows and two columns (only those pertinent to this example are shown). The array Person (a class) shows only the row associated with class variable ISA, and the array Human (another class) shows the row associated with class variable HANDS. These arrays have been made available through functions EVAL and DRIVER as a result of examination of the following expression:
FOOTNOTE: Since my work to date has been done under APL*PLUS, the above arrays would be stored in Component files as segmented strings.
The allocation of a proper domain of expertise may not be obvious in this case. Function EVAL may handle that with the following dialogue:
(Before solving the expression, the user is asked to specify the appropriate domain of expertise. Entering ‘Relatives’ should be adequate.)
If John is an instance of class Relatives, we may find that the variable HANDS is not defined in that class. However the label ISA points to possible inheritance in class Person. The search for a response to message HANDS eventually will lead to the contents of line ‘HANDS’ in class Human.
The practice of naming first the object, followed by the message is unwieldy and too restrictive. With the power of APL, some additional parsing functions should enable us to provide a more human-like communication:*
*FOOTNOTE: The attendees of my APL87 tutorial on the subject of Expert Systems had the option to take home with them a workspace and associated component files that illustrated this expanded notion.
Clearly, the array Person is more complex. Since it defines a class, it should contain additional information:
CMETHOD, for example may specify an APL function to be invoked whenever an instance of class Person is to be generated. (There may be more than one class method, since Person may require responding to more than one type of message.) CVARIABLE may specify one or more names (and possibly contents) of class variables. Some of the class components such as ISA may also be transferred to each instance. These would be called instance methods and instance variables. (Instance methods and variables may also be defined within an existing instance). ISA in instance JOHN is an example of a variable that points to a possible inheritance path. FUNCTION in Person could contain a list of local APL functions (methods) that will respond to messages sent to instances of class Person.
There is no implication that the approach illustrated in these two examples is the only or even the best way to handle Object Oriented programming with APL. It merely demonstrates the feasibility of using APL in that way.
Although the workspace approach to Object Oriented Programming may seem adequate, it is not. In a WINDOWS environment, such as that pioneered by Smalltalk, it is possible to re-enter a suppressed object by a mere click on the mouse. This would be roughly equivalent to being able to re-enter a defined function, and to reuse previously generated local variables. This is clearly not feasible with current versions of APL. The examples illustrated in this section could be made re-entrant, but only at prohibitive costs in execution speed.
What Would it Take to Make APL Object Oriented?¶
Developing Object Oriented programming style in APL is feasible, but requires building a superstructure of defined functions in a workspace. It also may require the generation of special component files to implement true encapsulation. The natural next step is to ask, if it would be possible to enhance APL so that the Object Oriented approach is built into the language.
In this section, we will consider such a modification, but do it with the caveat that it is not a proposal. At this stage, it is not clear whether the evolution of APL should take such a radical direction. We shall call the new version of this language APLOOP.
Since an object must have characteristics of both data and functions, it makes sense to use the Ø symbol to define it. To distinguish an object (or class) from a defined function or operator, let us use the square bracket in the header:
The header defines the usage of Human in a simple way. It is not adequate for applying the full range of Object Oriented programming as it stands, but is a good start for a discussion. So far Human looks like a defined function. It is monadic and produces a result. Let us see how it would work:
Even though Human looks like a function, we already see some differences. The right argument is a character string, but is not in quotes. In OOP terminology, the right argument is a message. This means that the right argument could be a query or an assertion about the object. In the above simplistic example, the right argument merely calls for the contents of variable THUMB. Is THUMB a local or a global variable? Well it is neither. If we write:
Yet as we will see, THUMB must be available to objects outside the local domain of Human. This property of making data or functions unavailable except through messages is encapsulation. As indicated in the comment line, Human is not an object but a class. The variable THUMBS is a class variable.
Suppose now that we desire to add more characteristics to the class Human. We could do it by entering a new line:
But wait, what is the domain of expertise of this application? If we are dealing with the subject of Natural History, should we not recognise that "stereoscopic vision" is possessed by all primates?
Certainly this is not only valid, but also a more general statement. We had to create a new class called Primate. How can we relate class Human to class Primate? We could do it by defining Human as a subclass of Primate. This could be done in the header for Human:
In OOP terminology, Human inherits VISION from Primate. Furthermore, Primate is a superclass of Human, and Human is a subclass of Primate. The structure tying the two classes together is class hierarchy. With this new definition, the APLOOP interpreter should work:
Suppose now that we broaden the scope of discourse to cover individuals. To describe people, anatomy alone is inadequate. We are embedded in a civilisation and therefore we need another class hierarchy:
Culture belongs to yet another class hierarchy. It may have its own class variables, or even class methods. FORM1 is an example of a class method. It is encapsulated in Culture:
Before we can see what FORM1 does, we must explain the assignment in the square brackets of the header. The class Culture is a prototype of instances. The header merely describes the manner in which a new instance is to be generated. Here is how it would work:
american ← Culture
------------------------------
|RELIGION ....................|
|GOVERNMENT ..................|
------------------------------
When Culture is invoked and assigned to "american", a new instance of class Culture will be generated, triggering the class method named FORM1. FORM1 is a defined APLOOP function that generates an object (instance of class) Window, and prompts the user to fill out the form. Suppose that we enter "mixed" under RELIGION, and "democracy" under GOVERNMENT. Then we can enter:
Given this approach, how could one define function FORM1 so that it will be available ONLY as a method in class Culture? One possible way would be to allow the following header in the function definition:
To instantiate an individual, we need to associate two distinct class concepts: anatomy and civilisation. To implement this complex structure we require a complex header:
We could have expanded this definition by adding more lines. But in this example, let us assume that we desire to describe each individual in a unique way. We are trying here to show that to each instance it is possible to add any number of class variables (or class methods):
This expression instantiates JOE.
We assign an instance variable named PHONE to JOE. To access the PHONE belonging to JOE, we enter:
The message VISION asks for the contents of variable VISION. The search proceeds up the class hierarchy, and eventually finds it under Primate. (This might be difficult to do in Smalltalk, since we are dealing with two distinct paths of search: Culture and Human. The concept that supports a solution is called multiple inheritance. Smalltalk does not support multiple inheritance, but C++ does.
Note that the message, a character string, has no quotes. The variable VISION now becomes an instance variable. Since it is contained in the instance called JOE, it will take precedence over any value that may exist in the hierarchical chain:
This exploration of possible expansion of APL to include Object Oriented characteristics could be extended with even more details. However, the purpose here is merely to open a possible door for a sketchy view of what might be. It is conceivable that the price for extending APL in this direction is too steep, when compared to potential benefits. Whatever the future of programming, we who love to use APL should not ignore such developments as OOP; on the contrary, we should strive to become more knowledgeable in them.