This page uses CSS, and renders properly in the latest versions of Microsoft Internet Explorer and Mozilla.
This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.
TOP UP: UserTalk Basics NEXT: Special Evaluation

8

Addresses

A UserTalk object, as we know, is a variable, a handler, or a database entry. You refer to an object with an object reference: in effect, you use the object's name. But there are times when you might like to do things with object references other than use them. For instance, it would be nice to be able to make an object reference be the value of a variable.

Imagine, for instance, a handler to create, in any table in the database, an entry called x and assign it the value 3. Here is some pseudo-code that expresses what the handler does:

on makeX (theTable) « this is fake UserTalk, do not imitate
    theTable.x = 3

But what sort of variable can theTable be? We don't want it to be an actual table; that would mean we're making an entry x in a table which lives in a local variable - not in the database. And how can we call makeX()? Suppose we want makeX() to operate upon the workspace table. We cannot say makeX(workspace), since this just copies the whole contents of the workspace table into the parameter. What we want to do is somehow package up the object reference workspace, hand that package to makeX(), and have makeX() open the package and find the object reference there, so that it knows what table to operate on.

UserTalk provides a way to make such a package: it's called an address. This chapter explains the syntax of addresses and discusses their primary uses - and some of their dangers.

Address Syntax

The address of an object is a pointer to that object. It's obtained and signified by preceding the object's name with @ . For example, workspace.notepad names an object in the database; @workspace.notepad is the address of that object. We say that the @ operator takes the address of the object whose name it is applied to.

It is a runtime error to try to take the address of an object if Frontier cannot possibly resolve the reference to that object. For example, workspace is a valid object reference, because Frontier can resolve it to root.workspace; so @workspace is legal. And workspace.zzz is a valid object reference, even though workspace.zzz does not exist, because Frontier could resolve it if you were to say this:

workspace.zzz = "hello"

So @workspace.zzz is legal. But if workspace.zzz does not exist, then workspace.zzz.yyy cannot possibly be resolved; so saying @workspace.zzz.yyy generates a runtime error.

An address becomes really useful only when it is dereferenced . An address is dereferenced by following a reference to it with ^ . For example, suppose myAddress is a variable whose value is the address @workspace.notepad. Saying myAddress^ then dereferences that address.

A dereferenced address is a reference to the object pointed to by that address. In essence, taking an object's address packages up a reference to that object; dereferencing the address opens the package, and there's the object reference, ready to be used. In other words, dereferencing an address is the same as using the name.1

For example, suppose you have made the following declarations and assignments:

local (s = "Hello")
on myHi ()
    dialog.notify ("Hi!")
local
    addrWorkspace = @workspace
    addrS = @s
    addrHi = @myHi

Then you can say addrWorkspace^ anywhere you would use the name workspace; you can say addrS^ anywhere you would use the name s; and you can say addrHi^ anywhere you would use the name myHi. For example, you can say:

addrWorkspace^.x = 7
    « like saying, workspace.x = 7
addrS^ = addrS^ + ", World!"
    « like saying, s = s + ", World!"
addrHi^()
    « like saying, myHi()

You can't use parentheses to modify the application of @ or ^. Only one @ can appear in connection with a name; it must precede the entire name, and it takes the address of the object referred to by the entire name. On the other hand, ^ can appear after any element; it dereferences the value of the object referred to by as much of the name as precedes it. If more than one ^ is applied to a name, they are evaluated from left to right. If both @ and ^ are applied to a name, ^ is evaluated first. So:

workspace.anAddr = @blue
workspace.anAddr^
    « "0000FF", because it's like saying blue
local
    addrWorkspace = @workspace
addrWorkspace^.anAddr^
    « "0000FF", because it's like saying workspace.anAddr^
addrWorkspace.anAddr^
    « error! causes Frontier to look for a table "addrWorkspace"
@workspace.anAddr^
    « @system.verbs.colors.blue
@addrWorkspace^.anAddr^
    « @system.verbs.colors.blue

The dereference operator can also be used to convert a string to an object reference; dereferencing a string is the same as using the name. So, for instance:

