Parameters and Arguments

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.

Tip

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.

Passing Arguments

A simple function performs an operation on the argument(s) passed into it. For example, let’s define an avg() function that averages two numbers.

Example 1-29. A Handler That Accepts Parameters

on avg a, b
  return (a+b) / 2.0
end

The names avg, a, and 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 answer 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 return statement).

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 avg().

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 alert.

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.

Tip

If the number of arguments does not match the number of parameters, Director won’t complain. It is up to you to ensure that the correct number of arguments is specified.

Modify the 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 a and b are implicitly assigned to the arguments in the function call, answer is explicitly assigned a value using set...=.

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.

Tip

The parameters within newAvg, namely a and b, are still equated to 5 and 8, respectively. The values of the arguments x and y, not x and y themselves, are passed to newAvg. This is called passing arguments by value, rather than by reference.

Refer to "Parameter Passing,” in Chapter 4 for more details on parameters passed by reference and to Chapter 6 for how this affects Lingo lists.

Generalizing Functions

NewAvg is a generalized handler that can average any two numbers passed into it (we’ll see later how to make it accept any number of arguments to average).

Generalized Sprite Handlers

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 currentSpriteNum, the spriteNum of me, and the clickOn properties and how they can be used to generalize handlers for use with any sprite.

Generalizing a Function with Parameters

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 "FOO.TXT" exists.

You might write the code shown in Example 1-31 (see Chapter 14 for an explanation of this Lingo and a more robust example).

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.

Tip

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

Using a Generalized Function

Let’s revisit the simple newAvg handler. Add the following handler to your movie script that already contains the newAvg handler shown earlier.

Example 1-33. Using Generalized Functions

on testAvg
  newAvg (5, 3)
  newAvg (8, 2)
  newAvg (4, 4)
  newAvg (7, 1)
end

Choose ControlRecompile Script, and then test testAvg from the Message window.

testAvg

You should see the output of the newAvg function repeated four times.

Now, type this in the Message window:

newAvg (5)

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

Special Treatment of the First Argument Passed

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

Isn’t 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

The variable 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.

Warning

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 x).

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).

Tip

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.

Example 1-35. Passing a Script Instance as an Argument

on mouseUp me
  displayInfo (me)
end

This is the movie script:

on displayInfo someScriptIntanceOrObject
  put the spriteNum of someScriptIntanceOrObject
end

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!

Rewrite the displayInfo function call as:

on mouseUp me
  displayInfo (VOID, me)
end

In the displayInfo handler declaration in the movie script we must add a dummy parameter to “catch” the first dummy argument:

on displayInfo dummyParam, someScriptIntanceOrObject
  -- Use a dummy argument as the first parameter
    -- to allow an object to be passed as second argument.
  put the spriteNum of someScriptIntanceOrObject
end

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.

Optional Arguments and Varying Argument Types

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 VOID.

The 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.

Refer to Example 5-3 in Chapter 5, Coordinates, Alignment and Registration Points, in Director in a Nutshell. It accepts parameters in numerous formats and includes substantial error checking.

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 waitFlag.

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

Tip

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 chan.

-- 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)

Variable-Length Parameter Lists

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 param(n) returns the nth parameter.

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 a, b, and 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 a or param(1). Likewise, we can access the second parameter as either b or 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 param() and 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.

Parameter Error Checking

The 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"

or

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

The 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.

Tip

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.

The 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. VerifyParams() returns 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.

See also Example 8-4 in Chapter 8.

Congratulations!

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:

Q:

What is TRUE?

A:

How about trying, put TRUE?

Where else but Lingo can you find out what is TRUE with a mere nine keystrokes?

Get Lingo in a Nutshell now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.