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.