Skip to main content
Previous sectionNext section

Callable User-defined Code Modules

This chapter describes how to create and invoke user-defined modules of ObjectScript code. These units of code can be user-defined functions, procedures, methods, routines, or subroutines.

As in other languages, ObjectScript allows you to create named code blocks that you can invoke directly. Such blocks are known as procedures. Strictly speaking, in ObjectScript terminology, a code block that is a procedure has a specific syntax and structure.

The syntax of a procedure definition is as follows:

ProcedureName(Parameters) [PublicVariables] 
 {

    /* code goes here */

    QUIT ReturnValue
 }
Copy code to clipboard

The elements of the procedure, here called ProcedureName, are:

  • Parameters (zero or more) — These can be of any type and, as is typical of ObjectScript, you do not need to declare their types when you define the procedure. By default, they are passed by value (not by reference). Unless otherwise specified, their scope is local to the procedure. For more information on parameters generally, see the section “Procedure Parameters.”

  • References to public variables (zero or more) — These, too, can be of any type. The procedure can both reference and set such a variable’s value. For more information on public variable references, see the section “Procedure Variables.”

  • Declaration that the procedure is public (optional) — By default, procedures are private, which means that you can only call them from elsewhere in the same routine (in ObjectScript terminology, a routine is a file containing one or more procedures or other user-defined code blocks). You can also create procedures that are public, using the PUBLIC keyword after the procedure name. Public procedures can be called from other routines or methods. For more information on public and private procedures, see “Public and Private Procedures.”

  • Code — The code in a procedure has all the features available in ObjectScript. Procedure code can also include Basic and Java. The code is delimited by curly braces and is also known as a procedure block.

  • Return value (optional) — This is the value that the procedure returns, and, must be a standard ObjectScript expression. Flow control within a procedure can specify various return values using computed expression values, multiple QUIT statements, or both.

Note:

For those familiar with versions of Caché before version 5 (and code written for those versions), procedures represent an advance from coding that was previously available with subroutines and user-defined functions. Procedure parameters are automatically local in scope within the procedure. They do not require a NEW command to ensure that they do not overwrite other values, since they are private to the procedure and do not interact with the symbol table. Also, the explicit declaration of public variables allows you to refer to global variables within an application, such a bank-wide interest rate; it also allows you to create and set values for variables within the procedure that are available to the rest of an application.

Procedures are a particular kind of ObjectScript routine.

Caché also provides a large number of system-supplied functions, all of which are described in the ObjectScript Language Reference; these are sometimes known as intrinsic functions. Calls to system functions are identified by a “$” prefix.

Procedures, Routines, Subroutines, Functions, and Methods: What Are They?

This chapter describes how to implement your own code using procedures, which are the recommended form for implementing user-defined functionality. Caché documentation as a whole talks about procedures, routines, subroutines, functions, and methods. Though all these entities share features, each has its own characteristics. Most notably, applications created before the release Caché 5.0 often include subroutines and functions that are not implemented as procedures. For that reason, it is important to provide a brief overview of all of these different structures for implementing your own code.

The most flexible, most powerful, and recommended form of named, user-defined code block is the procedure. The features of a procedure includes that it:

  • Can be private or public.

  • Can accept zero or more parameters.

  • Automatically maintains any variables created within it as local in scope.

  • Can refer to and alter variables outside it.

  • Can return a value of any type or no return value.

Important:

By default, methods are procedures. Because of this, all content on procedures in this chapter also describes methods. For more information about methods, see the chapter “Methods” in the book Using Caché Objects.

By contrast:

  • A subroutine is always public and cannot return a value.

  • A function is always public, requires explicit declaration of local variables (and, otherwise, overwrites external variables), and must have a return value.

  • By default, a method is a procedure that is specified as part of a class definition and that you can invoke on one or more objects or on a class. If you explicitly declare it a function, it is then a function with all the accompanying characteristics; this is not recommended.

  • A routine is an ObjectScript program. It can include one or more procedures, subroutines, and functions, as well as any combination of the three.

Note:

ObjectScript also supports a related form of user-defined code through its macro facility.

Routines

A routine is a callable block of user-written code that is an ObjectScript program. A routine performs commonly needed operations. Its name is determined by the name of the .MAC file that you choose for saving it. Depending on if a routine returns a value, you can invoke a routine with one or both of the following sets of syntax:

   DO ^RoutineName
   SET x = $$^RoutineName
Copy code to clipboard

A routine is defined within a namespace. You can use an extended routine reference to execute a user-defined routine defined in a namespace other than the current namespace:

  DO ^|"SAMPLES"|RoutineName
Copy code to clipboard

Generally, routines serve as containers for subroutines, methods, and procedures.

The routine is identified by a label (also referred to as a tag) at the beginning of the block of code. This label is the name of the routine. This label is (usually) followed by parentheses which contain a list of parameters to be passed from the calling program to the routine.

When you save a routine to a file, the file name cannot include the underscore (“_”), hyphen (“-”), or semicolon (“;”) characters; names that include such characters are not valid.

Subroutines

A subroutine is a named block of code within a routine. Typically, a subroutine begins with label and ends with a QUIT statement It can accept parameters and does not return a value. To invoke a subroutine, use the following syntax:

DO Subroutine^Routine

where Subroutine is a code block within the Routine file (Routine.MAC).

The form of a subroutine is:

Label(parameters) // comment
 // code
 QUIT // note that QUIT has no arguments
Copy code to clipboard

For more details on subroutines, see the section below on subroutines as legacy code.

