Working with arguments and variables in functions



Good argument naming practice

Use an argument name that represents its use. For example, the following code is unlikely to result in confusion:

<cfscript> 
    function SumN(Addend1,Addend2) 
    { return Addend1 + Addend2; } 
</cfscript> 
<cfset x = 10> 
<cfset y = 12> 
<cfoutput>#SumN(x,y)#</cfoutput>

The following, similar code is more likely to result in programming errors:

<cfscript> 
    function SumN(x,y) 
    { return x + y; } 
</cfscript> 
<cfset x = 10> 
<cfset y = 12> 
<cfoutput>#SumN(x,y)#<cfoutput>

Passing arguments

ColdFusion passes the following data types to the function by value:

  • Integers

  • Real numbers

  • Strings (including lists)

  • Date-time objects

  • Arrays

As a result, any changes that you make in the function to these arguments do not affect the variable that was used to call the function, even if the calling code is on the same ColdFusion page as the function definition.

ColdFusion passes queries, structures, and external objects such as COM objects into the function by reference. As a result, any changes to these arguments in the function also change the value of the variable in the calling code.

For an example of the effects of passing arguments, see Passing complex data.

Passing complex data

Structures, queries, and complex objects such as COM objects are passed to UDFs by reference, so the function uses the same copy of the data as the caller. Arrays are passed to user-defined functions by value, so the function gets a new copy of the array data, and the array in the calling page is unchanged by the function. As a result, always handle arrays differently from all other complex data types.

Passing structures, queries, and objects

For your function to modify the copy of a structure, query, or object, in the caller, pass the variable as an argument. Because the function gets a reference to the structure in the caller, the caller variable reflects all changes in the function. You do not have to return the structure to the caller. After the function returns, the calling page accesses the changed data by using the structure variable that it passed to the function.

If you do not want a function to modify the copy of a structure, query, or object, in the caller, use the Duplicate function to make a copy and pass the copy to the function.

Passing arrays

If you want your function to modify the caller’s copy of the array, the simplest solution is to pass the array to the function and return the changed array to the caller in the function return statement. In the caller, use the same variable name in the function argument and return variable.

The following example shows how to directly pass and return arrays. In this example, the doubleOneDArray function doubles the value of each element in a one-dimensional array.

<cfscript> 
//Initialize some variables 
//This creates a simple array. 
a=ArrayNew(1); 
a[1]=2; 
a[2]=22; 
//Define the function. 
function doubleOneDArray(OneDArray) { 
    var i = 0; 
    for ( i = 1; i LE arrayLen(OneDArray); i = i + 1) 
        { OneDArray[i] = OneDArray[i] * 2; } 
    return OneDArray; 
} 
//Call the function. 
a = doubleOneDArray(a); 
</cfscript> 
<cfdump var="#a#">

This solution is simple, but it is not always optimal:

  • This technique requires ColdFusion to copy the entire array twice, once when you call the function and once when the function returns. Doing so is inefficient for large arrays and can reduce performance, particularly if the function is called frequently.

  • You can use the return value for other purposes, such as a status variable.

If you do not use the return statement to return the array to the caller, you can pass the array as an element in a structure and change the array values inside the structure. Then the calling page can access the changed data by using the structure variable it passed to the UDF.

The following code shows how to rewrite the previous example using an array in a structure. It returns True as a status indicator to the calling page and uses the structure to pass the array data back to the calling page.

<cfscript> 
//Initialize some variables. 
//This creates a simple array as an element in a structure. 
arrayStruct=StructNew(); 
arrayStruct.Array=ArrayNew(1); 
arrayStruct.Array[1]=2; 
arrayStruct.Array[2]=22; 
//Define the function. 
function doubleOneDArrayS(OneDArrayStruct) { 
    var i = 0; 
    for ( i = 1; i LE arrayLen(OneDArrayStruct.Array); i = i + 1) 
        { OneDArrayStruct.Array[i] = OneDArrayStruct.Array[i] * 2; } 
    return True; 
} 
//Call the function. 
Status = doubleOneDArrayS(arrayStruct); 
WriteOutput("Status: " & Status); 
</cfscript> 
</br> 
<cfdump var="#arrayStruct#">

Use the same structure element name for the array (in this case Array) in the calling page and the function.

About the Arguments scope

All function arguments exist in their own scope, the Arguments scope.

The Arguments scope exists for the life of a function call. When the function returns, the scope and its variables are destroyed.

However, destroying the Argument scope does not destroy variables, such as structures or query objects, that ColdFusion passes to the function by reference. The variables on the calling page that you use as function arguments continue to exist; if the function changes the argument value, the variable in the calling page reflects the changed value.

The Arguments scope is special, in that you can treat the scope as either an array or a structure. This dual nature of the Arguments scope is useful because it makes it easy to use arguments in any of the following circumstances:

  • You define the function using CFScript.

  • You define the function using the cffunction tag.

  • You pass arguments using argument name=value format.

  • You pass arguments as values only.

  • The function takes optional, undeclared arguments.

