Roll Your Own
Getting the List of It
I hope you haven’t missed me too much, and I apologize for taking such an extended hiatus over the last couple of months. Life has been busy lately, but I’m glad to be back so that we can continue on our journey into the wonders of building software.
In my last column I signed off by saying that we would begin this column with a series of tutorials that will actually build a complete application. However, I’m going to put that column on hold for a little while so that we can delve deeper into the mechanics of creating software.
Software writing is the act of translation. You have in mind what you want the computer to do, and you may even be able to describe it rather completely in English, but to get the computer to actually do the job, you need to translate this idea into a language the computer understands. Of course, the language of this series is AppleScript, so for the next few columns we’ll begin investigating this process in more detail by building an object that may come in handy for later projects.
This time we’re going to build a list object. AppleScript does include a list data type, and it will be the basis for our list object, but AppleScript’s list has some serious limitations. There’s no built-in method for sorting a list, and inserting an item into the list at some arbitrary point is by no means trivial.
Performing these operations on lists is common enough that building a list object that includes these and other methods will probably be useful later. Even if you never actually use the list object in other programs, the process of building the object will prove instructive.
First of all, let’s define what we would want a list object to be able to do. As mentioned, it should be able to sort itself, and be able to insert an item anywhere in the list. We will also write our list object so that it can return how many members it has, return how it is sorted (ascending, descending, or none), return a member of the list by providing either an index (i.e., the fifth item in the list) or return the index given a member. Our list object will be able to remove duplicate members and will be able to let us know if it contains a particular member or how many times a particular member occurs in the list. We’ll provide the ability to remove an item by the index of the item or by naming the item. Finally, we’ll be able to get the list itself or reset the list to a different list entirely.
As mentioned in the prior columns on object creation, we’ll enclose the list object in a handler that will create an instance of the object. All methods that modify a list will actually return a new list object rather than actually change the contents of the list object. The only exception to this is the method for setting the list, which will actually reset the contents of the list.
Our final list will contain two properties. The first, and most obvious, is the list itself. But we’ll also include a property that stores the current sort order of the list. This will make it much easier for us to determine how the list is sorted when calling code requests it. Without this property we’d have to actually create a sorted version of the list and compare it to the actual list to see what the sort order is. In keeping with the encapsulation ideal of object oriented programming, we’ll provide methods for accessing both of the properties.
The version of the list object that we’ll build in this column will assume that all the members of the list are either text or can be coerced to text. This is important for sorting, but in future columns we’ll expand our list object so that other members can be sorted as well.
That’s a lot of functionality for an object, and we won’t get to all of it this month, but it is the complexity of the list object that makes it desirable to write. It will be easier to create this list object and use it in other software than to recreate these handlers each time we need them.
We’ll build our list object from the outside in, beginning with a very basic list object that we’ll add functionality to as we go. Here’s the simplest version of our list object.
script ListObject property theList : {} end script
This is a pretty useless object. It doesn’t give us anything that a normal list wouldn’t. The first functionality we’ll add to this object is to enclose it within an initialization handler.
Before we create such a handler, let’s think through how this will work. The MakeList()
handler will accept a list to assign to the theList
property. Such a parameter should, of course, be an AppleScript list, but we can, if we want, accept other data types. For instance, if a string is passed, we can coerce that to a list with a single item, so that "some text"
would become {"some text"}
. We can operate similarly if the handler is passed a real or integer.
Records are very similar to lists, in that they also store a series of values. Records can also be coerced into lists. For instance, {itemOne:1, itemTwo:"two"} as list
evaluates to {1, "two"}
.
Although we could have the MakeList()
handler perform the chores of coercing these values into a list, such functionality may be useful for the object itself to have, so we’ll create a new handler within the ListObject
that does this called Normalize()
.
Our assumption with the current version of this object is that the list will contain only strings, so after coercing our parameter into a list, we’ll attempt to coerce each of the members of the list into a string. If this fails, we’ll generate a custom error.
What if some other error occurs? Then we should pass this onto the calling code. Therefore, within the on error
handler, capture the error message and error number in variables. If the error number indicates that something couldn’t be coerced into a string, then we raise a custom error. Otherwise, we pass the error given using the errMsg
and errNum
variables.
Since the ListObject
definition will exist within the MakeList()
handler, and MakeList()
will accept a list (or something coercable into a list), we can initialize the theList
property to the parameter passed rather than an empty AppleScript list.
Now that we’ve clearly defined, in the English language, what our next version of MakeList()
and ListObject
are going to do, we need to translate those definitions into the AppleScript language. Here’s one example of how this might be done.
on MakeList(newList) script ListObject property theList : newList to Normalize() if class of theList is record then set theList to theList as list end if try repeat with i from 1 to count of theList set item i of theList to ¬ ((item i of theList) as string) end repeat on error errMsg number errNum if errNum is -1700 then error "List cannot be normalized to list" &¬ " of strings." number 501 else error errMsg number errNum end if end try end Normalize end script tell ListObject to Normalize() return ListObject end MakeList
Notice that I said this is “one example,” rather than “the one and only way.” In programming, there are always multiple ways to do things. If nothing else, we could have altered the order in which our nested if
s were placed. Rather than generating an error when a member of the list can’t be coerced into a string, we could skip it instead. The point is, think through what you want to do, and then you’ll more easily be able to translate the task into AppleScript or whatever language you move onto later.
Our MakeList()
handler, excluding the script object, will now remain static for the rest of this column. The remainder of our edits to the handler will be additional methods (i.e., handlers) within the script object, so the future listings will display only the handlers we’re working with. Simply insert these handlers within the script object.
The current version gets us a bit further, but we still don’t have all the functionality that a normal list provides. In fact, since we want to keep the encapsulation ideal, we have less. AppleScript lists allow changes to a stored list, so the first method we’ll add will be a SetList()
method for resetting the contents of the list. Getting the list is simply a matter of referring to it, so we’ll add a couple of methods for these two functions.
SetList()
will be sent a parameter: the list to set our list object to. We could easily implement this SetList()
handler by simply setting the theList
property to the parameter passed, and then use the Normalize()
handler to make sure it is in the form of a list and that the items in the list are strings. Here’s how this might look.
to SetList given listToSet:newList set theList to newList Normalize() end SetList
What will happen if the newList
parameter isn’t valid? Normalize()
will generate an error, and the theList
property will be in an intermediate state because it was never allowed to finish the job.
What should we do about this possible problem? If it occurs, we should restore the theList
property to its original state. To do this, we need to first store it in a temporary variable, perform the actions needed. If an error is generated, we can then restore the list to the state it was in when the handler began execution. Here’s the final version of SetList()
.
to SetList given listToSet:newList set tempList to theList try set theList to newList Normalize() on error errMsg number errNum set theList to tempList if errNum is 501 error "List cannot be normalized to list" & ¬ " of strings." number 501. else error errMsg number errNum end if end try end SetList
Notice that we generate the same error that would have been generated by Normalize()
if we hadn’t included the call to it within a try
block. The error that could be raised by Normalize()
is known to us, so we raise the exact same error that Normalize()
would have raised.
Notice that we’re using the labeled parameter version of AppleScript’s handler syntax. This is simply to provide additional experience with this syntax. We could just as easily have used SetList(newList)
. We’re using the given
version of the labeled parameter syntax because I find it much easier to read calls the handler when this is the version used.
Our GetList()
handler is much simpler. It isn’t provided any parameters, simply returning the AppleScript list stored in theList
. We don’t need to do any error checking, so the method contains a single line of code.
to GetList() return theList end GetList
AppleScript lists also have the ability to report how many members the list has. We need to provide this ability also, again with a simple handler that takes no parameters since the only data it needs is within the object itself.
to MemberCount() return count of theList end MemberCount
The final bit of built-in list behavior that we’ll duplicate is extracting an item given its index. As a reminder, in a list containing {1, "two", 3, "four", "five"}
, item 5
of the list is "four"
, while item -3
of the list is 3
. Our list object won’t be able to refer to the members of the list so simply, but we will be able to duplicate the functionality fairly easily.
Let’s write a first version and then consider what the consequences of the handler are.
on ItemX given theIndex:x return item x of theList end ItemX
This handler will do the job, but it takes a parameter and doesn’t error check the parameter. There are two possible errors. If ItemX()
is passed a value of 0
, then AppleScript will attempt to get the 0th item of the list, which isn’t possible. x
can be negative, so we don’t need to worry about that. However, what if the absolute value of x
is greater than the number of members in the list? Using our list in the paragraph above, which has five members, what if ItemX()
is passed a value of -9
or 7
? AppleScript will generate an error at this point as well.
We now have two choices. We can either leave the handler as it is and let AppleScript do the error checking for us, or we can check for the possible errors ourselves. In the interest of consistency, we’ll go ahead and check for the errors ourselves. We’re already checking for errors whenever a handler is passed a parameter, so let’s stick with that practice. Below is our new version of the handler.
on ItemX given theIndex:x if x is greater than MemberCount() or ¬ (x * -1) is greater than MemberCount() or ¬ x is 0 then error "Invalid index passed to ItemX()" number 502 else return item x of theList end if end ItemX
You may have noticed that we’re using MemberCount()
rather than count of theList
. In this case, the two are equivalent. However, using MemberCount()
has the advantage that if we ever decide to change how the internal list is stored, perhaps using a return-separated string property, then we can simply change how MemberCount()
works and then this handler will work without modification.
We’re going to stop here until next month, when we’ll continue modifying our list object. So far, all we’ve done is make more work for ourselves, since everything we’ve provided for our list object has been a duplication of the built-in functionality of an AppleScript list. However, I hope that working through the process of building the list object is educational for you, demonstrating the thought process that goes into building software.
Next month we’ll move beyond the AppleScript list, adding the ability for a list object to return a sorted version of itself, as well as get the index of an item given its contents. Until then, happy programming!
Also in This Series
- Getting the List of It—Part 2 · July 2003
- Getting the List of It · June 2003
- The Object of Programming—Part 2 · March 2003
- The Object of Programming · February 2003
- How to Handle Anything · January 2003
- Try to Handle This · December 2002
- Charting Your Success · September 2002
- Go with the Flow · August 2002
- Variables and Data · June 2002
- The Ultimate Customization · May 2002
- Complete Archive
Reader Comments (0)
Add A Comment