If you enclose the code and QUIT statement within curly braces, the subroutine is a procedure and can be treated as such.

Functions

Caché comes with many system-supplied functions (sometimes known as “intrinsic” functions), which are described in the ObjectScript Language Reference. This section describes user-defined (“extrinsic”) functions.

A function is a named block of code within a routine. Typically, a function begins with label and ends with a QUIT statement. It can accept parameters and can also return a value. To invoke a function, there are two valid forms of the syntax:

SET rval=$$Function()  /* returning a value */  
DO Function^Routine   /* ignoring the return value */ 

where Function is a code block within the Routine file (Routine.MAC). In both syntactic forms you can use an extended routine reference to execute a function located in a different namespace.

The form of a function is:

Label(parameters)
 // code
 QUIT ReturnValue
Copy code to clipboard

If you enclose the code and QUIT statement within curly braces, the function is a procedure and can be treated as such. Note that because a procedure is private by default, you may wish to specify the PUBLIC keyword, as follows:

Label(parameters) PUBLIC {
 // code
 QUIT ReturnValue }
Copy code to clipboard

The following example defines a simple function (MyFunc) and calls it, passing two parameters and receiving a return value:

Main ;
   TRY {
     KILL x
     SET x=$$MyFunc(7,10)
        WRITE "returned value is ",x,!
        RETURN
   }
   CATCH { WRITE $ZERROR,!
   }
MyFunc(a,b) 
   SET c=a+b
   QUIT c   
Copy code to clipboard

The code invoking the function can ignore the return value, but a function’s QUIT command must specify a return value. Attempting to exit a function with an argumentless QUIT generates a <COMMAND> error. The <COMMAND> error specifies the location of the call that invoked the function, followed by a message that specifies the offset location of the argumentless QUIT command within the called function. Refer to $ZERROR for further details.

For more details on functions, see the section below on functions as legacy code.

Defining Procedures

As in other languages, a procedure is a series of ObjectScript commands (a section of a larger routine) that accomplishes a specific task. Similar to constructs such as If, the code of a procedure is contained within curly braces.

Procedures allow you to define each variable as either public or private. For example, the following procedure, is called “MyProc”:

MyProc(x,y) [a,b] PUBLIC {
 Write "x + y = ", x + y
}
Copy code to clipboard

defines a public procedure named “MyProc” which takes two parameters, x and y. It defines two public variables, a and b. All other variables used in the procedure (in this case, x and y) are private variables.

By default, procedures are private, which means that you can only call them from elsewhere in the same routine. You can also create procedures that are public, using the Public keyword after the procedure name. Public procedures can be called from other routines.

Procedures need not have defined parameters. To create procedures with parameters, place a parenthesized list of variables immediately after the label.

Invoking Procedures

To invoke a procedure, either issue a DO command that specifies the procedure, or call it as a function using the “$$” syntax. You can control whether a procedure can be invoked from any program (public), or only from the program in which it is located (private). If invoked with DO, a procedure does not return a value; if invoked as a function call, a procedure returns a value. The “$$” form provides the most functionality, and is generally the preferred form.

Using the $$ Prefix

You can invoke a user-defined function in any context in which an expression is allowed. A user-defined function call takes the form:

$$name([param[ ,...]])

where:

  • name specifies the name of the function. Depending on where the function is defined, name can be specified as:

    • label is a line label within the current routine.

    • label^routine is a line label within the named routine that resides on disk.

    • ^routine is a routine that resides on disk. The routine must contain only the code for the function to be performed.

  • param specifies the values to be passed to the function. The supplied parameters are known as the actual parameter list. They must match the formal parameter list defined for the function. For example, the function code may expect two parameters, with the first being a numeric value and the second being a string literal. If you specify the string literal for the first parameter and the numeric value for the second, the function may yield an incorrect value or possibly generate an error. Parameters in the formal parameter list always have NEW invoked by the function. See the NEW command. Parameters can be passed by value or by reference. See Parameter Passing. If you pass fewer parameters to the function than are listed in the function’s formal parameter list, parameter defaults are used (if defined); if there are no defaults, these parameters remain undefined.

Using the DO Command

You can invoke a user-defined function using the DO command. (You cannot invoke a system-supplied function using the DO command.) A function invoked using DO does not return a value. That is, the function must generate a return value, but the DO command ignores this return value. This greatly limits the use of DO for invoking user-defined functions.

To invoke a user-defined function using DO, you issue a command in the following syntax:

DO label(param[,...])

The DO command calls the function named label and passes it the parameters (if any) specified by param. Note that the $$ prefix is not used, and that the parameter parentheses are mandatory. The same rules apply for specifying the label and param as when invoking a user-defined function using the $$ prefix.

A function must always return a value. However, when a function is called with DO, this returned value is ignored by the calling program.

Procedure Syntax

Procedure syntax:

 label([param[=default]][,...]) [[pubvar[,...]]] [access] {
   code 
} 
Copy code to clipboard

Invoking syntax:

DO label([param[,...]]) 
Copy code to clipboard

or

 command $$label([param][,...]) 
Copy code to clipboard

where

