Current issue

Vol.26 No.4

Vol.26 No.4

Volumes

© 1984-2024
British APL Association
All rights reserved.

Archive articles posted online on request: ask the archivist.

archive/23/4

Volume 23, No.4

Classes as a tool of thought
or: acquiring a new father after you’re born

Simon Marsden, MicroAPL Ltd
microapl@microapl.co.uk

This article was originally presented as a talk at the BAA AGM in June 2008.

APL has always been a great language for trying things out, changing and experimenting until you’re happy with an application. That’s why it is often referred to as a ‘tool of thought’.

My purpose in this talk is to demonstrate how the design of APLX encourages the user to work this way even when writing object-oriented code. In other words, to show how you can design and modify the class hierarchy as you go along, even when you have instances of the classes you are changing.

To do this, let’s consider a concrete example, one small enough to explore here but large enough to demonstrate some interesting features of interactive OOP development. Imagine that we work for a company called BigCorp and have been given the job of organising a 5-a-side football tournament for its employees.

Creating our first class

To start off with, we need to get a list of employees from the company’s personnel database. We can do this very easily in APLX using ⎕SQL.

    1 ⎕SQL 'connect' 'aplxodbc' 'DSN=BigCorpDB;PWD=xx;UID=root;'
0 0 0 0
    1 ⎕SQL 'do' 'use bigcorp'
0 0 0 0
    fields←'FIRSTNAME,LASTNAME,SEX,EMAIL,EMPLOYEENUMBER,DEPARTMENT'
    (rc errmsg employeeData)←1 ⎕SQL 'do' 'select',fields,'from employeedata'

We decide on an object-oriented solution to our 5-a-side problem, and that our first class should represent an Employee. This will have a number of properties like firstName, lastName, etc, and a constructor to initialise them when an object is created.

To save space, let’s omit the step of using the APLX class editor to create the class. Here is the listing of the finished class. The first part of the ⎕CR listing shows the properties, followed by the constructor. (In APLX, the constructor always has the same name as the class).

      ⎕CR 'Employee'
Employee {
firstName
lastName
sex
email
employeeNumber
department
 
∇Employee arg
(firstName lastName sex email employeeNumber department)←arg
∇
}

We now have a new workspace entry called Employee, which is a class reference which we can use to create new instances of the class. Here we create a vector with an object representing each employee:

      )CLASSES
Employee
      Employee
{Employee}

      employees←⎕NEW ¨⊂[2]Employee,employeeData
      ⍴employees
44
      3↑employees
[Employee] [Employee] [Employee]

Notice that the default display of the first three list elements is not very helpful – it just tells us that we have three objects of type Employee. We can examine an individual object (or many) using ⎕STATE:

       employees[1].⎕STATE 0
firstName                         Bert
lastName                         Brown
sex                                  M
email           Bert.Brown@bigcorp.com
employeeNumber                      24
department                           C

However, it would be nice to have a quick way of telling Employee objects apart, and we can do this by changing the default Display Format of an object using the system method ⎕DF:

      employees.⎕DF '[',¨employees.lastName,¨']'
      3↑employees
[Brown] [Ptolemy] [Oakum]

Let’s play football

The next thing to do is to tell everyone about the 5-a-side competition and invite them to play. We can do this fairly easily by sending everyone in the company an e-mail:

      SM←'⎕' ⎕NEW 'SendMail'
      SM.host←'smtp.bigcorp.com'
      SM.user←'bigcorp'
      SM.password←'sesame'
      SM.Open
0
      SM.from←'simon@bigcorp.com'
      SM.to←¯1↓∊employees.email,¨','
      SM.subject←'Five-a-side Tournament'
      SM.body←'Would you like to play five-a-side football?'
      SM.SendMessage
0
      SM.Close
0

Let’s imagine that we have had replies to our e-mail and that 25 people are interested in playing. We’ll choose 25 random players for the sake of the example:

     wantingToPlay←employees[25↑(⍴employees)?⍴employees]

Now we need to allocate them into teams of five players. We’ll use a very simple new class Team which has no constructor and no methods yet, and just has properties called name and players.

      ⎕CR 'Team'
Team {
name
players
}