The contents of the Arguments scope

The following rules apply to the Arguments scope and its contents:

  • The scope contains all the arguments passed into a function.

  • If you use cffunction to define the function, the scope always contains an entry “slot” for each declared argument, even if you do not pass the argument to the function when you call it. If you do not pass a declared (optional) argument, the scope entry for that argument is empty.

    When you call a function that you defined using CFScript, Pass the function a value for each argument declared in the function definition. Therefore, the Arguments scope for a CFScript call does not have empty slots.

The following example shows these rules. Assume that you have a function declared, as follows:

<cffunction name="TestFunction"> 
    <cfargument name="Arg1"> 
    <cfargument name="Arg2"> 
</cffunction>    

You can call this function with a single argument, as in the following line:

<cfset TestFunction(1)>

The resulting Arguments scope looks like the following:

As an array

As a structure

Entry

Value

Entry

Value

1

1

Arg1

1

2

undefined

Arg2

undefined

In this example, the following functions return the value 2 because the scope contains two defined arguments:

ArrayLen(Arguments) 
StructCount(Arguments)

However, the following tests return the value false, because the contents of the second element in the Arguments scope is undefined.

Isdefined("Arguments.Arg2") 
testArg2 = Arguments[2]> 
Isdefined("testArg2")
Note: The IsDefined function does not test the existence of array elements. Instead, place any code that could access an undefined array element in a try block and use a catch block to handle exceptions that arise if elements do not exist.

Using the Arguments scope as an array

The following rules apply to referencing Arguments scope as an array:

  • If you call the function using unnamed arguments, the array index is the position of the argument in the function call.

  • If you use names to pass the arguments, the array indexes correspond to the order in which the arguments are declared in the function definition.

  • If you use names to pass arguments, and do not pass all the arguments defined in the function, the Arguments array has an empty entry at the index corresponding to the argument that was not passed. This rule applies only to functions created using the cffunction tag.

  • If you use a name to pass an optional argument that is not declared in the function definition, the array index of the argument is the sum of the following:

    • The number of arguments defined with names in the function.

    • The position of the optional argument among the arguments passed in that do not have names defined in the function.

    However, using argument names in this manner is not good programming practice because you cannot ensure that you always use the same optional argument names when calling the function.

To demonstrate these rules, define a simple function that displays the contents of its Arguments array and call the function with various argument combinations, as the following example shows:

<cffunction name="TestFunction" > 
    <cfargument name="Arg1"> 
    <cfargument name="Arg2"> 
    <cfloop index="i" from="1" to="#ArrayLen(Arguments)#"> 
        <cfoutput>Argument #i#: #Arguments[i]#<br></cfoutput> 
    </cfloop> 
</cffunction>     
 
<strong>One Unnamed argument</strong><br> 
<cfset TestFunction(1)> 
<strong>Two Unnamed arguments</strong><br> 
<cfset TestFunction(1, 2)> 
<strong>Three Unnamed arguments</strong><br> 
<cfset TestFunction(1, 2, 3)> 
<strong>Arg1:</strong><br> 
<cfset TestFunction(Arg1=8)> 
<strong>Arg2:</strong><br> 
<cfset TestFunction(Arg2=9)> 
<strong>Arg1=8, Arg2=9:</strong><br> 
<cfset TestFunction(Arg1=8, Arg2=9)> 
<strong>Arg2=6, Arg1=7</strong><br> 
<cfset TestFunction(Arg2=6, Arg1=7)> 
<strong>Arg1=8, Arg2=9, Arg3=10:</strong><br> 
<cfset TestFunction(Arg1=8, Arg2=9, Arg3=10)> 
<strong>Arg2=6, Arg3=99, Arg1=7</strong><br> 
<cfset TestFunction(Arg2=6, Arg3=99, Arg1=7)>
Note: Although you can use the Arguments scope as an array, the IsArray(Arguments) function always returns false and the cfdump tag displays the scope as a structure.

Using the Arguments scope as a structure

The following rule applies when referencing Arguments scope as a structure:

  • Use the argument names as structure keys. For example, if your function definition includes a Principal argument, reference the argument as Arguments.Principal.

    The following rules are also true, but avoid writing code that uses them. To ensure program clarity, only use the Arguments structure for arguments that you name in the function definition. Use the Arguments scope as an array for optional arguments that you do not declare in the function definition.

  • If you do not name an optional argument in the function definition, but do use a name for it in the function call, use the name specified in the function call For example, if you have an unnamed optional argument and call the function using the name myOptArg for the argument, you can reference the argument as Arguments.myOptArg in the function body. This usage, however, is poor programming practice, as it makes the function definition contents depend on variable names in the code that calls the function.

Using the Arguments scope in CFScript

