Functions

A function is a value that represents a mapping from a set of argument values to a single value. A function is invoked by provided a set of input values (the argument values), and produces a single output value (the return value).

Writing functions

Functions are written using a function-expression:

function-expression:
      ( parameter-listopt ) function-return-typeopt => function-body
function-body:
      expression
parameter-list:
      fixed-parameter-list
      fixed-parameter-list
, optional-parameter-list
      optional-parameter-list
fixed-parameter-list:
      parameter
      parameter
, fixed-parameter-list
parameter:
      parameter-name parameter-typeopt
parameter-name:
      identifier
parameter-type:
      assertion
function-return-type:
      assertion
assertion:

      as nullable-primiitve-type
optional-parameter-list:
      optional-parameter
      optional-parameter
, optional-parameter-list
optional-parameter:

      optional parameter
nullable-primitve-type
      nullable
opt primitive-type_

The following is an example of a function that requires exactly two values x and y, and produces the result of applying the + operator to those values. The x and y are parameters that are part of the formal-parameter-list of the function, and the x + y is the function body:

(x, y) => x + y

The result of evaluating a function-expression is to produce a function value (not to evaluate the function-body). As a convention in this document, function values (as opposed to function expressions) are shown with the formal-parameter-list but with an ellipsis (...) instead of the function-body. For example, once the function expression above has been evaluated, it would be shown as the following function value:

 (x, y) => ...

The following operators are defined for function values:

Operator Result
x = y Equal
x <> y Not equal

The native type of function values is a custom function type (derived from the intrinsic type function) that lists the parameter names and specifies all parameter types and the return type to be any. (See Function types for details on function types.)

Invoking functions

The function-body of a function is executed by invoking the function value using an invokeexpression. Invoking a function value means the function-body of the function value is evaluated and a value is returned or an error is raised.

invoke-expression:
      primary-expression
( argument-listopt )
argument-list:
      expression-list

Each time a function value is invoked, a set of values are specified as an argument-list, called the arguments to the function.

An argument-list is used to specify a fixed number of arguments directly as a list of expressions. The following example defines a record with a function value in a field, and then invokes the function from another field of the record:

[ 
    MyFunction = (x, y, z) => x + y + z, 
    Result1 = MyFunction(1, 2, 3)           // 6
]

The following holds when invoking a function:

  • The environment used to evaluate the function-body of the function includes a variable that corresponds to each parameter, with the same name as the parameter. The value of each parameter corresponds to a value constructed from the argument-list of the invokeexpression, as defined in Parameters.

  • All of the expressions corresponding to the function arguments are evaluated before the function-body is evaluated.

  • Errors raised when evaluating the expressions in the expression-list or functionexpression are propagated.

  • The number of arguments constructed from the argument-list must be compatible with the formal parameters of the function, or an error is raised with reason code "Expression.Error". The process for determining compatibility is defined in Parameters.

Parameters

There are two kinds of formal parameters that may be present in a formal-parameter-list:

  • A required parameter indicates that an argument corresponding to the parameter must always be specified when a function is invoked. Required parameters must be specified first in the formal-parameter-list. The function in the following example defines required parameters x and y:
    [ 
        MyFunction = (x, y) => x + y, 

        Result1 = MyFunction(1, 1),     // 2 
        Result2 = MyFunction(2, 2)      // 4
    ] 
  • An optional parameter indicates that an argument corresponding to the parameter may be specified when a function is invoked, but is not required to be specified. If an argument that corresponds to an optional parameter is not specified when the function is invoked, then the value null is used instead. Optional parameters must appear after any required parameters in a formal-parameter-list. The function in the following example defines a fixed parameter x and an optional parameter y:
    [ 
        MyFunction = fn(x, optional y) =>
                          if (y = null) x else x + y, 
        Result1 = MyFunction(1),        // 1 
        Result2 = MyFunction(1, null),  // 1 
        Result3 = MyFunction(2, 2),     // 4
    ] 

The number of arguments that are specified when a function is invoked must be compatible with the formal parameter list. Compatibility of a set of arguments A for a function F is computed as follows:

  • Let the value N represent the number of arguments A constructed from the argumentlist. For example:
    MyFunction()             // N = 0 
    MyFunction(1)            // N = 1 
    MyFunction(null)         // N = 1 
    MyFunction(null, 2)      // N = 2 
    MyFunction(1, 2, 3)      // N = 3 
    MyFunction(1, 2, null)   // N = 3 
    MyFunction(1, 2, {3, 4}) // N = 3
  • Let the value Required represent the number of fixed parameters of F and Optional the number of optional parameters of F. For example:
()               // Required = 0, Optional = 0 
(x)              // Required = 1, Optional = 0 
(optional x)     // Required = 0, Optional = 1 
(x, optional y)  // Required = 1, Optional = 1
  • Arguments A are compatible with function F if the following are true:

    • (N >= Fixed) and (N <= (Fixed + Optional))
    • The argument types are compatible with F's corresponding parameter types
  • If the function has a declared return type, then the result value of the body of function F is compatible with F's return type if the following is true:

    • The value yielded by evaluating the function body with the supplied arguments for the function parameters has a type that is compatible with the return type.
  • If the function body yields a value incompatible with the function's return type, an error with reason code "Expression.Error" is raised.

Recursive functions

In order to write a function value that is recursive, it is necessary to use the scoping operator (@) to reference the function within its scope. For example, the following record contains a field that defines the Factorial function, and another field that invokes it:

[ 
    Factorial = (x) => 
                if x = 0 then 1 else x * @Factorial(x - 1), 
    Result = Factorial(3)  // 6 
]

Similarly, mutually recursive functions can be written as long as each function that needs to be accessed has a name. In the following example, part of the Factorial function has been refactored into a second Factorial2 function.

[ 
    Factorial = (x) => if x = 0 then 1 else Factorial2(x), 
    Factorial2 = (x) => x * Factorial(x - 1), 
    Result = Factorial(3)     // 6 
]

Closures

A function can return another function as a value. This function can in turn depend on one or more parameters to the original function. In the following example, the function associated with the field MyFunction returns a function that returns the parameter specified to it:

[ 
    MyFunction = (x) => () => x, 
    MyFunction1 = MyFunction(1), 
    MyFunction2 = MyFunction(2), 
    Result = MyFunction1() + MyFunction2()  // 3 
]

Each time the function is invoked, a new function value will be returned that maintains the value of the parameter so that when it is invoked, the parameter value will be returned.

Functions and environments

In addition to parameters, the function-body of a function-expression can reference variables that are present in the environment when the function is initialized. For example, the function defined by the field MyFunction accesses the field C of the enclosing record A:

[ 
A =  
    [ 
        MyFunction = () => C, 
        C = 1 
    ], 
B = A[MyFunction]()           // 1 
]

When MyFunction is invoked, it accesses the value of the variable C, even though it is being invoked from an environment (B) that does not contain a variable C.

Simplified declarations

The each-expression is a syntactic shorthand for declaring untyped functions taking a single formal parameter named _ (underscore).

each-expression:
      each each-expression-body
each-expression-body:
      function-body

Simplified declarations are commonly used to improve the readability of higher-order function invocation.

For example, the following pairs of declarations are semantically equivalent:

each _ + 1 
(_) => _ + 1  
each [A] 
(_) => _[A] 
 
Table.SelectRows( aTable, each [Weight] > 12 ) 
Table.SelectRows( aTable, (_) => _[Weight] > 12 )