TOP | UP: UserTalk Basics | NEXT: Referring to Database Entries |
We have seen that creating a script object creates a UserTalk verb. If a script object at workspace.sayHi consists of the script presented as Example 4-3, then it is possible to call it as a verb from any other script:
workspace.sayHi()
If you execute a script that contains this line, then at this point the script at workspace.sayHi will execute, much as it would if you opened workspace.sayHi's edit window and pressed its Run button.
But there is a complication. Scripts sometimes do not function correctly as verbs unless they contain an eponymous handler . And scripts called with a verb call do not always function the same way as when their Run button is pressed.
This chapter explains these important matters, and describes in detail the syntax of verb calls.
Let's examine this term one word at a time.
A handler is a verb definition: it tells the verb's name, how many parameters it expects, and what commands it consists of. Syntactically, a handler is a control structure introduced by the keyword on . This keyword is followed by the name of the verb that the handler defines, plus parentheses containing a comma-delimited list of variables to receive each of the verb's parameters (if the verb takes no parameters, the parentheses will be empty). The bundle subordinate to the on line is the sequence of commands to be executed when the verb is called.
For instance, here is our Example 4-3 routine rewritten as a handler taking no parameters.
An eponymous handler is a handler whose on line is at summit level, and whose name is the same as the last element of the name of the script object in which it lives. For instance, if the Example 5-1 handler lives in a script object at workspace.sayHi or at people.man.sayHi or in an entry called sayHi anywhere else in the database, and if the handler is not subordinate to anything else in the script, then it is that script object's eponymous handler.
We are now ready to enunciate the entirety of the "Handler Rule."
Let's consider some implications of this. First, compare Example 4-3 and Example 5-1. Imagine that one or the other of these is the complete content of workspace.sayHi. So in the former case, workspace.sayHi has no eponymous handler; in the latter, it does. Then, either way, we can call workspace.sayHi() - in the case of Example 4-3, this is because there are no parameters (rule 2) - and it will execute. Example 4-3 will execute according to rule 3b. Example 5-1 will execute according to rule 3a.
Now suppose workspace.sayHi consists of Example 4-3. If we press its edit window's Run button, it executes just as if it had been called with a verb call (rule 4).
But suppose workspace.sayHi consists of Example 5-1. If we press its edit window's Run button, nothing happens!2 That's because rule 4 says that Frontier will execute the script's summit-level commands; but the only summit-level command here is a handler, and a handler takes no action: it's just a definition. The commands in a handler's bundle execute only when the handler is called. So, if we want pushing the Run button to perform the action defined by the eponymous handler, we must include, after the eponymous handler (at summit level, not as part of the handler), a call to the handler.
We can write such a call in one of two ways. We can, if we wish, call the script object itself. workspace.sayHi would then look like the following example.
The second way to call the sayHi() handler is directly, treating sayHi not as a script object in the database but as a "local" handler (discussed in more detail in Chapter 7, The Scope of Variables and Handlers ). This is done by using just the handler's name, not a path.
This is legal because the sayHi() handler is "visible" directly to the other parts of the same script, in accordance with the rules of scope. Such, in fact, is the most common way of constructing a script that contains an eponymous handler. Either way, the script now contains an eponymous handler and, nevertheless, can be tested with the Run button.
Notice that pressing the Run button in Example 5-2 or Example 5-3 does not cause some sort of vicious loop! Consider Example 5-2 first.
We press the Run button, and Frontier starts executing all summit-level commands in order (rule 4). The first of these, the handler, is just a definition, so Frontier takes note of the definition and goes on to the next command, which is the call workspace.sayHi(). Frontier now goes out to the database, finds the object workspace.sayHi, discovers that it is indeed a script object, discovers further that it possesses an eponymous handler, and (rule 3a) executes only the eponymous handler's bundle - the rest of the script, consisting of the call workspace.sayHi(), is ignored, and therefore execution comes to an end with no circularity.
Now consider Example 5-3. We press the Run button, and Frontier starts executing all summit-level commands in order (rule 4). The first of these, the handler, is just a definition, so Frontier takes note of the definition and goes on to the next command, which is the call sayHi(). Obedient to the call, Frontier executes the sayHi() handler's bundle. This completes the execution of the last (and only) action line of the script, and execution comes to an end.
To be sure, it is possible for a script to call itself in a vicious loop. If, for instance, workspace.sayHi contained no eponymous handler, but did contain a line calling workspace.sayHi() - which is legal, since sayHi() takes no parameters - we would loop forever, whether we pressed the Run button or called it from another script:3
local (theDay, theMonth, theYear, theHour, theMin, theSec)
date.get (clock.now(), @theDay, @theMonth, \
@theYear, @theHour, @theMin, @theSec)
if theHour >= 12
dialog.notify ("Good afternoon or evening.")
else
dialog.notify ("Good morning.")
workspace.sayHi ()
The moral is: when in doubt, include an eponymous handler, to prevent such loops!
Taken together, rules 3a and 4 of the Handler Rule enable the following technique for writing and testing scripts.
Suppose sayHiTo() is the eponymous handler of workspace.sayHiTo, and that it is like sayHi(), but takes a parameter - for instance, the parameter is to be someone's name, and our dialog will now greet that person by name. (Rule 2 does not apply, so, by rule 1, our script must contain an eponymous handler.) The script might go like the following example.
By rule 3a, we can call this script from another script (providing a parameter in the call), like this:
workspace.sayHiTo ("Matt")
But, as we have already seen, we cannot run our script by pressing the Run button, because it contains no executable commands; to do so, we must introduce a call to sayHiTo(), after the handler, at summit level.
Well, as long as we are doing this, we may as well take the opportunity to test sayHiTo(). We might, for example, add several calls to sayHiTo(), with different parameters, just to make sure they all work. So, we might end up with something like the following example.
If we press the Run button, we get the dialog three times in a row, with each of the names substituted (by rule 4). But if, as before, we call:
workspace.sayHiTo ("Matt")
from another script, we don't get those three dialogs: we just get one dialog, with the name "Matt" appearing in it (by rule 3a).
Our three calls to sayHiTo() within workspace.sayHiTo are a stub, executed during testing when we press the Run button, but not when we really use the verb by calling it. It is very common to write scripts consisting of an eponymous handler and a stub.
A verb call doesn't just cause the verb to execute; it also has a value. Every verb generates a result as the final act of its execution. This result becomes the value of the verb call, within the calling script.
The script in which the verb call appears may or may not wish to use the verb call's value in some way. If it does, it must capture it somehow - for example, by assigning it to a variable. Otherwise, the value is simply thrown away, without penalty.4
Consider, for example, this simple script:
dialog.notify ("Hello, World!")
The verb dialog.notify() performs an action: it puts up a dialog on the screen. This really is all we want from it when we call it. But it also has a result. In this case, we are not concerned with the result, so we have done nothing to capture it. But if we were concerned with the result, we could capture it - by treating the call dialog.notify() as a value. For example, we could assign that value to a variable:
whatResult = dialog.notify ("Hello, World!")
This script runs dialog.notify() (causing the dialog to appear on the screen), and also gets its result, and assigns that result to the variable whatResult. Even so, we still don't know the result, because we don't know what whatResult is. To find out, let's display it - for example, by handing it as a parameter to dialog.notify():
whatResult = dialog.notify ("Hello, World!")
dialog.notify (whatResult)
The second dialog that appears says true; that's the result of the first call to dialog.notify(). This is not a particularly interesting result, to be sure. But sometimes, the result of a verb call is interesting; in fact, sometimes learning the result of a verb call is the whole point of calling that verb.
If a verb does not specify what its own result is to be, that result can be somewhat unpredictable; it is often the result of evaluating the last executed line of the script, but one cannot rely on this. So if a verb wants to specify its own result, it should do so explicitly. This is done with the return keyword. Actually, the return keyword has two effects: it states what the verb's result should be, and it terminates execution of the verb.5 (UserTalk has no separate halt or exit keyword.)
The syntax of return is as follows. The value that the verb's result is to have appears after the word return, on the same line with it. This value is often surrounded by parentheses as a matter of style, but this is not required. If the word return appears by itself with no value, true or false is returned (probably true if we've gotten this far in the execution of the verb); this syntax is generally used simply in order to halt execution of the verb before the last command is reached.6
Thus, to make a verb that reports the hypotenuse of a right triangle given the two sides as parameters, you might say (supposing the script was at work-space.hypotenuse):
on hypotenuse (a, b)
return trigCmd.sqrt ((a * a) + (b * b))
It would then be possible to use the value of a call to this verb as part of a calculation in some other script:
x = 3 + workspace.hypotenuse (8, 9)
« x is now 15.04159458
Given any line of UserTalk in a script edit window, it is possible to evaluate (and, if the line is an executable command, to execute) just that line. To do so, select the line or click within the line, and choose Run Selection from the Main menu. This causes Frontier to evaluate the line, and to insert its value as a comment subordinate to the line.
The technique is possible only if the line is understandable as a complete and independent script. For instance, in Example 5-5, selecting the line:
sayHiTo ("Mom")
and choosing Run Selection yields an error: Frontier has never heard of sayHiTo, because the handler defining it was never executed. On the other hand, if we change the line to:
workspace.sayHiTo ("Mom")
assuming the script lives in workspace.sayHiTo, selecting the line and choosing Run Selection does work - the dialog appears, and the value true is inserted as a comment subordinate to the line.
Line evaluation is a common way of learning the result of a verb call. For example, we could rewrite workspace.hypotenuse as an eponymous handler and a stub, as follows:
on hypotenuse (a, b)
return trigCmd.sqrt ((a * a) + (b * b))
workspace.hypotenuse (3, 4)
Selecting the last line and choosing Run Selection causes the value 5.0 to be inserted as a subordinate comment; since this is what we expect hypotenuse() to return given 3 and 4 as parameters, it appears that the handler is correctly written.
In this book I shall often use the convention of a subordinate comment to present the result of a verb call.
Example 5-4 illustrates the basic syntax for a handler defining a verb that takes a parameter. The name theName is completely arbitrary; any legal variable name will do. When a handler is actually called for execution, the variables named in its parameter list are created, and values are assigned them, based on evaluation of the corresponding expressions used in the call.
myName = "Matt" |
workspace.sayHiTo (myName) |
When we execute this script, in the second line, the variable myName is evaluated; and its value, which is the string "Matt", is handed to sayHiTo() to put into its variable called theName. Then, from sayHiTo()'s point of view, as it starts executing, it finds that a variable theName exists, whose value has been initialized to the string "Matt".7
When execution of the sayHiTo() bundle is over, the variable theName that it was using goes out of existence. This is because parameter variables in a handler are local. (Local variables and scoping are dealt with in Chapter 7.)
It is an error to call a verb with a number of parameters different from the number of parameter variables in the handler definition. For example, given Example 5-4, it is an error to say:
workspace.sayHiTo ()
workspace.sayHiTo ("Matt", "Mom")
Nonetheless, UserTalk provides a certain degree of flexibility as to the number of parameters in a verb call, as we shall see shortly.
Let's consider further the relationship between the caller script of Example 5-6 and the handler in Example 5-4. The variable theName which appears in the latter is, while it exists, like any other variable. This means that its value can be changed, for example, by assigning it a new value. Nevertheless, such a change will have no effect upon the variable myName used in the calling script. This is because the value of myName was copied in order to initialize the value of theName. The two variables, theName and myName, are separate entities.
In technical terms: all parameters, without exception, are passed by value in UserTalk.8 We shall see later on (Chapter 8, Addresses ), though, that it is possible to pass a parameter in such a way that a variable belonging to the calling script can be changed by the called handler. We shall also learn (Chapter 7) that the scoping rules sometimes allow a variable belonging to the caller to be changed by the called handler, without being passed as a parameter at all.
If a verb takes more than one parameter, those parameters are separated by commas both in the handler definition and in the call.
Which parameter value in the call is assigned to which parameter variable in the handler? By default, values are assigned to the parameter variables based on the order in which they are provided in the call.
Thus, in this example, a is initialized to 1, b is initialized to 5, and c is initialized to 32 (as the result demonstrates).
on addThreeAndMultiplyToo (a, b, c) |
return (a + (2 * b) + (3 * c)) |
addThreeAndMultiplyToo (1, 5, 32) |
« 107 |
Parameters can be provided in any order in a call, though, by specifying for each value what parameter variable it is to be assigned to. The parameter variable's name (from the handler definition) is followed by a colon and the value to be assigned to it. Thus, in this example, c is initialized to 1, a is initialized to 5, and b is initialized to 32.
on addThreeAndMultiplyToo (a, b, c) |
return (a + (2 * b) + (3 * c)) |
addThreeAndMultiplyToo (c:1, a:5, b:32) |
« 72 |
The two syntaxes can be combined, with some parameter values named and others not. All the unnamed values must precede all the named values, and are assigned according to the default parameter passing rule. The following, therefore, is legal:
addThreeAndMultiplyToo (5, c:1, b:32)
« 72
WARNING Throughout this book, when the syntax of built-in UserTalk verbs is summarized, the parameter names used are not the actual names used in the verb's definition. Instead, descriptive names are given, to make the purpose of each parameter more evident. If you want to call a UserTalk verb by naming a parameter, you should look up the verb in the database to learn the parameter's actual name.
A handler's bundle cannot execute without values for every parameter variable. However, some or all parameter variables can be assigned default values within the on line's parameter variable list. Values for any of those parameters can then be omitted from the call; the default values will be used for any parameters whose values are not specified in the call. But if a parameter's value is specified in the call, the specified value overrides the default value (if any).
Thanks to the calling syntax rules just described, Frontier will always be able to figure out which passed value goes to which parameter variable, and hence which values have been omitted.
An illustration is worth a thousand words.
UserTalk does not permit a handler to accept an indefinite number of parameters. There is an indirect mechanism for this, though, by way of the list datatype. Lists are discussed in Chapters 10 and 11; however, here is an illustration (a list is delimited by curly braces). The handler expects one parameter, but that parameter is a list which can consist of any number of items:
on addAny (theList)
theTotal = 0
for x in theList
theTotal = theTotal + x
return theTotal
addAny ({ 1, 5, 32, 4, 97 })
« 139
1. For this reason, when you choose New Script from the Table menu, Frontier creates the beginning of an eponymous handler for you.
2. Except that Frontier, figuring that our pressing the Run button in this situation probably means we don't understand the Handler Rule, will put up a warning dialog.
3. Actually, we would not loop literally forever; we would loop 40 times and then hit Frontier's stack limit on recursion (see Chapter 7, The Scope of Variables and Handlers ).
4. Thus, as in C (or Lisp), a UserTalk verb can serve as what Pascal calls a procedure and/or as what Pascal calls a function.
5. "The verb" is the deepest handler in which the return appears, or the script itself if the return is not inside a local handler. If handler B appears as a local handler inside handler A, you cannot use a return inside handler B to stop execution of handler A as well. One way to accomplish this might be to pass an error up from B and trap it in A. Error-trapping is discussed in Chapter 12, Control Structures .
6. Of course, when the last command is reached, the verb returns automatically; if it is not desired to return a particular value, there is generally no need to include a return as the last line of a handler or script. It can't hurt, though, to do so.
7. A distinction of nomenclature is sometimes drawn between parameters from the handler's point of view, which are variables created and given values when the handler is executed, and parameters from the caller's point of view, which are the values to be assigned to the handler's parameter variables. Sometimes "parameter" versus "argument" is used to make this distinction. But "parameter" has become common for both in UserTalk; when the context does not make the meaning obvious, I will speak of the handler's "parameter variables."
TOP | UP: UserTalk Basics | NEXT: Referring to Database Entries |