label The procedure name. A standard label. It must start in column one. The parameter parentheses following the label are mandatory.
param A variable for each parameter expected by the procedure. These expected parameters are known as the formal parameter list. The parameters themselves are optional (there may be none, one, or more than one param) but the parentheses are mandatory. Multiple param values are separated by commas. Parameters may be passed to the formal parameter list by value or by reference. Procedures that are routines do not include type information about their parameters; procedures that are methods do include this information. The maximum number of formal parameters is 255.
default An optional default value for the param preceding it. You can either provide or omit a default value for each parameter. A default value is applied when no actual parameter is provided for that formal parameter, or when an actual parameter is passed by reference and the local variable in question does not have a value. This default value must be a literal: either a number, or a string enclosed in quotation marks. You can specify a null string (“”) as a default value. This differs from specifying no default value, because a null string defines the variable, whereas the variable for a parameter with no specified or default value would remain undefined. If you specify a default value that is not a literal, Caché issues a <PARAMETER> error.
pubvar Public variables. An optional list of public variables used by the procedure and available to other routines and procedures. This is a list of variables both defined within this procedure and available to other routines and defined within another routine and available to this procedure. If specified, pubvar is enclosed in square brackets. If no pubvar is specified, the square brackets may be omitted. Multiple pubvar values are separated by commas. All variables not declared as public variables are private variables. Private variables are available only to the current invocation of the procedure. They are undefined when the procedure is invoked, and destroyed when the procedure is exited with a QUIT. If the procedure calls any code outside of that procedure, the private variables are preserved, but are unavailable until the call returns to the procedure. All % variables are always public, whether or not they are listed here. The list of public variables can include one or more of the param specified for this routine.
access An optional keyword that declares whether the procedure is public or private. There are two available values: PUBLIC, which declares that this procedure can be called from any routine. PRIVATE, which declares that this procedure can only be called from the routine in which it is defined. PRIVATE is the default.
code A block of code, enclosed in curly braces. The opening curly brace ({) must be separated from the characters preceding and following it by at least one space or a line break. The closing curly brace (}) must not be followed by any code on the same line; it can only be followed by blank space or a comment. The closing curly brace can be placed in column one. This block of code is only entered by the label.

You cannot insert a line break between a command and its arguments.

Each procedure is implemented as part of a routine; each routine can contain multiple procedures.

In addition to standard ObjectScript syntax, there are special rules governing routines. A line in a routine can have a label at the beginning (also called a tag), ObjectScript code, and a comment at the end; but all of these elements are optional.

InterSystems recommends that the first line of a routine have a label matching the name of the routine, followed by a tab or space, followed by a short comment explaining the purpose of the routine. If a line has a label, you must separate it from the rest of the line with a tab or a space. This means that as you add lines to your routine using Studio, you either type a label and a tab/space, followed by ObjectScript code, or you skip the label and type a tab or space, followed by ObjectScript. So in either case, every line must have a tab or space before the first command.

