March 1999 Draft
JavaScript 2.0
Functions
previousupnext

Wednesday, March 24, 1999

Function Definitions

To define a function we use the following syntax:

FunctionDefinition 
   [Visibility] [getter | setterfunction Identifier ( Parameters ) [TypeExpressionBlock
|  TraditionalFunctionDefinition

If Visibility is absent, the above declaration defines a local function within the current Block scope. If Visibility is present, the above declaration declares either a global function (if outside a ClassDefinition's Block) or a class function (if inside a ClassDefinition's Block) according to the declaration scope rules.

The function's result type is TypeExpression, which defaults to type Any if not given. If the function does not return a value, it's good practice to set TypeExpression to void to document this fact.

Block contains the function body and is evaluated only when the function is called.

A function definition can also be traditional, which is more similar to the behavior of JavaScript 1.x function definitions.

Parameters

Parameters has one of the following forms:

Parameters 
   RequiredParameter , ... , RequiredParameter [, OptionalParameter ... , OptionalParameter] [, ... [Identifier]]
|  ... [Identifier]

If the ... is present, the function accepts more arguments than just the listed parameters. If an Identifier is given after the ..., then that Identifier is bound to an array of arguments given after the listed parameters. That Identifier is declared locally as though by the declaration const array Identifier.

Individual parameters have the forms:

RequiredParameter 
   [TypeExpressionIdentifier
OptionalParameter 
   [TypeExpressionIdentifier = [AssignmentExpression]

TypeExpression gives the parameter's type and defaults to type Any. If the parameter name Identifier is followed by a =, then that parameter is optional. If the nth parameter is optional and a call to this function provides fewer than n arguments, then the nth parameter is set to the value of its AssignmentExpression (or undefined if the AssignmentExpression is missing), coerced to the nth parameter's type if necessary. The nth parameter's AssignmentExpression is evaluated only if fewer than n arguments are given in a call.

A RequiredParameter may not follow an OptionalParameter. If a function has n RequiredParameters and m OptionalParameters and no ... in its parameter list, then any call of that function must supply at least n arguments and at most n+m arguments. If this function has a ... in its parameter list, then any call of that function must supply at least n arguments. These restrictions do not apply to traditional functions.

The parameters' Identifiers are local variables with types given by the corresponding TypeExpressions inside the function's Block. Code in the Block may read and write these variables. Arguments are passed by value, so writes to these variables do not affect the passed arguments' values in the caller.

In addition to local variables generated by the parameters' Identifiers, each function also has a predefined arguments local variable which holds an array (of type const array) of all arguments passed to this function.

Evaluation Order

When a function is called, the following list indicates the order of evaluation of the various expressions in a FunctionDefinition. These steps are taken only after all of the arguments have been evaluated.

  1. Evaluate the first parameter's TypeExpression to obtain a type t.
  2. If the first parameter is optional and no argument has been supplied, evaluate the first parameter's AssignmentExpression and let it be the first parameter's value.
  3. Coerce the argument (or default) value to type t and bind the parameter's Identifier to the result.
  4. Repeat steps 1-3 for each additional parameter.
  5. If there are leftover parameters, make an array of them and the Identifier, if any, following a ... to the result.
  6. Evaluate the FunctionDefinition's result TypeExpression to obtain a result type r.
  7. Evaluate the body.
  8. Coerce the result to type r and return it.

Note that later TypeExpressions and AssignmentExpressions can refer to previously bound arguments. Thus, the following is legal:

function choice(boolean a, type b, b c, b d=) b {
  return a ? c : d;
}

The call choice(true,integer,8,4) would return 8, while choice(false,integer,6) would return 0 (undefined coerced to type integer).

Relationship to Methods and Classes

Unless the function is a traditional function, the function definition using the above syntax does not define a class; the function's name cannot be used in a new expression, and the function does not have a this parameter. Any attempt to use this inside the function's body is an error. To define a method that can access this, use the method keyword.

If a FunctionDefinition is located at a class scope (either because it is located the top level of a ClassDefinition's Block or it has a Visibility prefix and is located inside a ClassDefinition's Block), then the function is a static method of the class. Unlike C++ or Java, JavaScript 2.0 does not use the static keyword to indicate such functions; instead, instance methods (i.e. non-static methods) are defined using the method keyword.

Getters and Setters

If a FunctionDefinition contains the keyword getter or setter, then the defined function is a getter or a setter.

A getter must not take any parameters and cannot have a ... in its Parameters list. Unlike an ordinary function, a getter is invoked by merely mentioning its name without an Arguments list in any expression except as the destination of an assignment. For example, the following code returns the string <2,3,1>:

var integer x = 0;
getter function serialNumber() integer {return ++x}

var y = serialNumber;
return "<" + serialNumber + "," + serialNumber + "," + y + ">";

A setter must take exactly one required parameter and cannot have a ... in its Parameters list. Unlike an ordinary function, a setter is invoked by merely mentioning its name (without an Arguments list) on the left side of an assignment or as the target of a mutator such as ++ or --. The result of the setter becomes the result of the assignment. For example, the following code returns the string <1,2,43>:

var integer x = 0;
getter function serialNumber() integer {return ++x}
setter function serialNumber(integer n) integer {return x=n}

var s = "<" + serialNumber + "," + serialNumber;
serialNumber = 42;
return s + "," + serialNumber + ">";

A setter can have the same name as a getter in the same lexical scope. A getter or setter cannot be extracted from its variable, so the notion of the type of a getter or setter is vacuous; a getter or setter can only be called.

Contrast the following:

var integer x = 0;
function f() integer {return ++x}
function g() Function {return f}
getter function h() Function {return f}

f;     // Evaluates to function f
g;     // Evaluates to function g
h;     // Evaluates to function f (not h)
f();   // Evaluates to 1
g();   // Evaluates to function f
h();   // Evaluates to 2
g()(); // Evaluates to 3

We can use a getter and a setter to create an alias to another variable, as in:

getter function myAlias() {return Pkg::var}
setter function myAlias(x) {return Pkg::var = x}

myAlias = myAlias+4;

Traditional Functions

Traditional function definitions are provided for compatibility with JavaScript 1.x. The syntax is as follows:

TraditionalFunctionDefinition 
   [Visibilitytraditional function Identifier ( Identifier , ... , Identifier ) Block

A function declared with the traditional keyword cannot have any argument or result type declarations, optional arguments, or getter or setter keyword. Such a function is treated as though every argument were optional and more arguments than just the listed ones were allowed. Thus, the definition

traditional function Identifier ( Identifier , ... , Identifier ) Block

behaves like the following function definition:

function Identifier ( Identifier = , ... , Identifier = , ... ) Block

Furthermore, a traditional function defines its own class and treats this in the same manner as JavaScript 1.x.

Functions in Expressions

Every function (except a getter or a setter) is also a value and has type Function. Like other values, it can be stored in a variable, passed as an argument, and returned as a result. The identifiers in a function are all lexically scoped.

Function Expressions

We can use a variant of a function definition to define a function inside an expression. The syntax is:

FunctionExpression 
   function [Identifier( Parameters ) [TypeExpressionBlock

This expression defines a function and returns it as a value of type Function. The function can be named by providing the Identifier, but this name is only useful to identify the function in a debugger; other than that, the function is anonymous.

To avoid confusion between a FunctionDefinition and a FunctionExpression, a Statement (and a few other grammar nonterminals) may not begin with a FunctionExpression. To place a FunctionExpression at the beginning of a Statement, enclose it in parentheses.

A FunctionDefinition is merely convenient syntax for a const variable definition and a FunctionExpression:

[Visibilityfunction Identifier ( Parameters ) [TypeExpressionBlock

is equivalent to:

[Visibilityconst Function Identifier = function Identifier ( Parameters ) [TypeExpressionBlock ;

Function Calls

Unless a function is a getter or a setter, we call that function by listing its arguments in parentheses after the function expression, just as in JavaScript 1.x:

FullPostfixExpression 
   FullPostfixExpression ( AssignmentExpression , ... , AssignmentExpression )
|  other postfix expressions

Discussion

Getters and Setters

An alternative syntax for defining getters and setters would be:

FunctionDefinition 
   [Visibilityfunction [get | setIdentifier ( Parameters ) [TypeExpressionBlock
|  TraditionalFunctionDefinition

This way we would declare a getter by using, for example,

function get height() integer {...}

instead of:

getter function height() integer {...}

Should we switch to the new syntax?

Optional Parameters

Do we want to have a named rest parameter (as in the proposal above), or only support the arguments special local variable as in JavaScript 1.x? The main difference is in the handling of fixed arguments -- they must be added to the arguments array but can be omitted from the rest array.

Traditional Functions

The traditional keyword is ugly, so let's take a look at some alternatives. Unless we want to continue to make each function into a class (as JavaScript 1.x does), we need some way to indicate which functions are also classes and which ones are not. Also, we'd like to be able to indicate which functions can be called with more or fewer than the desired number of arguments and which cannot.

One possibility would be to state that any function that uses a type annotation in its signature (either the parameter list or the result type) is a new-style function and does not define a class; other functions would declare classes. Furthermore, new-style functions would have to be called with the exact number of arguments unless some parameters are optional or a ... is present in the parameter list. These are analogous to the rules that ANSI C used to distinguish new-style functions from traditional C functions. As with ANSI C, we have somewhat of a difficulty with functions that take no parameters; such functions would need to specify a return type to be considered new-style.

C++ did away with the ANSI C treatment of traditional C functions. We could do the same by having a pragma (analogous to Perl's use pragmas) that could indicate that all functions are to be considered new-style unless prefixed by the traditional keyword. If we do this, we should decide whether the default setting of this pragma would be on or off.


Waldemar Horwat
Last modified Wednesday, March 24, 1999
previousupnext