Serious First Steps In UserTalk Scripting
by Matt Neuburg
Author of the book Frontier: The Definitive Guide

Prev | TOC | Next


Returns, Addresses, and Dereferencing

Getting results

A feature of UserTalk that we have not taken advantage of so far is that every verb call returns a result of some sort to the script that calls it.

The way this happens is that every verb call is given a value; that is to say, the actual command to call the verb is assigned a value after the verb finishes executing. The reason we haven't noticed this up to now is that we have been ignoring the value of the verb call.

For example, in workspace.counterCaller, we call counter() by saying:

workspace.counter ( 10, 7 )

A line like this first calls the verb workspace.counter(), performing all the actions it says to perform, and then receives a value returned from workspace.counter() and substitutes it for the call here in workspace.counterCaller.

But we are not "capturing" that value, so we never find out what it is.

To find out, we must use the value in some way; for instance, we might assign it to a variable:

theAnswer = workspace.counter ( 10, 7 )

This time, after workspace.counter() has finished executing, the value it returns will be assigned to the variable "theAnswer".

But we still don't know what that value is. To find out what value was assigned to theAnswer, let's show it in a dialog, like this:

theAnswer = workspace.counter ( 10, 7 )
dialog.notify(theAnswer)

Press the Run button to run this script. The About Window shows counter counting down from 10 to 7; then a dialog says "7". That's the variable "theAnswer" being displayed by workspace.counterCaller; "7" is the result of calling workspace.counter().

But how did this happen? We wrote workspace.counter ourselves, and nowhere did we say anything about counter()'s returning a value "7" to the caller. So why did it do this?

The answer is that while we are always free, when we write a verb, to say what value it should return to the caller, yet if we fail to specify such a value, it will return something anyway.

Sometimes this will be "true". Sometimes it will be the value of the last assignment. Here, it's the last assignment made by the for-loop to the counting variable "n".

The return keyword

If the returned value of a handler is going to be important, it's a mistake to rely on the default behavior. Instead, we should explicitly specify the value that the handler will return to the caller, using the "return" keyword.

Actually, "return" has two purposes. One is to specify a returned value; the other is simply to terminate execution of a handler: the moment a "return" statement is encountered, a handler quits everything it is doing, returning the specified value, if any, and control of the action, to the caller.

To see "return" in action, modify workspace.counter to look like this:

on counter (lowerLimit = 1, upperLimit = 10)
    if lowerLimit < upperLimit
        for n = lowerLimit to upperLimit
            msg (n)
            clock.waitseconds (1)
    else
        for n = lowerLimit downto upperLimit
            msg (n)
            clock.waitseconds (1)
    return "I am good at counting!"
if dialog.getInt("Count from?", @fromWhat) \
and dialog.getInt("Count to?", @toWhat)
    window.about()
    counter(fromWhat, toWhat)

and press the Run button.

Our dialogs appear, and counter() counts, but nothing is done with the phrase "I am good at counting!", because when we called counter() in the last line we failed to capture the resulting value of the call.

Now push the Compile button, go back to workspace.counterCaller, and push the Run button.

This time, after the counting is done, when workspace.counterCaller displays the captured result of the call to workspace.counter(), it is "I am good at counting!"

Procedures and functions

This feature of UserTalk means that, in broad terms, there are two different ways to use a verb call. One is to use the call "procedurally" -- to cause actions to be performed. The other is to use the call "functionally" -- to get a result.

For instance, the main reason for calling window.about() is to open and bring forward the About Window; that's an action, so our call is procedural.

The main reason for calling trigCmd.sqrt(), on the other hand, is to get the result, which is the square root of whatever parameter we pass when we make the call.

(Try it! Choose Main: Quick Script, and type

trigCmd.sqrt(81)

into the Quick Script window, and hit the Run button.)

But there is no reason not to do both, and verbs do often both perform an action and return a significant result.

An example of a verb that does both is dialog.getInt(), which we used in our testing stub for workspace.counter.

Its main purpose is to perform an action, namely, to put up a dialog; but it also returns a meaningful result, namely "true" or "false" depending whether the user clicked OK or Cancel to dismiss the dialog. This is why we wrote:

if dialog.getInt("Count from?", @fromWhat)

If the user clicked Cancel in the dialog, the call to dialog.getInt() would evaluate to "false", so the bundle subordinate to the "if", bring the About Window to the front and calling workspace.counter(), would (quite properly) never take place.

But dialog.getInt() also did a third thing. Recall that after the call to dialog.getInt(), we proceeded to use the variable "fromWhat", feeding it as a parameter to workspace.counter(). How and when did "fromWhat" take on a value? And why did we precede its name with the "@" character in the call to dialog.getInt()?

Addresses

The "@" character, preceding the name of a variable or database entry, yields the address of that variable or database entry.

There are several reasons why we might need to take an address; the way we use addresses here exemplifies one of the most important.

The verb dialog.getInt() needs to know two things: a string to use as the prompt when the dialog is put up, and a variable into which to put what the user typed in the dialog's edit box. In other words, dialog.getInt()'s second parameter is something that dialog.getInt() is to change, a way for it to pass back a result.

But why not pass back the result in the normal way, by using a "return" statement? Because dialog.getInt() is already using that method to return information about whether the user clicked OK or Cancel -- remember?

In other words, dialog.getInt() needs to return two results. It returns one of them, the button clicked, as the value of the call itself, but the other it returns by putting it into a variable belonging to the calling script.