To denote a single-line comment use either a double slash (“//”) or a semicolon (“;”). If a comment follows code, there must be a space before the slashes or semicolon; if the line contains only a comment, there must be a tab or space before the slashes or semicolon. By definition, there can be no line break within a single-line comment; for a multiline comment, mark the beginning of the comment with “/*” and the end with “*/”.

Procedure Variables

Procedures and methods both support private and public variables; all of the following statements apply equally to procedures and methods:

Variables used within procedures are automatically private to that procedure. Hence, you do not have to declare them as such and they do not require a NEW command. To share some of these variables with procedures that this procedure calls, pass them as parameters to the other procedures.

You can also declare public variables. These are available to all procedures and methods; those that this procedure or method calls and those that called this procedure or method. A relatively small number of variables should be defined in this way, to act as environmental variables for an application. To define public variables, list them in square brackets following the procedure name and its parameters.

SAMPLES>WRITE

SAMPLES>DO ^publicvarsexample

setting a
setting b
setting c
The sum is: 6
SAMPLES>WRITE

a=1
b=2
SAMPLES>
Copy code to clipboard

The publicvarsexample.mac code:

publicvarsexample
    ; examples of public variables
    ;
    DO proc1()   ; call a procedure
    QUIT    ; end of the main routine
    ;
proc1() [a, b]
    ; a private procedure
    ; "c" and "d" are private variables
    {
    WRITE !, "setting a"  SET a = 1
    WRITE !, "setting b"  SET b = 2
    WRITE !, "setting c"  SET c = 3
    SET d = a + b + c
    WRITE !, "The sum is: ", d
    }
Copy code to clipboard

Public versus Private Variables

Within a procedure, local variables may be either “public” or “private”. The public list [pubvar] declares which variable references in the procedure are added to the set of public variables; all other variable references in the procedure are to a private set seen only by the current invocation of the procedure.

Private variables are undefined when a procedure is entered, and they are destroyed when a procedure is exited with a QUIT.

When code within a procedure calls any code outside of that procedure, the private variables are restored upon the return to the procedure. The called procedure or routine has access to public variables (as well as its own private ones.) Thus, [pubvar] specifies both the public variables seen by this procedure and the variables used in this procedure that are capable of being seen by a routine that the procedure calls.

If the public list is empty, then all variables are private. In this case, the square brackets are optional.

Variables whose name starts with the “%” character are typically variables used by the system or for some special purpose. Caché reserves all % variables (except %z and %Z variables) for system use; user code should only use % variables that begin with %z or %Z. All % variables are implicitly public. They can be listed in the public list (for documentation purposes) but this is not necessary.

Private Variables versus Variables Created with NEW

Note that private variables are not the same as variables newly created with NEW. If a procedure wants to make a variable directly available to other procedures or subroutines that it calls, then it must be a public variable and it must be listed in the public list. If it is a public variable being introduced by this procedure, then it makes sense to perform a NEW on it. That way it will be automatically destroyed when we QUIT the procedure, and also it protects any previous value that public variable may have had. For example, the code:

MyProc(x,y)[name]{
 NEW name
 SET name="John"
 DO xyz^abc
 QUIT
}
Copy code to clipboard

enables procedure “xyz” in routine “abc” to see the value “John” for name, because it is public. Invoking the NEW command for name protects any public variable named “name” that may already have existed when the procedure “MyProc” was called.

The NEW command does not affect private variables; it only works on public variables. Within a procedure, it is illegal to specify NEW x or NEW (x) if x is not listed in the public list and x is not a % variable.

Making Formal List Parameters Public

If a procedure has a formal list parameter, (such as “x” or “y” in MyProc(x,y) ) that is needed by other procedures it calls, then the parameter should be listed in the public list.

Thus,

MyProc(x,y)[x] {
 DO abc^rou
 }
Copy code to clipboard

makes the value of x, but not y, available to the routine “abc^rou”.

Public and Private Procedures

A procedure can be “public” or “private”. A private procedure can only be called from within the routine in which the procedure is defined, whereas a public procedure can be called from any routine. If the PUBLIC and PRIVATE keywords are omitted, the default is “private”.

For instance,

MyProc(x,y) PUBLIC { }
Copy code to clipboard

defines a public procedure, while

MyProc(x,y) PRIVATE { }
Copy code to clipboard

and

MyProc(x,y) { }
Copy code to clipboard

both define a private procedure.

Parameter Passing

An important features of procedures is their support for parameter passing. This is the mechanism by which you can pass values (or variables) to a procedure as parameters. Of course, parameter passing is not required; for example, procedures with no parameter passing could be used to generate a random number or to return the system date in a format other than the default format. Commonly, however, procedures do use parameter passing.

To set up parameter passing, specify:

  • An actual parameter list on the procedure call.

  • A formal parameter list on the procedure definition.

When Caché executes a user-defined procedure, it maps the parameters in the actual list, by position, to the corresponding parameters in the formal list. Thus, the value of the first parameter in the actual list is placed in the first variable in the formal list; the second value is placed in the second variable; and so on. The matching of these parameters is done by position, not name. Thus, the variables used for the actual parameters and the formal parameters are not required to have (and usually should not have) the same names. The procedure accesses the passed values by referencing the appropriate variables in its formal list.

The actual parameter list and the formal parameter list may differ in the number of parameters:

  • If the actual parameter list has fewer parameters than the formal parameter list, the unmatched elements in the formal parameter list are undefined. You can specify a default value for an undefined formal parameter, as shown in the following example:

    Main
       /* Passes 2 parameters to a procedure that takes 3 parameters */
       SET a="apple",b="banana",c="carrot",d="dill"
       DO ListGroceries(a,b)
       WRITE !,"all done"
    ListGroceries(x="?",y="?",z="?") {
       WRITE x," ",y," ",z,!   }
    Copy code to clipboard
  • If the actual parameter list has more parameters than the formal parameter list, a <PARAMETER> error occurs, as shown in the following example:

    Main
       /* Passes 4 parameters to a procedure that takes 3 parameters. 
          This results in a <PARAMETER> error  */
       SET a="apple",b="banana",c="carrot",d="dill"
       DO ListGroceries(a,b,c,d)
       WRITE !,"all done"
    ListGroceries(x="?",y="?",z="?") {
       WRITE x," ",y," ",z,!   }
    Copy code to clipboard

If there are more variables in the formal list than there are parameters in the actual list, and a default value is not provided for each, the extra variables are left undefined. Your procedure code should include appropriate IF tests to make sure that each procedure reference provides usable values. To simplify matching the number of actual parameters and formal parameters, you can specify a variable number of parameters.

The maximum number of actual parameters is 254.

When passing parameters to a user-defined procedure, you can use passing by value or passing by reference. You can mix passing by value and passing by reference within the same procedure call.

  • Procedures can be passed parameters by value or by reference.

  • Subroutines can be passed parameters by value or by reference.

  • User-defined functions can be passed parameters by value or by reference.

  • System-supplied functions can be passed parameters by value only.

Passing By Value

To pass by value, specify a literal value, an expression, or a local variable (subscripted or unsubscripted) in the actual parameter list. In the case of an expression, Caché first evaluates the expression and then passes the resulting value. In the case of a local variable, Caché passes the variable’s current value. Note that all specified variable names must exist and must have a value.

The procedure’s formal parameter list contains unsubscripted local variable names. It cannot specify an explicit subscripted variable. However, specifying a variable number of parameters implicitly creates subscripted variables.

Caché implicitly creates and declares any non-public variables used within a procedure, so that already-existing variables with the same name in calling code are not overwritten. It places the existing values for these variables (if any) on the program stack. When it invokes the QUIT command, Caché executes an implicit KILL command for each of the formal variables and restores their previous values from the stack.

In the following example, the SET commands use three different forms to pass the same value to the referenced Cube procedure.

 DO Start()
 WRITE "all done"
Start() PUBLIC {
 SET var1=6
 SET a=$$Cube(6)
 SET b=$$Cube(2*3)
 SET c=$$Cube(var1)
 WRITE !,"a: ",a," b: ",b," c: ",c,!
 QUIT 1
 }
Cube(num) PUBLIC {
 SET result = num*num*num
 QUIT result
}
Copy code to clipboard

Passing By Reference

To pass by reference, specify a local variable name or the name of an unsubscripted array in the actual parameter list, using the form:

.name
Copy code to clipboard

With passing by reference, a specified variable or array name does not have to exist before the procedure reference. Passing by reference is the only way you can pass an array name to a procedure. Note that you cannot pass a subscripted variable by reference.

  • Actual parameter list: The period preceding the local variable or array name in the actual parameter list is required. It specifies that the variable is being passed by reference, not passed by value.

  • Formal parameter list: No special syntax is required in the formal parameter list to receive a variable passed by reference. The period prefix is not permitted in the formal parameter list. However, an ampersand (&) prefix is permitted before the name of a variable in the formal parameter list; by convention this & prefix is used to indicate that this variable is being passed in by reference. The & prefix is optional and performs no operation; it is a useful convention for making your source code easier to read and maintain.

In passing by reference, each variable or array name in the actual list is bound to the corresponding variable name in the function’s formal list. Passing by reference provides an effective mechanism for two-way communication between the referencing routine and the function. Any change that the function makes to a variable in its formal list is also made to the corresponding by-reference variable in the actual list. This also applies to the KILL command. If a by-reference variable in the formal list is killed by the function, the corresponding variable in the actual list is also killed.

If a variable or array name specified in the actual list does not already exist, the function reference treats it as undefined. If the function assigns a value to the corresponding variable in the formal list, the actual variable or array is also defined with this value.

The following example compares passing by reference with passing by value. The variable a is passed by value, the variable b is passed by reference:

Main
   SET a="6",b="7"
     WRITE "Initial values:",!
     WRITE "a=",a," b=",b,!
   DO DoubleNums(a,.b)
     WRITE "Returned to Main:",!
     WRITE "a=",a," b=",b
DoubleNums(foo,&bar) {
  WRITE "Doubled Numbers:",!
  SET foo=foo*2
  SET bar=bar*2
  WRITE "foo=",foo," bar=",bar,!
  QUIT
  }
Copy code to clipboard

The following example uses passing by reference to achieve two-way communication between the referencing routine and the function through the variable result. The period prefix specifies that result is passed by reference. When the function is executed, result in the actual parameter list is created and bound to z in the function’s formal parameter list. The calculated value is assigned to z and passed back to the referencing routine in result. The & prefix to z in the formal parameter list is optional and non-functional, but helps to clarify the source code. Note that num and powr are passed by value, not reference. This is an example of mixing passing by value and passing by reference:

Start ; Raise an integer to a power.
 READ !,"Integer= ",num  QUIT:num="" 
 READ !,"Power= ",powr   QUIT:powr=""
 SET output=$$Expo(num,powr,.result)
 WRITE !,"Result= ",output
 GOTO Start
Expo(x,y,&z)
 SET z=x
 FOR i=1:1:y {SET z=z*x}
 QUIT z
Copy code to clipboard

Variable Number of Parameters

A procedure can specify that it accepts a variable number of parameters. You do this by appending three dots to the name of the final parameter; for example, vals.... This parameter must be the final parameter in the parameter list. It can be the only parameter in the parameter list. This ... syntax can pass multiple parameters, a single parameter, or zero parameters.

Spaces and new lines are permitted between parameters in the list, as well as before the first parameter and after the final parameter in the list. Whitespace is not permitted between the three dots.

To use this syntax, specify a signature where the name of the final parameter is followed by .... The multiple parameters passed to the method through this mechanism can have values from data types, be object-valued, or be a mix of the two. The parameter that specifies the use of a variable number of parameters can have any valid identifier name.

ObjectScript handles passing a variable number of parameters by creating a subscripted variable, creating one subscript for each passed variable. The top level of the variable contains the number of parameters passed. The variable subscripts contain the passed values.

This is shown in the following example. It uses invals... as the only parameter in the formal parameter list. ListGroceries(invals...) receives a variable number of values passed by value:

Main
   SET a="apple",b="banana",c="carrot",d="dill",e="endive"
   DO ListGroceries(a,b,c,d,e)
   WRITE !,"all done"
ListGroceries(invals...) {
  WRITE invals," parameters passed",!
   FOR i=1:1:invals {
      WRITE invals(i),! }
   }
Copy code to clipboard

The following example uses morenums... as the final parameter, following two defined parameters. This procedure can receive a variable number of additional parameters, starting with the third parameter. The first two parameters are required, either as defined parameters DO AddNumbers(a,b,c,d,e) or as placeholder commas DO AddNumbers(,,c,d,e):

Main
   SET a=7,b=8,c=9,d=100,e=2000
   DO AddNumbers(a,b,c,d,e)
   WRITE "all done"
AddNumbers(x,y,morenums...) {
  SET sum = x+y
  FOR i=1:1:$GET(morenums, 0) {
     SET sum = sum + $GET(morenums(i)) }
  WRITE "The sum is ",sum,!
  QUIT
  }
Copy code to clipboard

The following example uses morenums... as the final parameter, following two defined parameters. This procedure receives exactly two parameter values; the morenums... variable number of additional parameters is 0:

Main
   SET a=7,b=8,c=9,d=100,e=2000
   DO AddNumbers(a,b)
   WRITE "all done"
AddNumbers(x,y,morenums...) {
  SET sum = x+y
  FOR i=1:1:$GET(morenums, 0) {
     SET sum = sum + $GET(morenums(i)) }
  WRITE "The sum is ",sum,!
  QUIT
  }
Copy code to clipboard

As specified, AddNumbers(x,y,morenums...) can receive a minimum of two parameters and a maximum of 255. If you supply defaults for the defined parameters AddNumbers(x=0,y=0,morenums...) this procedure can receive a minimum of no parameters and a maximum of 255.

The following example uses nums... as the only parameter. It receives a variable number of values passed by reference:

Main
   SET a=7,b=8,c=9,d=100,e=2000
   DO AddNumbers(.a,.b,.c,.d,.e)
   WRITE "all done"
AddNumbers(&nums...) {
  SET sum = 0
  FOR i=1:1:$GET(nums, 0) {
     SET sum = sum + $GET(nums(i)) }
  WRITE "The sum is ",sum,!
  QUIT sum
  }
Copy code to clipboard

When a variable parameter list params... receives parameters passed by reference and passes the params... to a routine, the intermediate routine can add additional parameters (additional nodes in the params array) that will also be passed by reference. This is shown in the following example:

Main
   SET a(1)=10,a(2)=20,a(3)=30
   DO MoreNumbers(.a)
   WRITE !,"all done"
MoreNumbers(&params...) {
   SET params(1,6)=60
   SET params(1,8)=80
   DO ShowNumbers(.params) }
ShowNumbers(&tens...) {
   SET key=$ORDER(tens(1,1,""),1,targ)
   WHILE key'="" {
     WRITE key," = ",targ,!
     SET key=$ORDER(tens(1,1,key),1,targ)
   }
  }