stringWorkspace = "workspace"
stringWorkspace^.x = 7
    « like saying, workspace.x = 7

This actually involves implicit coercion of the string to an address (see Chapter 10, Datatypes ); dereferencing "workspace" turns it into @workspace first. Therefore, dereferencing a string causes a runtime error if the resulting address is illegal, as described previously.

Double-dereferencing (or more) is perfectly possible. Watch closely:

workspace.anAddr = @workspace.y
workspace.x = "workspace.anAddr"
workspace.x^^ = 8
    « like saying, workspace.y = 8

More exotic combinations are possible as well. For example, the verb window.frontmost() reports, as a string, the name of the object whose edit window is frontmost. The script for that verb lives in the database at window.frontmost (of course). Now suppose workspace's edit window is frontmost. Then:

myString = "window.frontmost"
myString^()^.x = 7
    « like saying, workspace.x = 7

Believe it or not, I have had occasion to phrase things this way in my code.

Name Construction

A frequent use of addresses is to help with the construction of object references. For example, suppose there is a table at workspace.myTable.mySubtable and we wish to make several entries in it. To reduce clutter and the likelihood of a typing error, we might say:

theTable = @workspace.myTable.mySubtable
theTable^.x = "hey"
theTable^.y = "ho"
theTable^.z = "hey nonny no"

We now also see how to write our handler makeX():

on makeX(theTable)
    theTable^.x = 3

And to call it so that it operates on the workspace table, we would say makeX(@workspace).

Similarly, a frequent technique for calculating an object reference at runtime is to store or construct a string, and then dereference it to use the name; this complements the square-bracket notation we studied in Chapter 6, Referring to Database Entries . So, for example:

theTable = "workspace"; theEntry = "x"
s = theTable + "." + theEntry
    « now s contains "workspace.x"
s^ = 3
    « like saying, workspace.x = 3
    « in this case we could have said [theTable].[theEntry] = 3 instead

Address Parameters

Suppose you need a verb to return more than one result value. Suppose, for example, we have a script at workspace.theAngles which calculates the three angles of a triangle given the three sides. A call to workspace.theAngles() can have only one value; how can three values be returned?

The solution is to pass as parameters to the verb the addresses of variables in which the verb is to store its results.2 Such parameters are called address parameters. For example, we might define theAngles() like this.

Example 8-1 Handler accepting addresses to return multiple results
on theAngles (angle1, angle2, angle3, addrSide1, addrSide2, addrSide3)
    « ... do the calculation based on the three angles ...
    addrSide1^ = ...; addrSide2^ = ...; addrSide3^ = ...

And we might call it like this.

Example 8-2 Calling such a handler
local (angle1, angle2, angle3, side1, side2, side3)
« ... initialize the angles ...
workspace.theAngles (angle1, angle2, angle3, @side1, @side2, @side3)

Let's review Example 8-1 and Example 8-2 in detail. The caller uses the @ operator to obtain the addresses of its local variables side1, side2, and side3; it passes those addresses to theAngles(), which uses the ^ operator to dereference the parameter variables holding the addresses, and assigns values to those dereferenced variables. This means that theAngles() has assigned values to the caller's local variables side1, side2, and side3!

The results of the calculation are sitting in the caller's side1, side2, and side3, even before theAngles() has finished executing. There is no need for theAngles() to return any other information, so theAngles() ends without a return statement. By the same token, the caller has no need to capture the value of the call to workspace.theAngles(); after the call, the variables side1, side2, and side3 have been set with the information the caller desired.

I have already said (Chapter 5, Handlers and Parameters ) that in UserTalk, parameters are passed by value, and so any changes that a verb makes to its parameter variables have no effect within the caller. Now we see how to circumvent this restriction. An address parameter gives a verb the ability to meddle with what the address points to.3

As a matter of style, the programmer has chosen, for the three address parameter variables of theAngles(), names beginning with addr. This has no special meaning to the computer; it is simply a reminder to readers of the code. Such a coding practice is strongly recommended, especially because (as we shall see) misuse of addresses can lead to disastrous results.

We can now understand these lines from Example 5-3:

local (theDay, theMonth, theYear, theHour, theMin, theSec)
date.get (clock.now(), @theDay, @theMonth, \
    @theYear, @theHour, @theMin, @theSec)

The built-in verb date.get() analyzes a date into six values representing its day, month, year, and so on. It takes seven parameters, but only the first contains information the caller is asking to have analyzed; the other six are addresses of places to store the six results. In this case, we want to know the hour that it is right now; so the first parameter is the result of a call to clock.now(), which gets the current date-time value from the computer's internal clock. After the call to date.get(), the results are sitting in our variables theDay, theMonth, and so on.

Actually, only theHour is of interest to us, but we cannot call date.get() without handing it the required number of parameters, and since these are to be addresses, we should hand date.get() actual addresses of existing objects; hence we create five "dummy" variables whose values we don't care about. In reality, though, we need not have created five different dummy variables; we might have said:

local (theHour, dummy)
date.get (clock.now(), @dummy, @dummy, @dummy, \
    @theHour, @dummy, @dummy)

The one dummy variable receives each value in turn, changing five times as date.get() executes; it has then served its purpose.

Not infrequently, a handler will return information both by way of address parameters and as its result. That's how many of the dialog verbs work, for example. Recall Example 4-6:

local (theReply)
theReply = user.name
if not dialog.ask("What is your name?", @theReply)
    return
dialog.notify ("Hello, " + theReply + "!")

Dialog.ask() puts up a dialog containing a user-editable field, and then returns true if the user clicked OK and false if the user clicked Cancel. If the user did click Cancel, we want to abort the whole routine; so we test to see if the result is false and exit if so. But dialog.ask() also takes as its second parameter the address of a variable to which it returns the text that the user entered into the edit field; so after dialog.ask() has been called, we have a second piece of returned information sitting in theReply.4

Further Uses of Address Parameters

Handing a verb an address parameter can eliminate the overhead involved in passing a large value. Suppose, for instance, we want our verb to extract information from a string. Handing a verb a string value as a parameter copies the whole string, which may be very large; handing it an address copies only the address, which is tiny.5 This is comparable to having a local handler operate upon a variable global to itself.

Another reason for handing a verb an address parameter is that the verb is to operate upon the "thing itself," not its value. A verb that deletes an object from the database, for example, doesn't want to know that the value of that object is "Hello"; it wants to know what object it is. If the object is at workspace.myString, saying workspace.myString as the parameter would pass "Hello"; it is @workspace.myString that should be the parameter. UserTalk has a great many verbs that operate upon objects qua objects, and they generally take address parameters.

Addresses of Verbs

The use of an address parameter pointing to a handler or script object allows a script to call a verb which is not specified until runtime. This increases the generality and value of the script.

Suppose, for instance, we have a script that is to help us make backups of recent files. It looks at all the files in a given folder, and in each case, adds the name of the file to a list, if the file is more recent than a certain date and time. Without worrying about the details, the structure of our script might look like this:

on listIfRecent()
    « ... some sort of loop to examine each file ...
        if file.modified(thisFile) > lastBackupDate
            « then add thisFile to the list

It then occurs to us that we might have many occasions for making selective lists of files in folders. We observe that at present we have a boolean test in the if line, which returns true or false for the particular file we are looking at. It seems wasteful to write the same routine over and over, differing only in the contents of this one line. If only we could specify at runtime what the test should be! By passing a verb's address as a parameter, we can do just that.

Example 8-3 Handler accepting a handler address
on listIfWhatever(addrTester)
    « ... some sort of loop to examine each file ...
        if addrTester^(thisFile)
            « then add thisFile to the list

To call listIfWhatever(), we hand it, as a parameter, the address of a handler or script. That handler or script must obviously be expecting one parameter, namely a file pathname. And it must return true or false reporting whether that file meets some criterion or not. Then listIfWhatever() calls that handler or script to perform the actual test.

For example, to call listIfWhatever() to the same effect as listIfRecent(), we could say the following.

Example 8-4 Telling a handler what handler to call
on isRecent(aFile)
    return (file.modified(aFile) > lastBackupDate)
