Serious First Steps In UserTalk Scripting
by Matt Neuburg
Author of the book Frontier: The Definitive Guide
As mentioned in the previous chapter, our counting routine works but is a bad example of programming.The program contains a clear patterned repetition, and, in cases of this sort, programming languages generally provide some method of reducing the code to the pattern alone, letting the computer perform the repetition.
You do see the pattern, right? At every step we are saying:
msg (n) clock.waitseconds (1)where by "n" I mean "some number".
Not only that, but there is a pattern to what "n" is: at every step it is larger by 1 than at the previous step. There are many ways in Frontier to create the loop we want, but here is one of the most obvious:
on counter () for n = 1 to 10 msg (n) clock.waitseconds (1) counter ()Rewrite workspace.counter like this, and run it. It has exactly the same effect as our original script; we have reduced a twenty-line handler to three lines, and we have eliminated for ourselves a great deal of work (and possible sources of error) by letting Frontier calculate for us what each successive number should be.
The first line of the script uses "for", a keyword introducing a looping structure.In a looping structure, everything inside the loop is repeated again and again before proceeding to whatever comes after the looping structure; and, as with a handler definition, we show Frontier what is to be inside the loop by indenting that material below the line that introduces the loop (in this case, the "for" line).
The "for" structure involves the use of a variable, which we have called "n". A variable is a named value (rather like a database entry).
We can call the variable in a "for" line anything we like, but once we have chosen a name for it we must be consistent; having called the variable "n" in the "for" line, we must refer to it as "n" within the repeated material.
The "for" line says: repeat the material of the loop again and again, and when you do, let the variable "n" take on a new value each time, starting with 1, increasing the value each time, and stopping the loop after the value 10 has been used.
Within the loop itself, we display the value of "n" in the About Window and pause for one second; since the value of "n" is 1 the first time through the loop, and is one more each time through the loop than it was the previous time, and is 10 the last time through the loop, the effect is exactly that of displaying the numbers 1 to 10.
Our counting verb, workspace.counter(), is now considerably more elegant; but at this point we might decide that it also unnecessarily specific. One of the goals of good programming is to write routines that are of general utility.Our counting verb is frozen, as it were, into counting from 1 to 10; it cannot count from 1 to 20, or from 10 to 15. We have "hard-coded" the limits 1 and 10 into our definition of counter(). How might we fix this?
Let's begin by thinking how to remove the restriction that what we are counting up to is 10. Let's make it so that we can count from 1 to any number at all.
To do this, we need to replace "10" by a variable, because at the time we write the code we don't actually know how high counter() is to count. Let's call the variable "upperLimit":
on counter () for n = 1 to upperLimit msg (n) clock.waitseconds (1) counter ()But if we try to run this script, we get an error; we have not told counter() how to know, at the time that it runs, what "upperLimit" is to be. Where should the value for "upperLimit" come from?
One answer is that it might be passed to counter() as a parameter. To make this be so, simply use "upperLimit" as the name of counter()'s parameter in its definition, like this:
on counter (upperLimit) for n = 1 to upperLimit msg (n) clock.waitseconds (1) counter ()The "on" line here says: I define a verb counter(), and whenever counter() is called it should be handed one parameter. Take the value of that parameter and assign it to a variable called "upperLimit".
Thus, when we get to the "for" line, "upperLimit" does have a value, namely, the value handed to counter() as a parameter when the call was made.
Of course, this means that we actually must remember to hand counter() a parameter when call it! If you run the script as written above, you still get an error (try it); we have defined counter() to require one parameter, but when we actually come to call counter(), in the last line, we have neglected to supply that parameter.
So let's supply one; let's supply 5. Modify the last line accordingly, and run the script:
on counter (upperLimit) for n = 1 to upperLimit msg (n) clock.waitseconds (1) counter (5)It works! Or, we might supply 20; change the last line again, and run the script:
on counter (upperLimit) for n = 1 to upperLimit msg (n) clock.waitseconds (1) counter (20)It works again!
Now, here's an interesting twist. A further extension of the power of parameters is to supply a default value for a parameter. This means that in the case where the caller fails to supply a value for that parameter, the handler supplies one instead.Thus, we could set up counter() so that it counts from 1 to whatever number is supplied as a parameter when the call is made, but in case no parameter is supplied at all, we'll count to 10. The way to code this is to assign the default value in the "on" line, like this:
on counter (upperLimit = 10) for n = 1 to upperLimit msg (n) clock.waitseconds (1) counter (20)If you press the Run button to run this script, we count to 20 as before. But if you change the last line so that no parameter is supplied when the call is made:
on counter (upperLimit = 10) for n = 1 to upperLimit msg (n) clock.waitseconds (1) counter ()then when you run the script, there is no longer an error; instead, counter() counts to 10, the default case.
Let's generalize counter() still more, so that we not only count to any number the caller cares to supply, but also from any number.We'll need another variable to represent this -- let's call it "lowerLimit" -- and let's give that variable its value by handing it as a parameter, just as with "upperLimit", and give that parameter a default value of 1.
Multiple parameters are signified by separating the parameters with commas:
on counter (lowerLimit = 1, upperLimit = 10) for n = lowerLimit to upperLimit msg (n) clock.waitseconds (1) counter ()If we run this script, counter() counts from 1 to 10. But if we change the last line to:
counter ( 2, 6 )then counter counts from 2 to 6. If we call counter() with just one parameter, it is taken to be the first parameter; so, for instance, if we change the last line to:
counter (5)then counter() counts from 5 to 10.
It is also possible to supply only the second parameter; to do this, we have to say by name what parameter we're supplying. For instance, we can change the last line to:
counter ( upperLimit : 5 )and counter() counts from 1 (the default) to 5 (the parameter we fed it).
Finally, we can generalize counter() even further, to allow our starting number to be bigger than our ending number.At the moment, if you change the last line to:
counter ( 6, 4 )then no error message appears, but nothing else happens either. This is because it is not an error for a "for...to" to have its first number be larger than its second, but in such a case the "for...to" never performs its loop.
If a "for" loop is to decrease its number by 1 on each loop instead of increasing it, it must be written as a "for...downto" loop instead.
So we need counter() to make a choice: if the first parameter is smaller than the second, then we should count up as we are doing already, but if the first parameter is larger than the second, we should count down.
Choices of this sort are indicated by use of a conditional structure. The basic conditional structure in UserTalk is "if"; when there two alternatives it is often simplest to use this in the form "if...else".
Once again, the work that is to be done in each case, the "if" case and the "else" case, is indicated by indenting the corresponding instructions below the "if" and the "else", respectively.
So we can rewrite workspace.counter 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) counter ()Try running this with the last line consisting of counter(1,3) and again with the last line consisting of counter(5,2) and see what happens.
Experiment with other numbers. What happens if one of the parameters is negative? What happens if one of the parameters is not an integer? What happens if one of the parameters is the word "haha" (in quotes)?
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:01 PM.