July 2000 Draft
JavaScript 2.0
Core Language
Functions
previousupnext

Friday, May 26, 2000

Syntax

FunctionDefinition 
   FunctionDeclaration Block
|  FunctionDeclaration Semicolon

A function definition not preceded by the abstract attribute and not followed by a Block is called a function declaration. A function declaration indicates that the function will be defined later in the same scope. A function declaration's signature must exactly match the eventual definition's.

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

Like other definitions and declarations, a function definition or declaration may be preceded by one or more attributes, which affect the function's scope, namespace, and semantics. Every function (except a getter or a setter) is also a value and has type Function.

Function Kinds

FunctionDeclaration 
   function FunctionName FunctionSignature
|  constructor [no line break] Identifier FunctionSignature
FunctionName 
   QualifiedIdentifier
|  get [no line break] [lookahead{(}] QualifiedIdentifier
|  set [no line break] [lookahead{(}] QualifiedIdentifier

If a FunctionDeclaration is introduced with the function keyword, then it defines a function, getter (if FunctionName begins with get), or setter (if FunctionName begins with set). If the FunctionDeclaration begins with the constructor keyword, then it defines a class constructor. It is customary to give a class constructor the same name as its class.

Function Signatures

FunctionSignature  ParameterSignature ResultSignature
ParameterSignature  ( Parameters )
Parameters 
   «empty»
|  RestParameter
|  RequiredParameters
|  OptionalParameters
|  RequiredParameters , RestParameter
|  OptionalParameters , RestParameter
RequiredParameters 
   RequiredParameter
|  RequiredParameters , RequiredParameter
OptionalParameters 
   OptionalParameter
|  RequiredParameters , OptionalParameter
|  OptionalParameters , OptionalParameter

A FunctionSignature gives the names and the types of the function's parameters and result. A function may take zero or more required parameters, followed by zero or more optional parameters, optionally followed by a rest parameter.

Parameters

Individual parameters have the forms:

RequiredParameter  TypedIdentifier
OptionalParameter  TypedIdentifier = AssignmentExpressionallowIn
TypedIdentifier 
   Identifier
|  Identifier : TypeExpressionallowIn

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

Rest Parameter

RestParameter 
   ...
|  ... TypedIdentifier
|  ... TypedIdentifier = AssignmentExpressionallowIn

If the ... is present, the function accepts more arguments than just the listed required and optional parameters. If a TypedIdentifier is given after the ..., then that TypedIdentifier's Identifier is bound to an array of arguments given after the listed parameters. Named arguments are passed as named properties of that array. That Identifier is declared as a local const using the TypedIdentifier's TypeExpression type, or Array if not provided.

If a named argument is passed to a function call, then that argument and all subsequent ones can only match a RestParameter.

In non-strict mode, each function also has a predefined const arguments local variable which holds an array (of type Array) of all arguments passed to this function.

Result Type

ResultSignature 
   «empty»
|  : TypeExpressionallowIn

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.

Evaluation Order

A function's parameter and result TypeExpressions are evaluated at the time the function definition or declaration is executed. These types are then saved for use in argument and result coercions at the time the function is called.

The static and dynamic extent of a parameter includes all subsequent parameters' and the result type's TypeExpressions and AssignmentExpressions. However, the case where a subsequent parameter's or the result type's TypeExpression references a prior parameter is reserved for a future language extension. For now, an implementation must detect this case and raise an error:

const t = Integer;
function choice(a:Boolean, t:Type, c:t, d:t):t {
  return a ? c : d;
}

This definition of choice should (for now) be an error and not:

function choice(a:Boolean, t:Type, c:Integer, d:Integer):Integer {
  return a ? c : d;
}

The intent is that a future language extension might make the first definition of choice legal and permit calls to it like choice(true,String,"Be","Not Be"), which would return "Be".

On the other hand, an optional or rest parameter's AssignmentExpression may refer to the values of prior parameters:

function f(a:Integer, b:Integer = a, c:Integer = b):Integer {
  return a + b + c;
}

f(3);        // Returns 9
f(3, 4);     // Returns 11
f(3, 4, 10); // Returns 17

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. Get the saved type t that was the result of evaluating the first parameter's TypeExpression at the time the function was defined.
  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 is a RestParameter with an Identifier, bind that Identifier to an array comprised of the zero or more leftover arguments not already bound to a parameter.
  6. Evaluate the body.
  7. Get the saved type r that was the result of evaluating the result TypeExpression at the time the function was defined.
  8. Coerce the result to type r and return it.

Getters and Setters

If a FunctionName contains the keyword get or set, then the defined function is a getter or a setter.

A getter must not take any parameters. 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 x:Integer = 0;
function get serialNumber():Integer {return ++x}

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

A setter must take exactly one required parameter. 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 x:Integer = 0;
function get serialNumber():Integer {return ++x}
function set serialNumber(n:Integer):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 x:Integer = 0;
function f():Integer {return ++x}
function g():Function {return f}
function get 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

Traditional Functions

A traditional function is specified by preceding the function definition with the traditional attribute. Traditional function definitions are provided for compatibility with JavaScript 1.5.

A traditional function defines its own class and treats this in the same manner as JavaScript 1.5. A non-traditional function 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 unless it is a non-static member of a class or interface.

Discussion

Getters and Setters

By consensus in the ECMA TC39 modularity subcommittee, we decided to use the above syntax for getters and setters instead of:

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

The decision was based on aesthetics; neither syntax is more difficult to implement than the other.

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.5 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 Friday, May 26, 2000
previousupnext