Using UDFs effectively



Many techniques help you use user-defined functions more effectively.

Using functions in ColdFusion component

In many cases, the most effective use of UDFs is within a CFC. For more information on CFCs, see Building and Using ColdFusion Components.

Using Application.cfm and function include files

Consider the following techniques for making your functions available to your ColdFusion pages:

  • If you consistently call a small number of UDFs, consider putting their definitions on the Application.cfm page.

  • If you call UDFs in only a few of your application pages, do not include their definitions in Application.cfm.

  • If you use many UDFs, place their definitions on one or more ColdFusion pages that contain only UDFs. You can include the UDF definition page in any page that calls the UDFs.

The next section describes other techniques for making UDFs available to your ColdFusion pages.

Specifying the scope of a function

User-defined function names are essentially ColdFusion variables. ColdFusion variables are names for data. Function names are names (references) for segments of CFML code. Therefore, like variables, functions belong to scopes.

About functions and scopes

Like ColdFusion variables, UDFs exist in a scope:

  • When you define a UDF, ColdFusion puts it in the Variables scope.

  • You can assign a UDF to a scope the same way you assign a variable to a scope, by assigning the function to a name in the new scope. For example, the following line assigns the MyFunc UDF to the Request scope:

    <cfset Request.MyFunc = Variables.MyFunc>

    You can now use the function from any page in the Request scope by calling Request.MyFunc.

Selecting a function scope

The following table describes the advantages and disadvantages of each function scope:

Scope

Considerations

Application

Makes the function available across all invocations of the application. Access to UDFs in Application scope is multithreaded and you can execute multiple copies of the UDF at one time.

Request

Makes the function available for the life of the current HTTP request, including in all custom tags and nested custom tags. This scope is useful if a function is used in a page and in the custom tags it calls, or in nested custom tags.

Server

Makes the function available to all pages on a single server. In most cases, this scope is not a good choice because in clustered systems, it only makes the function available on a single server, and all code that uses the function must be inside a cflock block.

Session

Makes the function available to all pages during the current user session. This scope has no significant advantages over the Application scope.

Using the Request scope

You can effectively manage functions that are used in application pages and custom tags by doing the following:

  1. Define the functions on a function definitions page.

  2. On the functions page, assign the functions to the request scope.

  3. Use a cfinclude tag to include the function definition page on the application page, but do not include it on any custom tag pages.

  4. Always call the functions using the request scope.

This way you only include the functions once per request and they are available throughout the life of the request. For example, create a myFuncs.cfm page that defines your functions and assigns them to the Request scope using syntax such as the following:

function MyFunc1(Argument1, Argument2) 
{ Function definition goes here } 
Request.MyFunc1 = MyFunc1

The application page includes the myFuncs.cfm page:

<cfinclude template="myfuncs.cfm">

The application page and all custom tags (and nested custom tags) call the functions as follows:

Request.MyFunc1(Value1, Value2)

Using the Request scope for static variables and constants

You can partially break the rule described in the section Referencing caller variables. Here, the function defines variables in the Request scope. However, it is a specific solution to a specific issue, where the following circumstances exist:

  • Your function initializes a large number of variables.

  • The variables have either of the following characteristics:

    • They must be static: only the function uses them, the function can change their values, and their values must persist from one invocation of the function to the next.

    • They are named constants; that is the variable value never changes.

  • Your application page (and any custom tags) calls the function multiple times.

  • You can assure that only the function uses the variable names.

In these circumstances, you can improve efficiency and save processing time by defining your function’s variables in the Request scope, rather than the Function scope. The function tests for the Request scope variables and initializes them if they do not exist. In subsequent calls, the variables exist and the function does not reset them.

The NumberAsString function, written by Ben Forta and available from www.cflib.org, takes advantage of this technique.

Using function names as function arguments

Because function names are ColdFusion variables, you can pass a function’s name as an argument to another function. This technique allows a function to use another function as a component. For example, a calling page can call a calculation function, and pass it the name of a function that does some subroutine of the overall function.

This way, the calling page could use a single function for different specific calculations, such as calculating different forms of interest. The initial function provides the framework, while the function whose name is passed to it can implement a specific algorithm that the calling page requires.

The following simple example shows this use. The binop function is a generalized function that takes the name of a function that performs a specific binary operation and two operands. The binop function simply calls the specified function and passes it the operands. This code defines a single operation function, the sum function. A more complete implementation would define multiple binary operations.

<cfscript> 
function binop(operation, operand1, operand2) 
{ return (operation(operand1, operand2)); } 
function sum(addend1, addend2) 
{ return addend1 + addend2;} 
x = binop(sum, 3, 5); 
writeoutput(x); 
</cfscript>