A function can have optional arguments that you do not have to specify when you call the function. To determine the number of arguments passed to the function, use the following function:

ArrayLen(Arguments)

When you define a function using CFScript, the function must use the Arguments scope to retrieve the optional arguments. For example, the following SumN function adds two or more numbers together. It requires two arguments and supports any number of additional optional arguments. You can reference the first two, required, arguments as Arg1 and Arg2 or as Arguments[1] and Arguments[2]. Access the third, fourth, and any additional optional arguments as Arguments[3], Arguments[4], and so on

function SumN(Arg1,Arg2) { 
    var arg_count = ArrayLen(Arguments); 
    var sum = 0; 
    var i = 0; 
    for( i = 1 ; i LTE arg_count; i = i + 1 ) 
    { 
        sum = sum + Arguments[i]; 
    } 
    return sum;  
} 

With this function, any of the following function calls are valid:

SumN(Value1, Value2) 
SumN(Value1, Value2, Value3) 
SumN(Value1, Value2, Value3, Value4)

and so on.

The code never uses the Arg1 and Arg2 argument variables directly, because their values are always the first two elements in the Arguments array and it is simpler to step through the array. Specifying Arg1 and Arg2 in the function definition ensures that ColdFusion generates an error if you pass the function one or no arguments.

Note: Avoid referring to a required argument in the body of a function by both the argument name and its place in the Arguments scope array or structure, as doing so can be confusing and makes it easier to introduce errors.

Using the Arguments scope in cffunction definitions

When you define a function using the cffunction tag, you generally reference the arguments directly by name if all arguments are named in the cfargument tags. If you do use the Arguments scope identifier, follow the rules listed in About the Arguments scope.

For more information on using the Arguments scope in functions defined using CFScript, see Using the Arguments scope in CFScript.

Function-only variables

In addition to the Arguments scope, each function can have variables that exist only inside the function, and are not saved between times the function gets called. As soon as the function exits, all the variables in this scope are removed.

In CFScript, you create function-only variables with the var statement. Unlike other variables, you never prefix function-only variables with a scope name.

Using function-only variables

Make sure to use the var statement in CFScript UDFs to declare all function-specific variables, such as loop indexes and temporary variables that are required only for the duration of the function call. Doing so ensures that these variables are available inside the function only, and makes sure that the variable names do not conflict with the names of variables in other scopes. If the calling page has variables of the same name, the two variables are independent and do not affect each other.

For example, if a ColdFusion page has a cfloop tag with an index variable i, and the tag body calls a CFScript UDF that also has a loop with a function-only index variable i, the UDF does not change the value of the calling page loop index, and the calling page does not change the UDF index. So you can safely call the function inside the cfloop tag body.

In general, use the var statement to declare all UDF variables, other than the function arguments or shared-scope variables, that you use only inside CFScript functions. Use another scope, however, if the value of the variable must persist between function calls; for example, for a counter that the function increments each time it is called.

Referencing caller variables

A function can use and change any variable that is available in the calling page, including variables in the caller’s Variables (local) scope, as if the function was part of the calling page. For example, if you know that the calling page has a local variable called Customer_name (and no function scope variable named Customer_name exists) the function can read and change the variable by referring to it as Customer_name or (using better coding practice) Variables.Customer_name. Similarly, you can create a local variable inside a function and then use it anywhere in the calling page after the function call. You cannot use the variable before you call the function.

However, generally avoid using the caller’s variables directly inside a function. Using the caller’s variables creates a dependency on the caller. Ensure that the code outside the function uses the same variable names as the function. Doing so can become difficult if you call the function from many pages.

You can avoid these problems by using only the function arguments and the return value to pass data between the caller and the function. Do not reference calling page variables directly in the function. As a result, you can use the function anywhere in an application (or even in multiple applications), without concern for the calling code variables.

As with other programming practices, valid exceptions to this recommendation exist. For example, you can do any of the following:

  • Use a shared scope variable, such as an Application or Session scope counter variable.

  • Use the Request scope to store variables used in the function. For more information, see Using the Request scope for static variables and constants.

  • Create context-specific functions that work directly with caller data if you always synchronize variable names.

Note: If your function must directly change a simple variable in the caller (one that is not passed to the function by reference), you can place the variable inside a structure argument.

Using arguments

Function arguments can have the same names, but different values, as variables in the caller. Avoid such uses for clarity, however.

The following rules apply to argument persistence:

  • Because ColdFusion passes simple variable and array arguments by value, their names and values exist only while the function executes.

  • Because ColdFusion passes structures, queries, and objects such as COM objects by reference, the argument name exists only while the function executes, but the underlying data persists after the function returns and can be accessed by using the variable name of the caller. The variable name of the caller and the argument name can be different.

Note: If a function must use a variable from another scope that has the same name as a function-only variable, prefix the external variable with its scope identifier, such as Variables or Form. (However, remember that using variables from other scopes directly in your code is often poor practice.)