10.5. Lists: Organizing Repetitive Chores inside Objects

In the Hello World example, each instance of the Person class is aware of the pplList that exists in the PplModelSwarm. Inside the individual person, the name used to refer to that list is pplPresent. Because pplPresent refers to an object that conforms to the List protocol, and all List objects follow the Collections protocol, then a number of interesting features can be put to use.

Suppose the we want to have the person go through the list of people in the list and make a new list that includes all of the people in that list who have a lot of friends, say more than 3. In order to carry this out, code has to be designed to traverse through the pplPresent list, ask each one how many friend it has, and then if that person has more than 3, then add that person to another list.

One of the most interesting protocols in Swarm is the Index protocol. In mathematics, one might have seen a variable Xi, and the index variable i can range from 1 through the number of possible values. In Swarm, Index means much more than that. A Swarm Index is a "living, breathing" object that can be moved around in a list, and the Index can also respond to requests for information.

For new Swarm users, the most puzzling thing about the usage of Index is the creation process. Index objects are not created with the standard swarm createBegin/createEnd pair. Instead, any object from the Collections class, such as a List, can spawn an index by using the begin: method. In one step, the begin: method will create an object that conforms to the Index protocol and positions that index object before the first element of the collection. Here is an example of how the pplPresent list might create an index called pplIndex:

id <Index> pplIndex;
pplIndex = [ pplPresent begin: [self getZone]];
      

The first line declares the instance variable that will be the index. It is not necessary to include the protocol name <Index> in the declaration, so it might as well have been just id pplIndex. Some programmers prefer to include the extra information in the declaration because it clarifies the code and also may help to catch programming mistakes.

After it is created, the pplIndex can respond to messages. Many of the methods that Index objects can carry out will do two things at the same time: the Index will be positioned and the identity of the object at which the index currently resides will be returned. For example, consider this code that sets a variable called elementFromList equal to the next one, as provided by the index:

elementFromList = [pplIndex next];
      

When it is first created, the pplIndex is positioned at the edge of the collection, just before the first object in the collection. If we want the index to move to the next object, and give us a pointer to the next object in the list, it is done with that command. (As in C, collections are numbered beginning with the number zero).

It is common in Swarm examples to use the next method of the Index object in a while statement that cycles through the elements of a list. Here is a bit of code that would go through the list of people in Hello World and ask each one how many friends it has. And, if the number is larger than 3, then that object is added to a list popularPeople (which we assume is created somewhere else in the code).

id <Index> pplIndex = nil;
id         element  = nil;
int numberOfFriends;

pplIndex = [pplPresent begin: [self getZone]];
while ((element = [pplIndex next]) != nil) 
{
  numberOfFriends = [element getNumFriends];
  if (numberOfFriends > 3) 
    [popularPeople addLast: element];
}
[index drop];
      

This example uses a number of convenient features from the C language. One is that the conditions evaluated in logical statements are actually calculated. Hence, the conditional in the while statement causes the pplIndex to move to the next element, in the process setting the variable element equal to that object. As a result, inside the curly braces, the variable element can be used to refer to that particular element from the list. In this case, that object is asked to give us its number of friends.

The index object, pplIndex plays a vital role in this example. The index index is accessed inside the while statement so that we can cycle through the elements in a list. The while statement in the previous example will begin with the first element of the list, and one-by-one it will move through the pplList. What happens when it gets to the end? When it is positioned at the last element of the list, then the [pplIndex next] command will return nil. The logical condition is set so that the program exits the while loop at that point.

If one inspects a number of Swarm examples, one will find the while loop is constructed in slightly different ways, but the effect is the same. For example, the logical condition is sometimes written simply as ([pplIndex next]). This is allowed because of the convention that, as long as this does not return nil, then the while loop will continue. If that approach is used, instead of using element in the while loop, we replace all occurrences of element with [index get], like so:

id <Index> pplIndex    = nil;
int numberOfFriends;

pplIndex = [pplPresent begin: [self getZone]];
while (([pplIndex next])) 
{
  numberOfFriends = [[pplIndex get] getNumFriends];
  if (numberOfFriends > 3) 
    [popularPeople addLast: [pplIndex get]];
}
[pplIndex drop];
      

This last change would cause a performance penalty because the pplIndex object is asked to evaluate and return on object three times.

It is hard to overstate the value of the Index protocol in working with Swarm lists. One especially important feature of Index is that it can be used to manage items in the list itself. That is, the index can do more than just point to objects. If an index is positioned at an object, and one wants to cut that object from the list, then the command [pplIndex remove] will get the job done. The index will automatically reorient itself, so that the next time the index receives the next instruction, it will point to the next valid member of the list.

Caution

Watch out for nil objects when using "while" loops

If you loop through a list, checking only that the index is not positioned over a nil object, your loop might end before you expect if there is a nil object in your list.

A variable of type id might be unintialized, or nil. Suppose that, through intention or error, an object person1 has been set to nil, as in

   
      person1=nil;

This could happen if, for example, the object referred to by the name person1 has been dropped, and the user is careful to set the name equal to nil in order to be safe.

Now suppose the program adds person1 to a list, and other (nonnil) objects are added as well. If the program creates an index and tries to loop through this list with the while construction described above, there will be a major problem. The loop will be executed, until the index object arrives at person1. Since person1 is equal to nil, then [pplIndex next] will return nil, and the program will exit the while list and continue with the next commands. If there is a danger that some of the objects in a list might be nil, and the programmer wants the loop to continue after "skipping over" the nil objects, then the best approach is a for loop that takes advantage of some symbols defined in the Swarm libraries. For each object in a list, the Index protocol's method getLoc will tell us whether the index is positioned in the list on a "Member." If the [index next] message causes the index to "step off the last" object in a list, then the return from that message is "End."

{
id <Index> pplIndex = [pplPresent begin: aZone];
id member;

for (member = [pplIndex next]; [pplIndex getLoc] == Member; member = [pplIndex next])
   {
    // do something with member ...
    }
        [index drop];   
}

When it is created, the index is automatically positioned at the Start. The first argument in the for statement positions the index on the first member of the collection. The second argument says that the for loop continues as long as the returned value from getLoc is equal to the symbol Member. And after the loop is complete, the third argument says that the index is supposed to step to the next object in the list.

Caution

Be sure to drop index objects when you are finished with them

It is important to drop the instance of Index when its use is completed. That's accomplished by the [pplIndex drop]; command in the last example. If this is forgotten, the index will continue to occupy memory and waste resources.