We can then create an object to represent each team:

      numTeams←⌊(⍴wantingToPlay)÷5
      teams←⎕NEW¨numTeams⍴Team
      teams.players←,⊂[2](numTeams,5)⍴wantingToPlay
      teams[1].players
[Brown] [Ptolemy] [Oakum] [Fry] [Wittering]

Saving Objects

Since the 5-a-side tournament is going to last for several weeks, we need to save the details of the teams somehow.

Imagine for a moment that you were writing this application in another object-oriented language like C# or Java, or even another interpreted object-oriented language like Ruby. In this case, you would need to write the team details to some kind of external file, either a plain text file or something more elaborate. You would also need to write code to read back the details from the file and recreate the team objects.

In APLX, things are much simpler. APL objects continue to exist if you )SAVE the workspace and reload it at a later date. Although this seems natural to an APL programmer, it’s only possible because APL uses the concept of a workspace. It’s interesting both because you have to write less code, and because it allows you to experiment freely with how classes should be structured…

Re-factoring classes

Let’s go back a step and imagine that we only had 24 people who want to play football. Unfortunately, we don’t now have enough players to make five complete teams, so some people will be left out:

      wantingToPlay←24↑wantingToPlay
      numTeams←⌊(⍴wantingToPlay)÷5
      teams←⎕NEW ¨numTeams⍴Team
      teams.players←,⊂[2](numTeams,5)⍴wantingToPlay
      wantingToPlay~∊teams.players
[Smith] [Jones] [Khan] [Pasty]

However, they ‘have this mate who plays a little football.’ He’s not a company employee, but can he play anyway?

The new player doesn’t have an employee number or a department, so it looks like we made a mistake with our initial design of the Employee class. We need to move most of its properties into a new class Person and then make Employee inherit from it.

This is where the interactive nature of APLX starts to get interesting, because we can accomplish this without needing to re-do everything from scratch. We can modify the class structure but carry on using all our existing objects! This is very unusual in the world of object-oriented programming languages.

To create the new Person class, we could use the APLX class editor to create the class and then add the constructor and all the properties we need, one at a time. For a small class this would not take too long but there is a quicker way to do it, because the system functions ⎕CR and ⎕FX have been extended to work with classes:

Get a text representation of Employee:

      text←⎕CR 'Employee'

Edit this text to rename the class as Person and delete the two unwanted properties employeeNumber and department:

      text
Person {
firstName
lastName
sex
email
 
∇Person arg
(firstName lastName sex email)←arg
∇
}

Fix the new class

      ⎕FX text
Person

Having created the new Person class, we want to make Employee into a child class which inherits from it by using the system function ⎕REPARENT:

      )CLASSES
Employee        Person

      Employee.⎕PARENT
[NULL OBJECT]
      Employee ⎕REPARENT Person
      Employee.⎕PARENT
{Person}

Finally, we change the class definition of Employee to delete the properties which we’ve moved into Person, and change the constructor as follows:

      ∇Employee arg
      ⍝
      ⍝ Call the constructor of our parent class
      Person 4↑arg
      ⍝
      ⍝ Initialise extra fields
      (employeeNumber department)←4↓arg
      ∇

(Notice how a constructor can be called just like an ordinary method. Here we call the base class constructor, Person.)

All the objects we’ve already created continue to exist:

      3↑wantingToPlay
[Brown] [Ptolemy] [Oakum]

Now we’ve changed our class hierarchy we are ready to bring in the extra player from outside the company to make up the fifth team:

      newplayer←⎕NEW Person 'Ronaldo' 'Moreira' 'M' ''
      newplayer.⎕DF '[Ronaldinho]'
      newteam←⎕NEW Team
      newteam.players←newplayer,wantingToPlay~∊teams.players
      teams←teams,newteam
      ⍴teams
5

Notice that the new team is of mixed composition:

      newteam.players.⎕CLASSREF
{Person} {Employee} {Employee} {Employee} {Employee}
      +/Employee=newteam.players.⎕CLASSREF
4

Any questions?

You may have some questions at this point. First of all, what happens to an existing object if you delete its class? The answer is that the object still exists but you can no longer do much with it:

      )SAVE football
2008-06-02 14.10.32
      teams[1]
[Team]
      teams[1].players
