Getting results
A feature of Frontier 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)
This expression has a value after it gets through calling Counter; 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)
A line like this first calls the verb workspace.Counter, performing all the actions it says to perform, and then receives the value returned from Counter and substitutes it for the call.
In this case, this means that, after workspace.Counter has finished executing, the value it returns will be assigned to theAnswer.
To find out what value was assigned to theAnswer, let's show it in the About Window, like this:
theAnswer = workspace.Counter (10, 7)
msg (theAnswer)
Press the Run button to run this script. The Main Window shows Counter counting down from 10 to 7; but then it changes to read "true". That's the variable "theAnswer" being displayed by CounterCaller; "true" is the result of calling Counter.
But how did this happen? We wrote Counter ourselves, and nowhere did we say anything about its returning a value "true" to the caller. So why did it do this?
The return keyword
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 either "true" or "false" depending on whether everything went okay during the call.
(Things would be considered not to have gone okay if the verb generated an error.)
To specify a value that a verb should return to the caller, we use 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 go back to workspace.CounterCaller and push the Run button.
Once more, after Counter gets through counting, CounterCaller displays in the Main Window the result of the call to Counter, which it has captured; this time, it's "I am good at counting!"
Procedures and functions
This feature of Frontier 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 bring the About Window to the front; that's an action, so our call is procedural.
Suppose we wanted a verb that returned the square of a number. We'd make a script, perhaps at workspace.squareIt, that reads:
on squareIt (numToSquare)
return (numToSquare * numToSquare)
The main reason for calling the above script is to get the result, which is the square of whatever parameter we pass when we make the call.
(Try it! Create the above verb. Then open your Quick Script window from the Main menu, and type
workspace.squareIt (9)
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 Counter.
Its main purpose is to perform an action, namely, to put up a dialog; but it also returns a result, which is "true" or "false" depending whether the user clicks 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, dialog.getInt would evaluate to "false," so the stuff inside the "if," the bringing to the front of the Main Window and the actual call to 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 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!
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 -- variables are "passed by value" in Frontier.
This simplifies the UserTalk language, and makes it safer, because it means that a verb can't have any effect on variables belong 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, even though it belongs to the calling script, and set it.
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, 7)
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 Main Window, displaying the value of "myVariable", shows 7, not 6, thus proving that setMyVariable was able to change "myVariable" even though "myVariable" belongs to theCaller.
It was able to do this because we handed setMyVariable the address of "myVariable".
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 be able 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 that is to be dereferenced. The expression "theAddress^" means, in effect, "the thing whose address theAddress is."
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 theCaller (do not run this script!):
myVariable = "system"
workspace.setMyVariable (myVariable, 7)
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 dereference this, meaning it will proceed to change the value of whatever is living at Frontier's address "system."
But in Frontier's mind, that's root.system, a crucial part of the database! We're about to take the whole inner workings of Frontier and put a 7 in its place!
Luckily, the "=" operator is not powerful enough to overwrite a whole table like this; but there do exist verbs which are!
The moral is: be extra careful when dealing with addresses. It helps to study some existing code, or practice without saving the root to make very sure your script does what you expect.
|