Now, when a variable is passed as a parameter in a verb call, it is not the variable itself which is passed, but a copy of its value -- variables are "passed by value" in UserTalk.

This simplifies the UserTalk language, and makes it safer, because it means that a verb can't have any effect on variables belonging to the script that calls the verb.

But in this case, we want dialog.getInt() to be able to change a variable in the calling script. So instead of passing a variable, we pass the address of that variable; the address information lets dialog.getInt() reach the actual variable and set it, even though it belongs to the calling script.

Dereferencing addresses

How does dialog.getInt() do this? It does it by "dereferencing" the address.

To see how this works, let's write a pair of scripts that do the same sort of thing. Call the first one workspace.theCaller, and have it go like this:

myVariable = 6
workspace.setMyVariable(@myVariable, 200)
msg(myVariable)

Call the second script workspace.setMyVariable, and have it go like this:

on setMyVariable(theAddress, what)
    theAddress^ = what

Make sure you push the Compile button on workspace.setMyVariable; then go back to workspace.theCaller, and run it.

The About Window, displaying the value of "myVariable", shows 200, not 6, thus proving that setMyVariable() was able to change "myVariable" even though "myVariable" belongs to workspace.theCaller.

It was able to do this because we handed setMyVariable() the address of "myVariable".

Let's look at the process from workspace.theCaller's point of view. We don't want to say:

workspace.setMyVariable(myVariable, 200)

That would cause the value of myVariable, which is 6, to be passed as the first parameter to workspace.setMyVariable(). But that isn't what we want to do! We don't want to pass a 6; we want to pass the variable, itself.

Taking the address gives us a way to do exactly that.

Now let's look at the process from setMyVariable()'s point of view. What comes in as the first parameter is the address of the variable that setMyVariable() is to set. In order to set the variable, setMyVariable() needs to speak of the variable itself, and not just of the address it was handed.

To do this, it dereferences the address, using the dereference operator, "^", which comes after the name of the address variable that is to be dereferenced. The expression "theAddress^" means, in effect, "the thing whose address theAddress is". That's a hard concept to grasp, though, so I've come up with a little mantra that makes it clearer: Dereferencing an address is the same as using the name.

In this case, theAddress has the value @myVariable. So whenever we say "theAddress^" it's just as if we'd said "myVariable". The last line of workspace.setMyVariable:

    theAddress^ = what

is therefore effectively like saying:

    myVariable = 200

Except that we wouldn't have been able to say that, because there is no "myVariable" in workspace.setMyVariable's world. That's why we passed it an address -- so that we can effectively say it.

The power of passing addresses

Clearly, handing a verb an address as a parameter gives it extra power to change things that are not normally its prerogative to change.

Such power can be dangerous! You should always be very, very careful when you hand a parameter to a verb that expects an address as that parameter.

To see why, imagine that you made the following mistake in writing workspace.theCaller:

myVariable = "system"
workspace.setMyVariable(myVariable, 200)
msg(myVariable)

Do you see the mistake?

You've left off the "@" before "myVariable" as you hand it to setMyVariable(). So what will be handed to setMyVariable() as its first parameter will be the string "system", because that's the value of "myVariable".

Then, setMyVariable will try to dereference this. Well, you can't dereference a string, so Frontier treats the string, "system", as an address, @system. (That's called implicit coercion, and is an important UserTalk feature; we'll talk more about it later.)

Okay, but dereferencing an address is the same as using the name, so now workspace.setMyVariable() is saying, in effect:

    system = 200

But in Frontier's mind, the name "system" means root.system, a crucial part of the database! We're about to take the whole inner workings of Frontier and put the number 200 in its place!

Luckily, the "=" operator is not powerful enough to overwrite a whole table like this. If you run workspace.theCaller, you get an error message that saves you from yourself:

Assignment over existing table object "system" is not allowed.
Delete the object first, or use table.assign to override protection.

Wow, that was close! And it could have been much, much worse. There are UserTalk verbs that expect an address as a parameter and do have the power to overwrite or delete a whole table.

And if root.system had been a non-scalar, it would have been over-written. For instance, suppose workspace.theCaller goes like this:

myVariable = "cr"
workspace.setMyVariable(myVariable, 200)
msg(myVariable)

Don't run that program! If you do, you will overwrite the value of an important UserTalk constant, "cr" (whose value is a return-character) -- and scripts which use that constant, of which there are many in the database, will break from then on.

And all because you left out the "@" character.

The moral is: be extra careful when dealing with addresses. It helps to study some existing code. It's also a good idea to practice without saving the root, to make very sure your script does what you expect.


Prev | TOC | Next

All text is by Matt Neuburg, phd, matt@tidbits.com.
For information about the book Frontier: The Definitive Guide, see my home page:
http://www.tidbits.com/matt
All text copyright Matt Neuburg, 1997 and 1998. ALL RIGHTS RESERVED.
No one else has any right to copy or reproduce in any form, including electronic. You may download this material but you may not post it for others to see or distribute it to others without explicit permission from the author.
Downloadable versions at http://www.ojai.net/matt/downloads/scriptingTutorial.hqx and http://www.ojai.net/matt/downloads/scriptingTutorial.zip.
Please do not confuse this tutorial with a certain other Frontier 5 tutorial based upon my earlier work.
This page created with Frontier, 2/11/2000; 6:59:02 PM.