Imagine you own a calculator with buttons that each perform a single complete operation—one button adds 5 plus 7, and another button adds 5 plus 8, and so on. It might be convenient for those limited operations, but the calculator could rapidly become unwieldy. (Similarly, Chinese pictographs are inconvenient for computer usage compared to an alphabet from which you can construct any word.) In reality, each button on a calculator actually represents either an operand, such as the number 5, or an operation, such as addition, allowing for many possible combinations with relatively few keys.
At the beginning of this chapter we used the
alert command to display the string “Hello World” in a dialog box. The
alert command accepts any text string as an argument. An argument is analogous to the operands used in the example of a calculator above, and the
alert command is analogous to the addition button that performs some operation using the argument(s). The words parameters and arguments are often used interchangeably to indicate inputs that are passed to a function on which it can operate. Strictly speaking, an argument is the item specified as part of the function call, and a parameter is the same item once it is received inside the function.
Just as the
alert command can accept any string for display, we should strive to make our custom handlers flexible by using variables to represent values that can change. In contrast, using a fixed number, such as 5, or a fixed string, such as “Bruce,” in your program is called hardcoding a value.
Instead of hardcoding specific values into a handler, generalize the function so that it can operate on whatever arguments are passed into it.
For example, a handler that finds a file should be flexible enough to find any file we ask it to find, rather than always looking for a particular hardcoded filename. Beginning programmers often create two or more copies of the same handler with only minor variations to accomplish nearly identical tasks. Instead, you should create a generalized (that is, flexible) version of the handler that accepts arguments (or parameters) to accommodate the differences. This is shown in detail in Example 1-31 and Example 1-32. Let’s start with a discussion of how arguments are passed to a function.
b are chosen arbitrarily. Note that there is a space between the handler name (
avg) and the first parameter (
a) but that subsequent parameters (such as
b) are separated by a comma from the previous parameter. The
return statement sends the answer back to whoever called the function. Without the
return statement, the
avg calculates would never be known! (Forgetting to return a result is a very common error. If the result from a custom function returns
VOID, you probably forgot the
Type the handler in Example 1-29 into a movie script, and test it from the Message window. The
put command prints the result returned by
put avg (5,8) -- 6.5000
The integers 5 and 8 are arguments that are operated upon by
avg(). The arguments are separated by commas. The parentheses are required to obtain the value returned by
avg(), but parentheses are optional when calling a command that does not return a value, such as
The first argument (5) is automatically assigned to the first parameter (
a), and the second argument (8) is assigned to the second parameter (
b). In this case, the order of the parameters does not affect the result, but in most cases the order of the parameters is crucial. For example, division is not reflexive: 5 divided by 8 would not be the same as 8 divided by 5.
avg() handler to create
newAvg as follows:
on newAvg a, b put "The first parameter, a, equals" && a put "The second parameter, b, equals" && b set answer = (a+b)/ 2.0 put "The answer is" && answer end
We’ve added a local variable, arbitrarily named
answer, which is convenient for holding the value that is printed and then returned to the calling program. Whereas
b are implicitly assigned to the arguments in the function call,
answer is explicitly assigned a value using
There is no difference (as far as the
newAvg handler can tell) if we pass integer variables as arguments to
newAvg instead of the integer literals 5 and 8. Type each of these lines in the Message window, pressing
RETURN after each:
set x = 5 set y = 8 put x newAvg(x, y) -- "The first parameter, a, equals 5" -- "The second parameter, b, equals 8" -- "The answer is 6.500"
We don’t need to use the
put command because
newAvg displays the result itself using
put, rather than returning an answer.
The parameters within
b, are still equated to 5 and 8, respectively. The values of the arguments
y themselves, are passed to
newAvg. This is called passing arguments by value, rather than by reference.
To perform an operation on a sprite you must refer to the sprite by its channel number (or an expression that evaluates to a channel number). For a sprite in channel 1, you might use:
on mouseUp set the foreColor of sprite 1 = random (255) end
A beginner might create another script to attach to a different sprite in channel 2 as follows:
on mouseUp set the foreColor of sprite 2 = random (255) end
Not only is this wasteful, but these scripts will fail miserably if you move the sprites to a new channel. Thankfully, Lingo provides several system properties that can be used to generalize a handler. When a script is attached to a sprite,
the currentSpriteNum property always indicates the sprite’s channel number. Therefore, we can replace the two separate scripts with a single sprite script that can be attached to any sprite in any channel and that will always work.
Example 1-30. A Simple Generalized Behavior
on mouseUp set the foreColor of sprite (the currentSpriteNum) = random (255) end
Refer to Chapter 9 for details on
the spriteNum of me, and
the clickOn properties and how they can be used to generalize handlers for use with any sprite.
The following is a more sophisticated example of a generalized function, but the principle is the same (feel free to skip this section if it is confusing). Let’s suppose you want to check if the file "
Example 1-31. A HardCoded Function
on doesFooExist -- Use the FileIO Xtra to try to open the file FOO.TXT set fileObj = new (xtra "FileIO") if objectP(fileObj) then openFile (fileObj, "FOO.TXT", 1) set result = status (fileObj) -- A result of 0 indicates success if result = 0 then alert "FOO.TXT exists!" else -- Print the error message in the Message window put error (fileObj, result) alert "FOO.TXT can't be found" end if -- Clean up after ourselves closeFile (fileObj) set fileObj = 0 end if end doesFooExist
Now suppose you want to check if a different file exists. Most beginners would duplicate this long block of code, then change the filename in the
openFile() function call from “FOO.TXT” to their new filename.
Never duplicate near-identical long blocks of code. Your code becomes harder to debug—and much harder to change if you do find a bug. Always generalize the code into a utility function that you can add to your “tool belt” for future use.
Below we’ve created a generalized function. Note that it accepts a file name as a parameter. The name that you specify gets substituted automatically for the
fileName parameter and is used in the
openFile command. Note also that it returns either
TRUE (1) or
FALSE (0) to indicate whether the file was found. If it couldn’t be found, you may want to return the error code that was obtained from the FileIO Xtra’s
status() call. (It is good practice to simply return some result or status and let the caller decide whether to post an alert message or do something else.) Beyond that, it is essentially the same handler as shown in Example 1-31. Try to make your utility code as non-intrusive as possible, and clean up after yourself. Note that we opened the file in read-only mode to avoid failing if the file was already open. We also closed the file when done to clean up after ourselves. Example 1-32 is primarily for illustration. The FileIO Xtra will search in the current folder if
the searchCurrentFolder is
TRUE (the default). It will also search the list of paths, if any, in
the searchpaths. It may not work correctly with long filenames under Windows. Thus Example 1-32 is not completely robust.
Example 1-32. A Generalized FileExists Function
on fileExists fileName -- Use the FileIO Xtra to open the specified file set fileObj = new (xtra "FileIO") if objectP(fileObj) then -- Open file with mode = 1 "read-only" openFile (fileObj, fileName, 1) set result = status (fileObj) -- A status of 0 indicates success if result = 0 then set found = TRUE else -- Display the error for debugging put error (fileObj, result) set found = FALSE end if closeFile (fileObj) set fileObj = 0 -- Return TRUE or FALSE to indicate if the file exists return found end if end fileExists
Now we can easily determine if any file exists, such as:
put fileExists ("FOO.TXT") -- 1 put fileExists ("FOOPLE.TXT") -- 0
Or use it as follows:
if fileExists ("FOO.TXT") then -- Do whatever I want to with the file... -- such as open and read it else alert "The file can't be found" end if
Example 1-33. Using Generalized Functions
on testAvg newAvg (5, 3) newAvg (8, 2) newAvg (4, 4) newAvg (7, 1) end
Recompile Script, and then test
testAvg from the Message window.
You should see the output of the
newAvg function repeated four times.
Now, type this in the Message window:
What happens and why? What is the value of the second parameter within the
newAvg handler? It defaults to
VOID, as do all unspecified parameters, because only one argument was specified in the function call. What happens if we forget to specify the
fileName when calling our
fileExists() function created earlier?
Let’s create a simple
divide function (in reality you’d just use “/” to divide):
on divide a, b return float(a)/ b end put divide (5,5) -- 1.0000
What happens if we forget to specify the parameter used as the divisor?
put divide (7) -- This causes an error
We saw earlier that an error occurs when a local variable is used before it is assigned a value. Enter the code shown in Example 1-34 in a movie script, and recompile the script.
Example 1-34. Special Treatment of First Argument to a Function Call
on testFirstArg dummyHandler(x) end testFirstArg
x an unassigned local variable? Shouldn’t it generate a "Variable used before assigned a value" error message? Before answering that, let’s alter
testFirstArg and recompile the script:
on testFirstArg dummyHandler(x, y) end testFirstArg
y is also an unassigned local variable. Why does it generate an error message, if
x did not? The answer lies in the unique way that Lingo treats the first argument to any function call.
Even though it is an error, Lingo does not complain if the first argument to a function call is an undeclared local variable (in this case
Multiple scripts may contain handlers of the same name. When you call a function, Lingo must decide which script to look in first. If the first argument to the function call is a special entity called a script instance (see Chapter 2), Director runs the handler in that particular script rather than performing its usual search to find the right script automatically. Lingo allows anything as the first argument to a function call because it does not verify script instances or handler names during compilation. (At runtime it will most likely cause an error, though).
If the first argument passed to a custom function call is a script instance, Lingo searches only that script instance for the specified handler. Therefore, you can not pass a script instance as the first parameter to a handler in a movie script because Lingo won’t look in the movie script!
Suppose you want to call a movie script’s handler from a Behavior, and suppose you want to pass in the Behavior’s script instance as an argument. Assume that this is the Behavior script.
This is the movie script:
someScriptIntanceOrObjectput the spriteNum of
What will happen? Director will issue a "Handler not defined" error because it will look for the
displayInfo handler only in the Behavior script (but it won’t find it). You can move the
displayInfo handler into the Behavior script, in which case it will be available only to instances of that Behavior, or you can rewrite the example as shown in the code that follows. Note that we add a dummy
VOID argument as a placeholder for the first argument to the function call, which allows our script instance to become the second argument. Because the second argument is not afforded any special treatment, Lingo searches the usual hierarchy and finds the
displayInfo handler in the movie script!
displayInfo function call as:
displayInfo handler declaration in the movie script we must add a dummy parameter to “catch” the first dummy argument:
someScriptIntanceOrObject-- Use a dummy argument as the first parameter -- to allow an object to be passed as second argument. put the spriteNum of
A script instance or child object (which can be thought of as the same thing) is often intentionally passed as the first argument to force Lingo to look in the correct script for the correct handler or property variables. See the example of property variables in the earlier "Variable Types" section and Chapter 12 and Chapter 13.
Lingo’s built-in commands typically complain if you pass the wrong number or unexpected type of arguments, but some accept arguments of different data types and/or a variable number of arguments (”variable" is used here to mean "varying,” not a Lingo variable). For example, the second argument to
setaProp() is either a property name or a property value, depending on whether the first argument is a property list or linear list. Likewise, the
puppetSound command accepts either one or two arguments, and some arguments to the
puppetTransition command are optional.
You can also design your custom handlers to allow a variable number of parameters or arguments of varying data types. This makes it easier to create generalized functions rather than multiple, highly similar versions. If you are creating a library of functions for yourself or others, it also makes those functions more flexible and easier to use by the calling routine.
Your function will typically require one or more mandatory arguments that should be placed at the beginning of the parameter list. Optional arguments should be placed after the required parameters. If the caller specifies fewer arguments than the number of parameters you are expecting, later parameters will be
playSound example that follows accepts the name or number of a sound to play and an optional sound channel number. If no channel is specified, it plays the sound in channel 1. Note the use of
voidP() to check whether the caller has specified the requested parameters.
Example 1-36. Function Accepting Varying Arguments
on playSound soundID, chan if voidP(soundID) then alert "A sound must be specified" exit end if -- Use channel 1 if the caller does not specify a channel -- Or specifies an invalid channel if voidP(chan) then set chan = 1 else if not integerP(chan) or ¬ (integer (chan) < 1 or integer (chan) > 8) then put "Channel should be an integer from 1 to 8" set chan = 1 end if -- Play the sound puppetSound chan, the number of member soundID updateStage end -- This plays "woof" in channel 1 (the default) playSound ("woof") -- This plays "bark" in channel 3 playSound ("bark", 3)
Note that we also check whether the channel number passed in is an integer and whether it is a valid sound channel number. Our example also accepts either a sound cast member’s number or its name, just like as built-in
puppetSound command. We could enhance the error checking to make sure the specified cast member is a sound.
You can extend the
playSound example to create a handler that accepts additional optional arguments, but note that the caller cannot pass a value for an optional argument unless all preceding arguments have been specified. For example, if we wanted to add a flag to
playSound indicating whether to wait for the sound to play, we could add another optional parameter called
Example 1-37. Placeholder Arguments
on playSound soundID, chan, waitFlag -- Beginning of handler is the same code as example above -- but is omitted here for brevity puppetSound chan, the number of member soundID updateStage -- This will wait if waitFlag is non-zero. It will -- not wait if waitFlag is omitted, and therefore VOID if waitFlag then repeat while soundBusy (chan) nothing end repeat end if end
Arguments and parameters are always matched up by position. The first argument in the function is assigned to the first parameter in the handler definition, and so on.
In this example, if the caller specifies only two arguments, the second argument will be used as the second parameter (
chan). If the caller wants to specify
waitFlag (the third parameter), he or she must specify three arguments, including a placeholder for
-- The second argument is assumed to be the second-- parameter and is mistakenly -- interpreted as a channel number playSound ("bark", TRUE) -- Instead, use 1 as a placeholder for the chan parameter playSound ("bark", 1, TRUE)
In the previous example, some arguments are optional, but the maximum number of arguments is known. You can also create handlers that accept an unknown (ostensibly unlimited) number of arguments. The
paramCount property and
param() function decipher an unknown number of arguments passed into a handler.
The paramCount indicates the total number of parameters received and
) returns the
Example 1-38. Variable Number of Parameters
on countParams put "Total Params:" && the paramCount repeat with n = 1 to the paramCount -- This statement prints out each parameter's -- number and its value by building a fancy string put "Param" && n & ":" && param(n) end repeat end countParams countParams ("Hello", "there", 5) -- "Total Params: 3" -- "Param 1: Hello" -- "Param 2: there" -- "Param 3: 5"
Note that no parameters are declared in the handler definition of
on countParams. It will accept any number of parameters, as would be appropriate if we wanted to, say, average any number of values. If we expected a fixed number of parameters, we could instead declare some parameters (in this case
c) when we define our handler, such as:
on newCountParams a, b, c put "Total Params:" && the paramCount put "Param a:" && a put "Param b:" && b put "Param c:" && c put "Param 1:" && param(1) put "Param 2:" && param(2) put "Param 3:" && param(3) end newCountParams newCountParams ("Hello", "there", 5) -- "Total Params: 3" -- "Param a: Hello" -- "Param b: there" -- "Param c: 5" -- "Param 1: Hello" -- "Param 2: there" -- "Param 3: 5"
We can access the first parameter as either
param(1). Likewise, we can access the second parameter as either
param(2), and so on. That is,
param(1) is always the first parameter, not merely the first unnamed parameter. Note that named parameters are easier to work with when you know how many to expect, but
the paramCount are more flexible. Use any combination of the two. Refer to Example 8-6 and Example 8-7 which use
the paramCount and
param() to take the sum or average of an indeterminate number of arguments.
playSound example discussed previously ignores extraneous arguments (that is, if more arguments are specified than the number of parameters expected), as do many Lingo commands. You can always check
the paramCount to warn the caller if too many or too few arguments are specified, such as:
if the paramCount > 3 then alert "No more than 3 please"
if the paramCount <> 4 then alert "Expected 4 params"
You can also check the type of each argument, as described in detail in Chapter 5:
if not integerP(param(1)) then alert "First parameter must be an integer" exit end if
verifyParams() function shown in Example 1-39 checks whether the parameter(s) passed into a handler are of the expected data type(s). The details are fairly complex, but you don’t need to understand them at this point.
You can often use a handler as a "black box.” You don’t need to know what happens inside the box; you need to know only what inputs it requires and what outputs it provides.
Likewise, you may provide handlers to others without supplying details on how they work. You need not understand all the magic as long as you trust the wizard behind the curtain. This book and its companion, Director in a Nutshell, try to dispel some of the mystery about how Director and Lingo work.
verifyParams() function shown in Example 1-39 accepts a property list containing parameters and their expected data types (it checks only for integers, floats, and strings). See Chapter 6 if you don’t understand lists, or just skip the details for now.
TRUE if the number and type of parameters are correct and
FALSE otherwise. You can extend
verifyParams() to handle more data types or to post an alert dialog instead of printing errors to the Message window.
Example 1-39. Verifying Parameters
on verifyParams verifyList, numInput -- Check the number of parameters vs. the number expected set numExpected = count (verifyList) if numInput < numExpected then put "Too few parameters. Expected" && numExpected return FALSE else if numInput > numExpected then put "Too many parameters. Expected" && numExpected return FALSE end if -- Check each item in the list and its data type repeat with x = 1 to count (verifyList) set nextItem = getAt (verifyList, x) case (getPropAt(verifyList, x)) of #integer: if not integerP (nextItem) then put "Expected integer for parameter" && x return FALSE end if #float: if not floatP (nextItem) then put "Expected float for parameter" && x return FALSE end if #string: if not stringP (nextItem) then put "Expected string for parameter" && x return FALSE end if otherwise: put "Unsupported type for parameter" && x return FALSE end case end repeat return TRUE end verifyParams
You can use
verifyParams() to check if your routine is called with the correct number and type of arguments. This is useful for debugging your own code or for trapping errors if you distribute your code for others to use.
VerifyParams() expects a property list containing each parameter and its expected data type. The following verifies whether
a is an integer,
b is a string, and
c is a float. It also checks whether exactly three parameters have been received.
on myHandler a, b, c -- Make sure that we received the expect parameters if not (verifyParams([#integer:a, #string:b, #float:c],[LC] the paramCount)) then alert "Something was wrong" exit end if -- Otherwise everything is okay and we can proceed. statements end myHandler
Test it from the Message window:
myHandler (12.5, "a", 5.7) -- "Expected integer for parameter 1" myHandler (12, 6, 5.7) -- "Expected string for parameter 2" myHandler (5, "a") -- "Too few parameters. Expected 3"
Reader Exercise: Modify
Verify Params() to return an error string.
Whew! You now have a foundation on which to build a greater understanding of Lingo. Even the most complex programs are built with simple components—variables, handlers, keywords,
repeat loops, and
if statements—so don’t be intimidated. With patience, you can (de)construct very complicated programs. Refer to Table 18-1, for a list of all Lingo keywords so that you can distinguish them from variables and custom handler names. Look at examples of other people’s Lingo code. Try to recognize the various pieces of the Lingo puzzle. (Remember diagramming sentences in English class, where you picked out the verbs, subjects, adjectives, and prepositional phrases?) Which items are variables? Which are keywords? Which are parameters? Which items are arbitrarily chosen by the programmer, and which are dictated by Lingo’s grammar or syntax?
This single chapter has covered material from both beginner and intermediate programming courses that might be spread out over many months. We also touched on some very advanced concepts that will serve you well as you read the rest of this book. Don’t be discouraged if you didn’t understand a lot of it, or if you skipped the more intimidating parts. Re-visit this chapter frequently, and you’ll find new treasures each time. It may seem hard to believe now, but when you look back on this chapter a year from now, most of the things that confused you will seem quite simple.
Most of this chapter applies to other programming languages you may encounter. If Lingo is your first programming language, rest assured that picking up additional languages becomes much easier. In Chapter 4 we compare Lingo to C/C++ so that you can see both the nitty-gritty details of Lingo and how other languages may differ.
Even though this “book work” may seem tedious, it will allow you to breathe new life into all your Director projects once you are out in the field. (Don’t forget to save your test Director movie periodically).
I leave this chapter with a reminder that I can point you in the right direction and even provide a map of the terrain and a steady compass, but you are ultimately your own navigator for the journey that lies ahead.
This exchange took place in the Director support forum:
Where else but Lingo can you find out what is
TRUE with a mere nine keystrokes?