[Brown] [Ptolemy] [Oakum] [Fry] [Wittering]
      )ERASE Team
      teams[1]
[DELETED CLASS]
      teams[1].players
VALUE ERROR
      teams[1].players
      ^

How about if, instead of deleting the class Team we just delete the players property? Because the property no longer exists, APLX immediately deletes it from any object instances and reclaims the memory, whilst leaving the objects otherwise untouched

      )LOAD football
SAVED 2008-06-02 14.10.32
      )ERASE Team.players
      teams[1].players
VALUE ERROR
      teams[1].players
      ^

However, before doing so, APLX will check to see whether the parent class contains a property of the same name. If so, the object’s property value is still accessible and is not deleted. (This is why it was important to re-parent our Employee class before deleting its unwanted properties, so that we didn’t change any existing Employee objects).

How about trying to create circularities in the class inheritance hierarchy?

      Employee ⎕REPARENT Person
      Person ⎕REPARENT Employee
DOMAIN ERROR
      Person ⎕REPARENT Employee
      ^

Creating a football league

It’s now time to assign names to the teams. Again we’ll use the ⎕DF trick to make it easier to identify different Team objects:

      teams.name←'Sheffield Tuesday' 'Water Cooler Wanderers' 'The P45s'
                 '5-0-0 Formation' 'Brazil Nuts'
      teams.⎕DF '[',¨teams.name,¨']'
      1↑teams
[Sheffield Tuesday]

The last class we need to create is a Match class. This will have three properties – the two teams who will play, the score (initially 0,0), and a Boolean played indicating whether the match has taken place yet. The class also has a constructor, and a method Winner which returns the winning team. Here is the listing of the completed class:

    ⎕cr 'Match'
Match {
played
score
teams
 
∇Match arg
⍝
⍝ Store object references for the two teams who will play the match
teams←arg
⍝
⍝ Match not played yet
played←0
score←0 0
∇
 
∇R←Winner
⍝
⍝ Returns winning team as 1-element vector,
⍝ or empty vector if match is a draw or not yet played
⍝
R←played/(×-/score)↑teams
∇
}

Having created the Match class, we can create a vector of all the matches, assuming that each team plays every other team once:

      numTeams←⍴teams
      x←x≠∨\x←(2⍴numTeams)⍴(numTeams+1)↑1
      x
0 1 1 1 1
0 0 1 1 1
0 0 0 1 1
0 0 0 0 1
0 0 0 0 0
      matches←⎕NEW¨Match,¨(,x)/,teams∘.,teams
      ⍴matches
10
      matches[1].teams
[Sheffield Tuesday] [Water Cooler Wanderers]

Changing the class of an object

What would happen if one of the employees leaves the company but wants to continue playing? We can use the ⎕RECLASS system function to change the class of an object instance:

Pick someone to leave and check a few things:

      leaver←wantingToPlay[?⍴wantingToPlay]
      leaver
[Jones]
      leaver.⎕CLASSREF
{Employee}
      leaver.employeeNumber
5

Change the class, and note how some properties are no longer accessible:

      Person ⎕RECLASS leaver
      leaver
[Jones]
      leaver.⎕CLASSREF
{Person}
      leaver.employeeNumber
VALUE ERROR
      leaver.employeeNumber
      ^

The normal use of ⎕RECLASS is to change the class of an object to another related class, as in the example above. However, there is nothing stopping you changing to a completely unrelated class if you wish.

Send in the clones

Now suppose that someone is injured and has to drop out of the tournament completely, so that their team needs to bring in a new player.

It’s easy enough to modify the appropriate Team object, but if we do so we’ll no longer have any record that the injured player took part in the earlier matches. It would be quite nice to keep a record of who actually played in each match.

To do this, we can use the system method ⎕CLONE to make a copy of each team object at the time that a match is played. After adding a new Match property called whoPlayed, we can do:

      matches[1].played←1
      matches[1].score←3 2
      matches[1].whoPlayed←matches[1].teams.⎕CLONE 1

The ⎕CLONE method has produced one duplicate copy of each of the two teams. The clone objects are independent from their original parents; changing the teams will not affect the copies.

Producing a League Table