Copy code to clipboard

The following example shows that this args... syntax can be used in both the formal parameter list and in the actual parameter list. In this example, a variable number of parameters (invals...) are passed by value to ListNums, which doubles their values then passes them as invals... to ListDoubleNums:

Main
   SET a="1",b="2",c="3",d="4"
   DO ListNums(a,b,c,d)
   WRITE !,"back to Main, all done"
ListNums(invals...) {
  FOR i=1:1:invals {
    WRITE invals(i),!
    SET invals(i)=invals(i)*2 }
  DO ListDoubleNums(invals...)
  WRITE "back to ListNums",!
ListDoubleNums(twicevals...)
  WRITE "Doubled Numbers:",!
  FOR i=1:1:twicevals {
    WRITE twicevals(i),! }
  QUIT
  }
Copy code to clipboard

For specifics about methods that can accept a variable number of parameters, see the section “Variable Numbers of Arguments in Methods” in the “Methods” chapter of Using Caché Objects.

Procedure Code

The body of code between the braces is the procedure code, and it differs from traditional ObjectScript code in the following ways:

  • A procedure can only be entered at the procedure label. Access to the procedure through “label+offset” syntax is not allowed.

  • Any labels in the procedure are private to the procedure and can only be accessed from within the procedure. The PRIVATE keyword can be used on labels within a procedure, although it is not required. The PUBLIC keyword cannot be used on labels within a procedure — it yields a syntax error. Even the system function $TEXT cannot access a private label by name, although $TEXT does support label+offset using the procedure label name.

  • Duplicate labels are not permitted within a procedure but, under certain circumstances, are permitted within a routine. Specifically, duplicate labels are permitted within different procedures. Also, the same label can appear within a procedure and elsewhere within the routine in which the procedure is defined. For instance, the following three occurrences of “Label1” are permitted:

    Rou1 // Rou1 routine
    Proc1(x,y) { 
    Label1 // Label1 within the proc1 procedure within the Rou1 routine
    } 
    
    Proc2(a,b,c) { 
    Label1 // Label1 within the Proc2 procedure (local, as with previous Label1)
    } 
    
    Label1 // Label1 that is part of Rou1 and neither procedure
    Copy code to clipboard
  • If the procedure contains a DO command or user-defined function without a routine name, it refers to a label within the procedure, if one exists. Otherwise, it refers to a label in the routine but outside of the procedure.

  • If the procedure contains a DO or user-defined function with a routine name, it always identifies a line outside of the procedure. This is true even if that name identifies the routine that contains the procedure. For example:

    ROU1 ;
    PROC1(x,y) {
     DO Label1^ROU1
    Label1 ;
     }
    Label1 ; The DO calls this label
    Copy code to clipboard
  • If a procedure contains a GOTO, it must be to a private label within the procedure. You cannot exit a procedure with a GOTO.

  • "label+offset" syntax is not supported within a procedure, with a few exceptions:

    • $TEXT supports label+offset from the procedure label.

    • GOTO label+offset is supported in direct mode lines from the procedure label as a means of returning to the procedure following a Break or error.

    • The ZBREAK command supports a specification of label+offset from the procedure label.

    • The $TEST state in effect when the procedure was called is restored upon the QUIT for the procedure.

    • The “}” that denotes the end of the procedure can be in any character position on the line, including the first character position. Code can precede the “}” on the line, but cannot follow it on the line.

    • An implicit QUIT is present just before the closing brace.

    • Indirection and XECUTE commands behave as if they are outside of a procedure.

