110 Block-Bodied Functions

A function declared with func may have a block body. Block bodies use the statement language: local variables, assignment, loops, branches, and return. This is the function form for pure algorithms that are clearer as steps than as a single expression.


func Integer sumTo(Integer limit)
{
    var Integer total = 0;

    for Integer n in 1..limit {
        total += n;
    }

    return total;
}

The function signature is the same as an expression-bodied function: result type, name, optional generic parameters, parameters, and optional generic where constraints. A plain parameter is read-only and non-owning. A parameter prefixed with ^ is an owned variable and may be updated or consumed. Functions cannot have exclusive-reference parameters. The body is a statement block instead of = Expression;.

Same Function, Four Bodies

The following declarations are alternative definitions of factorial for inputs where n ≥ 0. A component may contain only one declaration named factorial in a namespace; they are shown together only to compare body forms.


func Integer factorial(Integer n)
{
    var Integer result = 1;

    for Integer k in 1..n {
        result *= k;
    }

    return result;
}

func Integer factorial(Integer n) =
    (1..n).Σ{ 1 ; result value ↦ result * value };

func Integer factorial(Integer n)
{
    if n = 0 {
        return 1;
    }

    return n * factorial(n - 1);
}

func Integer factorial(Integer n) =
    if n = 0 then 1 else n * factorial(n - 1);

All four declarations define the same pure function on non-negative inputs. The first body uses local program-state updates and a loop. The second expresses the same computation as a fold over the finite range 1..n. The third uses a block body with recursive calls and early return. The fourth writes the same recursion as a single conditional expression. The body form does not change how callers use the function.

Return values

A block-bodied function returns with a return statement:


return total;

The returned expression is checked in the context of the declared result type, and its type must be assignable to that result type. In a block-bodied function, return must include an expression. A return inside a nested block returns from the enclosing function. A non-void function body must return on every reachable path. The language has no Void result type; operations that exist only for side effects are procedures. Outside a block-bodied function or procedure body, using return is a static error. A path ending with unreachable; has no normally completing successor and therefore does not require a return; see Unreachable.

Returning an expression reads and exposes its value. A returned location, including every constituent location of a composite value, must therefore be definitely initialised as specified in Uninitialised Locations.

Local state and purity

A block body does not by itself make a function impure. A block-bodied function is pure when its body:

  • uses only its parameters, local variables, constants, read-only query expressions, and pure function calls;
  • writes only local variables allocated inside the function body;
  • does not read or write program-global variables;
  • does not write database-state locations;
  • does not perform I/O such as print;
  • does not call a procedure.

Such a function may be called from read-only expression contexts, including derived attributes, constraints, query expressions, and expression-bodied functions. It is observationally the same as an expression-bodied pure function, even though its implementation uses local mutation or control flow.

Updating an owned parameter is a local update, just like updating a local variable, and does not make the function impure. Parameter access and ownership are specified in Parameter Access and Ownership.

Side effects belong in procedures

A function body that would read or write global program state, perform I/O, write database-state locations, depend on a transaction, or call a procedure is rejected as a function body. Write the operation as a proc instead. This rule keeps function calls valid in ordinary expression contexts without introducing hidden updates.

Generic block-bodied functions

Generic block-bodied functions use the same generic parameter, default, constraint, inference, and canonical instantiation rules as expression-bodied generic functions. Instantiation substitutes the generic arguments into the signature and statement body before type and effect checking.


func T firstOr<Type T>(T fallback, Set<T> values)
{
    for T value in values {
        return value;
    }

    return fallback;
}

The example is pure despite using a loop because it reads only its parameters, writes no non-local state, and returns a value determined by its inputs.