Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Functions

New functions are declared with the syntax

fn <id> ( [<param> [, <param> ...]] ) : <expr>

where

  • <id> is an identifier representing the name of the function,
  • each <param> is an identifier representing a positional argument to the function, and
  • <expr> is any expression that implements the function.

Function declarations must appear in the declaration section of a scope.

The function body <expr> may refer to the passed-in arguments by name.

Specifically, the references to the named parameters are field references of the special value this, as in any expression. In particular, the value of this referenced in a function body is formed as record from the actual values passed to the function where the field names correspond to the parameters of the function.

For example, the function add as defined by

fn add(a,b): a+b

when invoked as

values {x:1} | values add(x,1)

is passed the record the {a:x,b:1}, which after resolving x to 1, is {a:1,b:1} and thus evaluates the expression

this.a + this.b

which results in 2.

Any function-as-value arguments passed to a function do not appear in the this record formed from the parameters. Instead, function values are expanded at their call sites in a macro-like fashion.

Functions may be recursive. If the maximum call stack depth is exceeded, the function returns an error value indicating so. Recursive functions that run for an extended period of time without exceeding the stack depth will simply be allowed to run indefinitely and stall the query result.

Subquery Functions

Since the body of a function is any expression and an expression may be a subquery, function bodies can be defined as subqueries. This leads to the commonly used pattern of a subquery function:

fn <id> ( [<param> [, <param> ...]] ) : (
    <query>
)

where <query> is any query and is simply wrapped in parentheses to form the subquery.

As with any subquery, when multiple results are expected, an array subquery may be used by wrapping <query> in square brackets instead of parentheses:

fn <id> ( [<param> [, <param> ...]] ) : [
    <query>
]

Note when using a subquery expression in this fashion, the function’s parameters do not appear in the scope of the expressions embedded in the query. For example, this function results in a type error:

fn apply(a,val): (
  unnest a
  | collect(this+val))
)
values apply([1,2,3], 1)

because the field reference to val within the subquery does not exist. Instead the parameter val can be carried into the subquery using the alternative form of unnest:

fn apply(a,val): (
  unnest {outer:val,item:a}
  | collect(outer+item)
)
values apply([1,2,3], 1)

See the example below.

Examples


A simple function that adds two numbers

# spq
fn add(a,b): a+b
values add(x,y)
# input
{x:1,y:2}
{x:2,y:2}
{x:3,y:3}
# expected output
3
4
6

A simple recursive function

# spq
fn fact(n): n<=1 ? 1 : n*fact(n-1)
values fact(5)
# input

# expected output
120

A subquery function that computes some stats over numeric arrays

# spq
fn stats(numbers): (
    unnest numbers
    | sort this
    | avg(this),min(this),max(this),mode:=collect(this)
    | mode:=mode[len(mode)/2]
) 
values stats(a)
# input
{a:[3,1,2]}
{a:[4]}
# expected output
{avg:2.,min:1,max:3,mode:2}
{avg:4.,min:4,max:4,mode:4}

Function arguments are actually fields in the “this” record

# spq
fn that(a,b,c): this
values that(x,y,3)
# input
{x:1,y:2}
# expected output
{a:1,b:2,c:3}

Functions passed as values do not appear in the “this” record

# spq
fn apply(f,arg):{that:this,result:f(arg)}
fn square(x):x*x
values apply(&square,val)
# input
{val:1}
{val:2}
# expected output
{that:{arg:1},result:1}
{that:{arg:2},result:4}

Function parameters do not reach into subquery scope

# spq
fn apply(a,val): (
  unnest a
  | collect(this+val)
)
values apply([1,2,3], 1)
# input

# expected output
"val" no such field at line 3, column 18:
  | collect(this+val)
                 ~~~

The compound unnest form brings parameters into subquery scope

# spq
fn apply(a,val): (
  unnest {outer:val,item:a}
  | collect(outer+item)
)
values apply([1,2,3], 1)
# input

# expected output
[2,3,4]