Indirection, XECUTE Commands, and JOB Commands within Procedures

Name indirection, argument indirection, and XECUTE commands that appear within a procedure are not executed within the scope of the procedure. Thus, XECUTE acts like an implied DO of a subroutine that is outside of the procedure.

Indirection and XECUTE only access public variables. As a result, if indirection or an XECUTE references a variable x, then it references the public variable x regardless of whether or not there is also a private x in the procedure. For example:

 SET x="set a=3" XECUTE x ; sets the public variable a to 3
 SET x="label1" DO @x ; accesses the public subroutine label1
Copy code to clipboard

Similarly, a reference to a label within indirection or an XECUTE is to a label outside of the procedure. Hence GOTO @A is not supported within a procedure, since a GOTO from within a procedure must be to a label within the procedure.

Other parts of the documentation contain more detail on indirection and the XECUTE command.

Similarly, when you issue a JOB command within a procedure, it starts a child process that is outside the method. This means that for code such as the following:

    KILL ^MyVar
    JOB MyLabel
    QUIT $$$OK
MyLabel
    SET ^MyVar=1
    QUIT
Copy code to clipboard

In order for the child process to be able to see the label, the method or the class cannot be contained in a procedure block.

Error Traps within Procedures

If an error trap gets set from within a procedure, it needs to be directly to a private label in the procedure. (This is unlike in legacy code, where it can contain “+offset” or a routine name. This rule is consistent with the idea that executing an error trap essentially means unwinding the stack back to the error trap and then executing a GOTO.)

