Function signature

Note: the syntax described on this page is available from GeoDMS version 20.9.0 and later.

A function signature describes the type of a function — its parameters and its result — without giving a body. It answers “what shape of function goes here?” rather than “what does the function compute?”.

A function signature is written (params) -> resultType;: a parameter list and a result type, with no result expression and no body.

A signature is most useful as a named Type alias, so it can be referred to by name wherever a type is expected:

unary_fn = (unit<uint32> D; attribute<float64> v (D)) -> attribute<float64> (D);

This declares unary_fn as the type of any function that takes a domain D and an attribute<float64> (D), and returns an attribute<float64> (D).

a signature is a type, not a callable

Because a signature has no body, applying a signature-only function is an error. It names a type; it does not compute anything. You use a signature to type a parameter or a result, never to call it directly.

typing a function-valued parameter

A higher-order function receives another function as an Argument. The receiving parameter states the signature that argument must match:

container lib
{
  function Halve(unit<uint32> D; attribute<float64> v (D))
  -> attribute<float64> (D) := v / 2.0;

  // f is checked against unary_fn at every application
  function ApplyTwiceT(unit<uint32> D; f: unary_fn; attribute<float64> x (D))
  -> attribute<float64> (D) := f(D, f(D, x));
}

attribute<float64> quarter (Road) := lib/ApplyTwiceT(Road, lib/Halve, Road/flow);

When ApplyTwiceT is applied, the function passed for f (here lib/Halve) is checked against unary_fn: the arity must match, each parameter’s item class must match (a plain-item signature position acts as a wildcard), and the result class must match. A mismatch is reported at application time. For instance, passing a three-parameter function where unary_fn has two parameters is rejected with an arity-mismatch diagnostic.

Unit relationships between signature positions (the dependent part, such as “the result shares the domain of the argument”) are not yet compared; only the item classes are checked.

Since GeoDMS 20.20.0 a declared signature is the only way to type a function-valued parameter or result. The keyword function on its own is not a type: it says “some function” and constrains nothing, so it is now rejected in both positions, with a message naming the spelling to use. A configuration that wrote f: function or -> function declares a signature and refers to it by name; that has to be changed on upgrade, and the shipped examples/function.dms shows the typed form.

naming the signature

Since GeoDMS 20.20.0 the signature may be named by a path as well as by a plain name. A path is read against the namespace the declaration sits in, which for a parameter is the function itself, the same base an item’s calculation rule is read against:

  • unary_fn — the nearest unary_fn, looked for in the function itself and then in each enclosing container
  • ../unary_fn — dots as elsewhere in the configuration: one dot is that namespace, each further dot one level up, so .. is the container holding the function and ... the one above that
  • /lib/unary_fn — from the configuration root

Before that version only the plain name resolved, and a path was silently accepted and then ignored, which left the parameter an untyped container without a word about it.

A signature declared further down the configuration is legal: the reference is kept as written and resolved when the whole configuration is in place. One that resolves to nothing then fails that function declaration, naming the parameter; on a function /lib/Apply the failure reads:

parameter 2: the type '/nosuch/unary_fn' does not resolve to a declared item

generic signatures and type application

A signature may be generic, carrying type variables in a function<...> clause. This describes a whole family of functions in one alias:

nuf = function<V: numerics, D: domains>(attribute<V> (D)) -> attribute<V> (D);

nuf is the type of any numeric-attribute-to-same-attribute function. Where a signature is used as a parameter or result type, its type variables can be applied with concrete arguments, written nuf<V, D>:

// f and g must both satisfy nuf at V, D; the result is a nuf at V, D
function compose2<V: numerics, D: domains>(f: nuf<V, D>; g: nuf<V, D>) -> nuf<V, D>
{
  function result(attribute<V> x (D)) -> attribute<V> (D) := f(g(x));
}

Here the signature nuf types the two incoming functions and the returned closure, keeping the composition fully typed. The same nuf can equally type a parameter of a function that returns a fresh closure:

nuf1 = function<V: numerics>(attribute<V> x) -> attribute<V>;

function twice(h: nuf) -> nuf1
{
  function result<V: numerics>(attribute<V> x) -> attribute<V> := h(h(x));
}

nuf types what twice accepts and nuf1 what it returns; the returned closure takes one attribute and carries no domain of its own, so it is a signature of its own rather than nuf.

see also

since version

20.9.0