Skip to content

Function definition

MaartenHilferink edited this page Jul 25, 2026 · 1 revision

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.

the using import clause

A function's scope is closed: only its parameters, its own body items and explicitly imported containers are visible; the surrounding namespace is not. To bring outside names in, add a using clause after the parameter list. 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, with the surrounding namespace as fallback closed: parameters, locals and explicit using imports only
result some subitem, by convention; untyped a typed, named result (default result)

see also

since version

20.9.0

Clone this wiki locally