If an error occurs inside a procedure, $ZERROR gets set to the procedure “ label+offset”, not to a private “label+offset”.

To set an error trap, the normal $ZTRAP or $ETRAP is used, but the value must be a literal. For instance:

 SET $ZTRAP = "abc"
 // sets the error trap to the private label "abc" within this block
Copy code to clipboard

For more information on error traps, see the chapter of this document on Error Processing.

Legacy User-Defined Code

Before the addition of procedures to Caché, there was support for user-defined code in the form of subroutines and functions (which themselves can now be implemented as procedures). These legacy entities are described here, primarily to help explicate already-written code; their ongoing use is not recommended.

Subroutines

Syntax

Routine syntax:

label [ ( param [ = default  ][ , ...] ) ] 
 /* code */
 QUIT 
Copy code to clipboard

Invoking syntax:

DO label [ ( param [ , ...]  ) ] 
Copy code to clipboard

or

GOTO label 
Copy code to clipboard
label The name of the subroutine. A standard label. It must start in column one. The parameter parentheses following the label are optional. If specified, the subroutine cannot be invoked using a GOTO call. Parameter parentheses prevent code execution from “falling through” into a subroutine from the execution of the code that immediately precedes it. When Caché encounters a label with parameter parentheses (even if they are empty) it performs an implicit QUIT, ending execution rather than “falling through.”
param The parameter value(s) passed from the calling program to the subroutine. A subroutine invoked using the GOTO command cannot have param values, and must not have parameter parentheses. A subroutine invoked using the DO command may or may not have param values. If there are no param values, empty parameter parentheses may be specified or omitted. Specify a param variable for each parameter expected by the subroutine. The expected parameters are known as the formal parameter list.. There may be none, one, or more than one param. Multiple param values are separated by commas. Caché automatically invokes NEW on the referenced param variables. Parameters may be passed to the formal parameter list by value or by reference.
default An optional default value for the param preceding it. You can either provide or omit a default value for each parameter. A default value is applied when no actual parameter is provided for that formal parameter, or when an actual parameter is passed by reference and the local variable in question does not have a value. This default value must be a literal: either a number, or a string enclosed in quotation marks. You can specify a null string (“”) as a default value. This differs from specifying no default value, because a null string defines the variable, whereas the variable for a parameter with no specified or default value would remain undefined. If you specify a default value that is not a literal, Caché issues a <PARAMETER> error.
code A block of code. This block of code is normally accessed by invoking the label. However, it can also be entered (or reentered) by calling another label within the code block or issuing a label + offset GOTO command. A block of code can contain nested calls to other subroutines, functions, or procedures. It is recommended that such nested calls be performed using DO commands or function calls, rather than a linked series of GOTO commands. This block of code is normally exited by an explicit QUIT command; this QUIT command is not always required, but is a recommended coding practice. You can also exit a subroutine by using a GOTO to an external label.

Description

A subroutine is a block of code identified by a label found in the first column position of the first line of the subroutine. Execution of a subroutine most commonly completes by encountering an explicit QUIT statement.

A subroutine is invoked by either the DO command or the GOTO command.

  • A DO command executes a subroutine and then resumes execution of the calling routine. Thus, when Caché encounters a QUIT command in the subroutine, it returns to the calling routine to execute the next line following the DO command.

  • A GOTO command executes a subroutine but does not return control to the calling program. When Caché encounters a QUIT command in the subroutine, execution ceases.

You can pass parameters to a subroutine invoked by the DO command; you cannot pass parameters to a subroutine invoked by the GOTO command. You can pass parameters by value or by reference. See Parameter Passing.

The same variables are available to a subroutine and its calling routine.

A subroutine does not return a value.

Functions

As of Caché 5, a function, by default and recommendation, is a procedure. You can, however, continue to define a function that is not a procedure. This section describes such functions.

Syntax

Non-procedure function syntax:

label([param[=default]][,...]) 
   code 
   QUIT expression 
Copy code to clipboard

Invoking syntax:

command $$label([param[ ,...]]) 
Copy code to clipboard

or

DO label([param[ ,...]]) 
Copy code to clipboard
label The name of the function. A standard label. It must start in column one. The parameter parentheses following the label are mandatory.
param A variable for each parameter expected by the function. The expected parameters are known as the formal parameter list . There may be none, one, or more than one param. Multiple param values are separated by commas. Caché automatically invokes NEW for the referenced param variables. Parameters may be passed to the formal parameter list by value or by reference.
default An optional default value for the param preceding it. You can either provide or omit a default value for each parameter. A default value is applied when no actual parameter is provided for that formal parameter, or when an actual parameter is passed by reference and the local variable in question does not have a value. This default value must be a literal: either a number, or a string enclosed in quotation marks. You can specify a null string (“”) as a default value. This differs from specifying no default value, because a null string defines the variable, whereas the variable for a parameter with no specified or default value would remain undefined. If you specify a default value that is not a literal, Caché issues a <PARAMETER> error.
code A block of code. This block of code can contain nested calls to other functions, subroutines, or procedures. Such nested calls must be performed using DO commands or function calls. You cannot exit a function’s code block by using a GOTO command. This block of code can only be exited by an explicit QUIT command with an expression.
expression The function’s return value, specified using any valid ObjectScript expression. The QUIT command with expression is a mandatory part of a user-defined function. The value that results from expression is returned to the point of invocation as the result of the function.