Next, we want to be able to produce a League Table which shows the teams’ positions as the 5-a-side tournament progresses. To do this, we choose to modify the definition of our Team class to add the following methods:

MatchesPlayed
Return a vector with one element for each match the team has played
MatchesWon, MatchesDrawn, MatchesLost
Return a vector with one element for each match the team has won, drawn or lost.
GoalsFor, GoalsAgainst
Return the number of goals scored and conceded.

Here, for example, is the first new method. Note how the niladic system function ⎕THIS is used within the class method to find out whether our team matches any of the teams playing:

∇R←MatchesPlayed
⍝ Returns a list of all the matches the team has played in
⍝
⍝ Get a list of all the matches that have been played so far
→(0=⍴R←(matches.played)/matches)/0
⍝
⍝ Did we play as either team in each match?
R←(∨/¨⎕THIS=R.teams)/R
∇

And here is the global function which will produce the league table:

      ∇League[⎕]∇
      ∇R←League;points;goalDifference
[1]  ⍝ Return league table of current positions
[2]  ⍝
[3]  ⍝ First calculate points and Goal Difference
[4]  ⍝ (3 points for a win, 1 for a draw)
[5]   points←(3×∊⍴¨teams.MatchesWon)+(1×∊⍴¨teams.MatchesDrawn)
[6]   goalDifference←teams.GoalsFor-teams.GoalsAgainst
[7]  ⍝
[8]  ⍝ Now create the league table
[9]   R←teams.name
[10]  R←R,(∊⍴¨teams.MatchesPlayed,teams.MatchesWon,teams.MatchesDrawn,
      teams.MatchesLost)
[11]  R←R,teams.GoalsFor,teams.GoalsAgainst,goalDifference,points
[12]  R←⍉(9,⍴teams)⍴R
[13] ⍝
[14] ⍝ Sort it. For teams with the same number of points,
[15] ⍝ sort on least matches played, then on Goal Difference, then Goals
     Scored
[16]  R←R[⍒⍉⊃[2](points)(-∊⍴¨teams.MatchesPlayed)(goalDifference)
      (teams.GoalsFor);]
[17] ⍝
[18] ⍝ Add the column headings
[19]  R←(('')('Pld')('  W')('  D')('  L')(' GF')(' GA')('GD')('Pts')),[1]R
      ∇

Let’s imagine that the results are in, and the final positions are these:

      matches.played←1
      matches.score←(3 2)(0 0)(7 1)(2 2)(0 0)(1 5)(8 0)(1 1)(0 1)(0 0)

      League
                        Pld   W   D   L  GF  GA GD Pts
 Sheffield Tuesday        4   2   2   0  12   5  7   8
 5-0-0 Formation          4   1   2   1   7   9 ¯2   5
 Brazil Nuts              4   1   2   1   3  10 ¯7   5
 Water Cooler Wanderers   4   1   1   2  11   8  3   4
 The P45s                 4   0   3   1   1   2 ¯1   3

One more thing…

Finally, we need to publish the results so that the teams can see how they did. We can easily export the League Table to HTML format for inclusion in a web page:

League ⎕EXPORT 'C:\Users\Simon\Documents\LeagueTable.html' 'html'

Here is the HTML file opened in a web browser. Although you cannot tell from the screenshot, APLX has produced a properly formatted HTML table, not just a simple text representation.

screen shot League Table

In conclusion

A recent article about objects in the C# programming language included the sentence: “Unfortunately, though, objects are like snowmen; they live happily for a brief period of time before disappearing into the spring sunshine”. In APLX, this is by no means the case. APL objects are persistent; they can be saved in a workspace and even survive and adapt as you modify and extend your classes.

script began 11:47:45
caching off
debug mode off
cache time 3600 sec
indmtime not found in cache
cached index is fresh
recompiling index.xml
index compiled in 0.1863 secs
read index
read issues/index.xml
identified 26 volumes, 101 issues
array (
  'id' => '10012010',
)
regenerated static HTML
article source is 'HTML'
source file encoding is 'UTF-8'
URL: mailto:microapl@microapl.co.uk => mailto:microapl@microapl.co.uk
URL: marsden/leaguetable.jpg => trad/v234/marsden/LeagueTable.jpg
completed in 0.2095 secs