Object-oriented programming in APL (9 of 9)
Contents
Dyalog APL adds support for object-oriented programming to the core APL language. These facilities are broadly similar to those implemented in other object-oriented programming languages (such as C++, C#, Java, or Ruby), but with the difference that APL's array-programming approach applies to classes and objects in the same way as it applies to ordinary data.
For further information you should read the vendors' documentation, or see the British APL Association's "Vector" web site for introductory articles on the subject covering Dyalog APL.
The Dyalog APL programmer can write her own APL classes and use them to create instances of these as APL objects. A programmer can create objects from existing .NET classes.
Dyalog APL includes a large number of language features including keyed access properties. Dyalog can not only make use of classes written in other .NET languages like C#. Classes written in Dyalog can be used from C# and relatives as well.
The material on this Wiki page is intended just to give a flavour of what is possible in object-oriented APL without worrying too much about the differences...
Jargon
The fundamental building block for object-oriented programming is the class. For example, in a commercial invoicing application, a given class might represent the attributes and behaviour of an invoice, and another class might represent a credit note.
In an application concerned with geometry, a class might represent a sphere, or a rectangle, or a polygon.
A class contains definitions both for program logic (functions and operators, known collectively as the methods of the class), and for data (named variables associated with the class, known as fields and properties). The term members is used to describe both the fields/properties and methods of a class.
In most cases, when you come to use a class, you need to create an instance of that class, also known as an object. Whereas the class represents an abstraction of (say) an Invoice, or a Sphere, or a Rectangle, an object represents a particular invoice, sphere or rectangle. Typically, you may have many instances of a given class, each containing independent copies of data (fields/properties), but all supporting the same program logic (methods).
Dyalog APL makes a distinction between a field and a property. From the object user's point of view, both look like variables belonging to the object. However, a field is a simple variable whereas a property is accessed via getter and setter functions, allowing the author of the class more control.
To keep things simple, in this tutorial we will use the term 'property' to mean both 'field' and 'property'.
Getting Started
To make things clearer, let's use an example of a class representing a circle. To keep things simple initially, we will give the Circle class a single property representing the radius of the circle.
There are a number of ways to create the Circle class, but for try the following:
Start editing a new class by entering:
)ED ○ Circle
(The ○ char is a shortcut to tell the Dyalog editor that you are going to edit a class script. Otherwise Dyalog would assume the default, meaning editing a function or operator)
- Then enter the following class definition:
:Class Circle :Field Public radius :EndClass
We have now created a new class, which we can see by using the )CLASSES system command, which is analogous to )FNS and )VARS:
)CLASSES Circle
To create an instance of the Circle class, you typically use the ⎕NEW system function:
FirstCircle ← ⎕NEW Circle
This has created a new instance (object) and assigned a reference to it in the variable FirstCircle:
)VARS FirstCircle
If you try to inspect the new object, APL will display it in the following format by default. Note that (unless you change the default display), the object is displayed by showing its class name in square brackets
FirstCircle [Circle]
Now let's assign a value to the new Circle object's radius:
FirstCircle.radius ← 10 FirstCircle.radius 10
Note the dot-notation used to specify the object and property being assigned. Except that it's a member of the object FirstCircle, the radius property behaves like any other APL variable:
⍳FirstCircle.radius 1 2 3 4 5 6 7 8 9 10 FirstCircle.radius=20 0
We can also create a vector containing five different circles:
CircleList ← ⎕NEW ¨5⍴Circle CircleList [Circle] [Circle] [Circle] [Circle] [Circle]
It is possible to set the radii of all of the circles in the same statement:
CircleList.radius ← 10 50 20 30 20 CircleList.radius 10 50 20 30 20 CircleList.radius=20 0 0 1 0 1
Notice that APL lists the radius values for all five circles in a single vector, which can be used in expressions.
We could also specify all the radii using a single scalar, in which case the scalar is assigned to the radius property in each object using scalar extension:
CircleList.radius ← 20 CircleList.radius 20 20 20 20 20
Now let's add a method called Area to our Circle class. Again, there are many ways of editing classes, but for now you can use the following:
Bring up the editor as before and change the class definition to:
:Class Circle :Field Public radius ∇R←Area :Access Public R ← (○1)×radius*2 ∇ :EndClass
We can now call our new method using the following notation. (Notice that the new method can be applied to existing objects - try doing that in Java!)
FirstCircle.Area 314.1592654
Within the method, the object's radius can be referred to directly as just radius, without using dot notation. APL knows that the Area method has been called for the object FirstCircle, and hence uses the radius value contained in the object.
Again, if you have an array of objects you can apply the same method to each one.
CircleList.radius ← 10 50 20 30 20 CircleList.Area 314.1592654 7853.981634 1256.637061 2827.433388 1256.637061
Internally, APL will call the Area method on each of the objects in turn, in the order in which they occur in the object list.
It is also possible to have vectors containing references to objects of different classes. Suppose that we have a new class called Square, in addition to our Circle class, and that Square also has a class method called Area:
FirstSquare ← ⎕NEW 'Square' ShapeList ← FirstCircle,FirstSquare ShapeList [Circle] [Square] ShapeList.Area 314.1592654 100
Note that there is not necessarily any relationship between the Area methods in the Circle and Square classes.
Constructors
What would happen if we create a new Circle object and immediately try to display its radius property?
AnotherCircle ← ⎕NEW Circle AnotherCircle.radius VALUE ERROR AnotherCircle.radius ^
Because the radius property of the new object has not been assigned, we get a VALUE ERROR; the new object is incomplete. Is there some way we can ensure that only completely-built objects are created by ⎕NEW?
One way in which this can be done is to add a new class method called a Constructor. The APL interpreter will call the constructor method automatically when a new object is created. The Constructor for the Circle class might look something like this:
Bring up the class in an editor window again, and add the following:
∇make R :Implements Constructor :Access Public radius ← R ∇
The constructor method we added takes a right argument, which is the initial value of the new circle object's radius. To create the circle, the value is passed as an argument to ⎕NEW:
AnotherCircle ← ⎕NEW Circle 100 AnotherCircle.radius 100
In some object-oriented languages, constructors are the only way of assigning initial values to properties. In APL, it is also possible to specify the default value of any properties in the class definition. Instead of using a constructor, we could have changed our Circle class to specify that the radius property of all new Circle objects should have an initial value of 10.
Inheritance
Suppose that we wanted to re-write our example to handle two types of shape, circles and squares.
One way to do this would be to use a general class called Shape. We could add a property which specified the type of shape (0 for Circle, 1 for Square), and we could add an Area method, something like this:
∇R←Area [1] :Access Public [2] :If type=0 [3] ⍝ Circle [4] R ← (○1)×radius*2 [5] :Else [6] ⍝ Square [7] R ← sidelength*2 [7] :EndIf ∇
However, in object-oriented APL there is a much more elegant way to do this, by using Inheritance.
When you define a class, you can specify that it inherits from another class. The new class is said to be the child, and the class it inherits from is the parent or base class. Inheritance means that (unless you explicitly change their definition), all of the properties and methods defined in the parent class are also available in the child class. This works for further levels of inheritance as well, so that methods and properties can be inherited from the immediate parent, or from the parent's parent, and so on. The terms derived classes or descendants are sometimes used to denote the children of a class, and the children's children, and so on. Similarly, the term ancestors of a class is used to denote the parents, parent's parents, and so on.
So you might have a class Shape, representing an abstract geometric shape. This might have properties called X and Y giving the centre point of the shape, and methods called Move and Area.
A Circle class might inherit from Shape, introducing further properties such as radius. Equally, a class Polygon might also inherit from Shape, and further classes Triangle and Square inherit from Polygon. All of the classes Circle, Polygon, Triangle and Square are derived from Shape. Because of the way inheritance works, they would all include the properties X and Y, and the methods Move and Area.
When a class inherits from another, you can specify that the definition of a given method of the parent (or the initial value of a property) is different in the child class. In our example, you would need to supply a different definition of the Area method for a Circle and a Square. This is known as overriding the method.
Definition of the Area method in the Circle class:
∇R←Area [1] R ← (○1)×radius*2 ∇
Definition of the Area method in the Square class:
∇R←Area [1] R ← sidelength*2 ∇
(Again, for Dyalog APL you would also need to add an ":Access Public" line to each method)
For classes defined in APL, all methods can be overridden, and all methods are virtual, that is to say if method A in a base class calls another method B, and the second method B is overridden in a child class, then running method A in the child class will cause the overridden version of B to be called, not the version of B defined in the parent. For example, if you are running a method defined in the base class Shape, and that method calls Area, the version of Area which gets called will be Circle.Area or Square.Area as appropriate.
Object References and Class References
When you create an object, i.e. an instance of a class (using the system function ⎕NEW), the explicit result that is returned is not the object itself, but a reference to the object. This reference is held internally as just an index into a table of objects which APL maintains. If you assign the reference to another variable, the object itself is not copied; instead, you have two references to the same object.
Of course, because APL is an array language, you can have arrays of object references, and you can embed object references in nested arrays along with other data. For example, you might have an array containing references to hundreds of Rectangle objects.
You can also have a reference to a class. This makes it possible for general functions to act on classes without knowing in advance which class applies.
External classes
Dyalog APL allows you to create objects from classes written in .NET languages like C#. They can then be used by other classes no matter which language any of them is written in.
Further Information
Object oriented programming in APL is a big subject and we have only covered a small part of it. For further details consult the documentation provided by your APL vendor.