Description

User-defined functions are described in this section. Calls to user-defined functions are identified by a “$$” prefix. (A user-defined function is also known as an extrinsic function.)

User-defined functions allow you to add functions to those supplied by Caché. Typically, you use a function to implement a generalized operation that can be invoked from any number of programs.

A function is always called from within a Caché command. It is evaluated as an expression and returns a single value to the invoking command. For example:

 SET x=$$myfunc()
Copy code to clipboard

Function Parameters

As a rule, user-defined functions use parameter passing. A function, however, can work without externally supplied values. For example, you can define a function to generate a random number or to return the system date in a format other than the default format. Note that in these cases, too, you must supply the parameter parentheses in both the function definition and the function call, even though the parameter list is empty.

Parameter passing requires:

  • An actual parameter list on the function call.

  • A formal parameter list on the function definition.

When Caché executes a user-defined function, it maps the parameters in the actual list, by position, to the corresponding parameters in the formal list. For example, the value of the first parameter in the actual list is placed in the first variable in the formal list; the second value is placed in the second variable; and so on. The matching of these parameters is done by position, not name. Thus, the variables used for the actual parameters and the formal parameters are not required to have (and usually should not have) the same names. The function accesses the passed values by referencing the appropriate variables in its formal list.

If there are more variables in the formal list than there are parameters in the actual list, and a default value is not provided for each, the extra variables are left undefined. Your function code should include appropriate If tests to make sure that each function reference provides usable values.

When passing parameters to a user-defined function, you can use passing by value or passing by reference. You can mix passing by value and passing by reference within the same function call. See Parameter Passing.

Return Value

The basic form for defining a user-defined function is as follows:

label ( parameters )  
 /* code */
 QUIT expression 
Copy code to clipboard

The function must contain a QUIT command followed by an expression. Caché terminates the execution of the function when it encounters the QUIT, and returns the single value that results from the associated expression to the invoking program.

If you specify a QUIT command without an expression, Caché issues an error.

Variables

The invoking program and the called function use the same set of variables, with the following special considerations.

  • Caché executes an implicit NEW command for each parameter in the formal list. This is shown in the following example, where x is reinitialized when myfunc is invoked:

    mainprog
     SET x=7
     SET y=$$myfunc(99)
    myfunc(x)
     WRITE x
     QUIT 66
    Copy code to clipboard
  • The system saves the current value of the system variable $TEST when it enters the function and restores it when the function terminates. Any change in the $TEST value during execution of the function will be discarded when the function exits, unless you include code to explicitly save it by some other means.

Location of Functions

You can define a user-defined function within the routine that references it, or in a separate routine where multiple programs can reference it. Recommended practice is to use one routine to contain all your user-defined function definitions. In this way, you can easily locate any function definition to examine or update it.

Invoking a User-defined Function

You can invoke a user-defined function using either the $$ prefix, or by using the DO command. The $$ form provides the most functionality, and is generally the preferred form.

Using the $$ Prefix

You can invoke a user-defined function in any context in which an expression is allowed. A user-defined function call takes the form:

$$name([param [,...]])
Copy code to clipboard

where

  • name specifies the name of the function. Depending on where the function is defined, name can be specified as:

    • label — A line label within the current routine.

    • label^routine — A line label within the named routine that resides on disk.

    • ^routine — A routine that resides on disk. The routine must contain only the code for the function to be performed.

    A routine is defined within a namespace. You can use an extended routine reference to execute a user-defined function that is located in a routine defined in a namespace other than the current namespace:

      WRITE $$myfunc^|"SAMPLES"|routine
    Copy code to clipboard
  • param specifies the values to be passed to the function. The supplied parameters are known as the actual parameter list. They must match the formal parameter list defined for the function. For example, the function code may expect two parameters, with the first being a numeric value and the second being a string literal. If you specify the string literal for the first parameter and the numeric value for the second, the function may yield an incorrect value or possibly generate an error. Parameters in the formal parameter list always have NEW invoked by the function. See the NEW command. Parameters can be passed by value or by reference. See Parameter Passing. If you pass fewer parameters to the function than are listed in the function’s formal parameter list, parameter defaults are used (if defined); if there are no defaults, these parameters remain undefined.

Using the DO Command

You can invoke a user-defined function using the DO command. (You cannot invoke a system-supplied function using the DO command.) A function invoked using DO does not return a value. That is, the function must generate a return value, but the DO command ignores this return value. This greatly limits the use of DO for invoking user-defined functions.

To invoke a user-defined function using DO, you issue a command in the following syntax:

DO label(param[ ,...]) 

The DO command calls the function named label and passes it the parameters (if any) specified by param. Note that the $$ prefix is not used, and that the parameter parentheses are mandatory. The same rules apply for specifying the label and param as when invoking a user-defined function using the $$ prefix.

A function must always return a value. However, when a function is called with DO, this returned value is ignored by the calling program.