Handling query results using UDFs

When you call a UDF in the body of a tag that has a query attribute, such as a cfloop tag, any function argument that is a query column name passes a single element of the column, not the entire column. Therefore, the function must manipulate a single query element.

For example, the following code defines a function to combine a single first name and last name to make a full name. It queries the cfdocexamples database to get the first and last names of all employees, and then it uses a cfoutput tag to loop through the query and call the function on each row in the query.

<cfscript> 
function FullName(aFirstName, aLastName) 
    { return aFirstName & " " & aLastName; } 
</cfscript> 
 
<cfquery name="GetEmployees" datasource="cfdocexamples">  
    SELECT FirstName, LastName 
    FROM Employee 
</cfquery> 
 
<cfoutput query="GetEmployees"> 
#FullName(FirstName, LastName)#<br> 
</cfoutput>

You generally use functions that manipulate many rows of a query outside tags that loop over queries. Pass the query to the function and loop over it inside the function. For example, the following function changes text in a query column to uppercase. It takes a query name as an argument.

function UCaseColumn(myquery, colName) { 
    var currentRow = 1; 
    for (; currentRow lte myquery.RecordCount; currentRow = currentRow + 1) 
    { 
        myquery[colName][currentRow] = UCase(myquery[colName][currentRow]); 
    } 
    Return ""; 
}

The following code uses a script that calls the UCaseColumn function to convert all the last names in the GetEmployees query to uppercase. It then uses cfoutput to loop over the query and display the contents of the column.

<cfscript> 
    UCaseColumn(GetEmployees, "LastName"); 
</cfscript> 
<cfoutput query="GetEmployees"> 
    #LastName#<br> 
</cfoutput>

Identifying and checking for UDFs

You can use the IsCustomFunction function to determine whether a name represents a UDF. The IsCustomFunction function generates an error if its argument does not exist. As a result, ensure that the name exists before calling the function, for example, by calling the IsDefined function. The following code shows this use:

<cfscript> 
if(IsDefined("MyFunc")) 
    if(IsCustomFunction(MyFunc)) 
        WriteOutput("MyFunc is a user-defined function"); 
    else 
        WriteOutput("Myfunc is defined but is NOT a user-defined function"); 
else 
    WriteOutput("MyFunc is not defined"); 
</cfscript>

You do not surround the argument to IsCustomFunction in quotation marks, so you can use this function to determine if function arguments are themselves functions.

Using the Evaluate function

If your user-defined function uses the Evaluate function on arguments that contain strings, Make sure that all variable names you use as arguments include the scope identifier. Doing so avoids conflicts with function-only variables.

The following example returns the result of evaluating its argument. It produces the expected results, the value of the argument, if you pass the argument using its fully scoped name, Variables.myname. However, the function returns the value of the function local variable if you pass the argument as myname, without the Variables scope identifier.

<cfscript> 
    myname = "globalName"; 
    function readname(name) { 
        var myname = "localName"; 
        return (Evaluate(name)); 
    } 
</cfscript> 
 
<cfoutput> 
<!--- This one collides with local variable name. ---> 
    The result of calling readname with myname is:  
        #readname("myname")# <br> 
<!--- This one finds the name passed in. ---> 
    The result of calling readname with Variables.myname is:  
        #readname("Variables.myname")#  
</cfoutput>

Using recursion

A recursive function is a function that calls itself. Recursive functions are useful when an algorithm that repeats the same operation multiple times using the results of the preceding repetition can solve the problem. Factorial calculation, used in the following example, is one case where recursion is useful. The Towers of Hanoi game is also solved using a recursive algorithm.

A recursive function, like looping code, must have an end condition that always stops the function. Otherwise, the function continues until a system error occurs or you stop the ColdFusion server.

The following example calculates the factorial of a number, that is, the product of all the integers from 1 through the number; for example, 4 factorial is 4 X 3 X 2 X 1 = 24.

function Factorial(factor) { 
    If (factor LTE 1)  
        return 1; 
    else  
        return factor * Factorial(factor -1); 
}

If the function is called with a number greater than 1, it calls itself using an argument one less than it received. It multiplies that result by the original argument, and returns the result. Therefore, the function keeps calling itself until the factor is reduced to 1. The final recursive call returns 1, and the preceding call returns 2 * 1, and so on, until all the initial call returns the end result.

Important: If a recursive function calls itself too many times, it causes a stack overflow. Always test any recursive functions under conditions that are likely to cause the maximum number of recursions to ensure that they do not cause a stack overflow.