Function definition
Note: the syntax described on this page is available from GeoDMS version 20.9.0 and later.
A function is a typed, reusable unit of model logic. Like a Template, it packages calculation rules so the same logic can be applied to different arguments. Unlike a template, a function declares the value type and domain of every parameter and of its result, and is type-checked once, at its definition — not re-checked separately at each call site.
Because a function has a declared result type, an application F(args) is an expression of that type, and so it nests inside larger expressions like any operator or function call.
parameters — a typed telescope
The parameters follow the function name in parentheses, separated by semicolons. Each is an ordinary typed declaration, and together they form a telescope: a later parameter may refer to an earlier one. A domain parameter is a unit; an attribute parameter names its domain in parentheses.
function CongestionRatio(
unit<uint32> Rd;
attribute<float64> flow (Rd);
attribute<float64> cap (Rd))
Here flow and cap are attributes over the unit Rd, which is itself a parameter. The argument list at the call is matched against this telescope by position, and its length (arity) is checked at the call.
Declarations may also be written in name: type style, and several names may share a single type and calculation rule:
Rd: unit<uint32>;
c1, c2: parameter<float64> := 1.0;
the result — after ->
The result specification follows ->. It gives an optional result name, the result type, and — after := — a result expression that relates the result to the body:
-> attribute<float64> (Rd) := min_elem(raw, 1.0);
If a name is given it precedes the type; otherwise the synthesised result item is named result:
-> link_counts: attribute<uint32> (nw/nodeset) := pcount(nw/F1) + pcount(nw/F2);
the body and the three forms
A function may carry an optional body block in braces, holding intermediate items. How the result expression relates to that body gives three forms.
Form a — the result expression is a plain name designating a body item (matched case-exactly, else case-insensitively-unique):
function CongestionRatio3(
Rd: unit<uint32> { flow: attribute<float64>; cap: attribute<float64>; })
-> attribute<float64> (Rd) := Result
{
attribute<float64> raw (Rd) := Rd/flow / Rd/cap;
attribute<float64> result (Rd) := min_elem(raw, 1.0);
}
Form b — the result expression is the calculation rule of the result, written over body items:
function CongestionRatio(
unit<uint32> Rd; attribute<float64> flow (Rd); attribute<float64> cap (Rd))
-> attribute<float64> (Rd) := min_elem(raw, 1.0);
{
attribute<float64> raw (Rd) := flow / cap;
}
Form c — expression-only, with no body block at all:
function CongestionRatio2(
unit<uint32> Rd { attribute<float64> flow; attribute<float64> cap; })
-> attribute<float64> (Rd) := min_elem(Rd/flow / Rd/cap, 1.0);
structured unit parameters
A unit parameter may carry a member block declaring the attributes the argument table must supply. This is a declared interface only: the body reaches the members through the parameter, as Rd/flow.
unit<uint32> Rd {
attribute<float64> flow;
attribute<float64> cap;
}
At the call the parameter binds by reference to the actual argument — the argument’s data is not copied.
scope — lexical from the definition site
Identifiers in a function’s expressions resolve to what is visible at the point of definition, as everywhere else in the language. A body sees, in order:
- the function’s parameters,
- its own body items (nearest enclosing body scope first),
- containers explicitly imported with a
usingclause, - the surrounding namespace of the definition — the definition parent and its ancestors, with their
usingdirectives — exactly as any ordinary calculation rule written at that spot would see them, - the auto-imported prelude of standard function definitions, as implicit outermost namespace.
So a unit, parameter or sibling function declared next to the function is simply visible in its body, without any import:
parameter<float64> outside := 42.0;
function F(unit<uint32> Rd; attribute<float64> x (Rd))
-> attribute<float64> (Rd) := x * outside;
What a function never sees is the call site. The only channel from the caller is the argument list, and argument expressions are resolved in the caller’s scope. This call-site isolation is the crucial difference from a Template, whose instantiated body also keeps names at the instantiation point reachable as a fallback.
the using import clause
A using clause after the parameter list imports additional containers into the function’s scope; its paths are resolved at the definition site.
function Capped(
unit<uint32> Rd;
attribute<float64> x (Rd))
, using = shared
-> attribute<float64> (Rd) := min_elem(x, limit);
Here limit is found in the imported container shared.
meta-reference parameters
A parameter declared with the item keyword is a meta-reference parameter: its argument binds as a raw item reference rather than as a computed value, so the body can read the argument item’s own metadata. The built-in property accessors are defined this way:
function name(item t) -> parameter<string> := PropValue(t, 'name');
applying a function
An application F(args) is an expression of the result type, and calls compose freely — a function may apply another in its result expression, and a top-level item may apply that function in turn:
function F(unit<uint32> Rd; attribute<float64> x (Rd)) -> attribute<float64> (Rd) := 2.0 * x;
function useF(unit<uint32> U; attribute<float64> y (U)) -> attribute<float64> (U) := F(U, y);
attribute<float64> c (Road) := useF(Road, Road/flow);
Where a function has a body block whose intermediate items you want to keep, instantiate lands the whole body as a container and you reach the result through it:
container cr := instantiate CongestionRatio(Road, Road/flow, Road/cap);
attribute<float64> congestion (Road) := cr/result;
function versus template
A Template and a function both package reusable model logic, but differ in what is checked and when.
| aspect | template | function |
|---|---|---|
| parameters | untyped; the “first N subitems”, matched by argument count | declared, named, typed telescope; arity checked at the call |
| checking | body checked per instantiation, at every call site | type-checked once, at the definition |
| composability | cannot be used as a sub-expression | F(args) is an expression of the result type, nestable |
| scope | definition scope first, with the instantiation site as fallback | lexical definition scope; the call site is never visible |
| result | some subitem, by convention; untyped | a typed, named result (default result) |
see also
- Template
- Function signature
- Type alias
- Generic function
- Anonymous function
- Closure
- Variadic parameters
- Map
- Operators and functions
since version
20.9.0