workspace.listIfWhatever(@isRecent)

But now it would be just as easy to use listIfWhatever() to generate a list of files that are textfiles, or files that are larger than a certain size.

For a further example of this technique, see samples.basicStuff.aliasGatherer, and the way it is called by samples.basicStuff.appAliases. It is also used by most of the visit verbs; see Chapter 12, Control Structures .

Being Careful with Address Parameters

An all-too-easy coding accident is to hand something that is not an address as a parameter to a verb that expects an address. Typically, this is due to forgetting to put @ before a name. Sometimes this mistake will just cause a runtime error, but it can be a recipe for disaster; it can harm the database, and the trouble that results can be difficult to track down.

The danger often comes from the fact that it is possible to dereference a string. Suppose, for instance, one were to say the following.

Example 8-5 Bad way to call dialog.ask( )
local (theReply)
theReply = "Last"
if not dialog.ask("What is your last name?", theReply) « no no NO!!!
    return

The dialog appears, the user types something - let's say Neuburg - and hits the OK button. Now, dialog.ask() was expecting the second parameter to be an address. It dereferences that address so that it can store, in the object pointed to, whatever the user typed in the dialog. If the program's third line had been correct, like this:

if not dialog.ask("What is your last name?", @theReply)

then "Neuburg" would have ended up stored in theReply. But as it is, Frontier will dereference the string "Last", yielding an object reference, last: Frontier is going to try to store "Neuburg" in last. Unfortunately, this is possible: last resolves to system.macintosh.constants.last, and "Neuburg" is therefore stored there - replacing the value of an important constant. To top it all off, it could be a long time before the damage manifests itself in misbehavior; and even a diligent user who deduces what has happened may not know what value last should have, and may need to revert to a backup or a clean root.

Now let's turn up the heat. Consider the verb new(). It takes an address and creates a new variable or database entry there - overwriting anything that may be there already. It can create an object of any type. It is, for example, often used to create a new outline in the database. The following command creates a new outline at workspace.s, wiping out whatever may be at workspace.s already:

new (outlineType, @workspace.s)

Suppose the programmer forgets the @, and types the following instead.

Example 8-6 A really, really bad way to call new( )
new (outlineType, workspace.s)

And suppose workspace.s exists, and that it is a string. And suppose that string is "system". When new() dereferences "system" to get an object reference, it's going to be talking about root.system, where the heart of Frontier lives: constants, verbs, extensions, the UserTalk compiler, everything. Now Frontier is going to put an outline at root.system ! Executing this line will replace the whole inner workings of Frontier with an empty outline!

These examples are not farfetched. They should suffice to instill caution when using addresses. You cannot avoid addresses, nor should you; they are tremendously valuable, and a clear understanding of them is part of any good UserTalk programmer's repertory. Still, you should be careful.


1. This mantra is the UserTalk equivalent of "Port is left, starboard is right," or "Buy low, sell high." Repeating it several times into the mirror each morning will, in short order, make a beginning UserTalk programmer into an expert.

2. There is another solution, using the list datatype. A list, signified by curly braces, can consist of several items, yet it is only one value. (See Chapter 11, Arrays .) So:

on theAngles (angle1, angle2, angle3)
    local (side1, side2, side3)
    « ... do the calculation, obtaining side1, side2, and side3 ...
    return {side1, side2, side3}

theAngles() returns a list consisting of the values of the three sides. However, there is a certain inconvenience to this approach; after a script calls workspace.theAngles(), it must parse the resulting list to get at its contents. In any case the method using addresses goes back to a time before Frontier had list datatypes, and remains very prevalent.

3. Of course, the address itself is passed by value; but the new copy of the address points to the same object as the original address did.

4. It happens that dialog.ask() also uses the value in the variable whose address it is handed in its second parameter, if that variable has a value, as the default text appearing in the dialog's edit field. That's why we initialize theReply with a reasonable default value before calling dialog.ask().

5. Nevertheless, most of UserTalk's built-in string verbs take strings, not addresses of string variables, as their parameters.


TOP UP: UserTalk Basics NEXT: Special Evaluation

This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.