Defining functions
This page explains how to define functions within TypeDB schemas.
Overview
Functions provide powerful abstractions of query logic. They are a cornerstone of the functional query programming model, and generalize logic programs à la Datalog. Functions calls can be nested, recursive, and negated. There syntax natively embeds into TypeQL’s declarative pattern language.
Functions can be defined in two ways:
-
We can define functions with “globally” in the schema of your database. Functions defined in the schema can be used in any data pipeline.
-
Or, we can define functions “locally” as part of a specific data pipeline. Such functions can only be called from within their parent pipeline.
The function definition syntax in both cases is identical. This page focuses on defining functions in schemas. For local functions in data pipeline see the with stage reference.
Defining functions
Similar to type definitions, functions can be stored in a database schema using schema
transactions.
Three types of schema definition queries are available for working with functions:
-
define fun <function_name> <function_signature>: <function_body>
. Adefine
query adds a new function to the schema for future use. -
undefine fun <function_name>
. Anundefine
query removes a function from the schema. -
redefine <function_name> <function_signature>: <function_body>
. Aredefine
query updates an existing function in the schema.
Function signatures
Function signatures determine:
-
The tuple of typed input variables, e.g.,
$x : user, $y : string, $z : bool
(these variables can then be used in the function body) -
The tuple of output types, e.g.
post, integer, integer
, which can either be a stream of output tuples (indicated by surrounding brackets{ … }
) or a scalar tuple, meaning the function return at most one tuple.
( $x : user, $y : string, $z : bool ) -> post, integer, integer # scalar output
( $x : user, $y : string, $z : bool ) -> { post, integer, integer } # stream output
( ) -> integer # empty input
Function body
The function body comprises
-
A TypeQL read pipeline (which may have multiple stages, like
match
,sort
,reduce
) -
A return statement starting with the keyword
return
For stream functions, return
will be followed simply by a <tuple>
of variables that are to be returned.
For scalar functions, the return
statements will contain some sort of reduction of the answer set to (zero or) one answer:
-
return first <tuple>
returns the first answer only, and similarly,return last …
returns the last. -
return <aggr>, …
returns a built-in aggregation result (like sums, counts, means, …)
Examples
Computing values
define
fun add_streamed($x: integer, $y: integer) -> { integer }:
match
let $z = $x + $y;
return { $z };
If you are certain that $z
will only match a single value (or if only one value is needed), the first
or last
keywords can be used to ensure the function returns a scalar result.
define
fun add($x: integer, $y: integer) -> integer:
match
let $z = $x + $y;
return first $z;
Querying for data instances
define
fun user_phones($user: user) -> { phone }:
match
$user has phone $phone;
return { $phone };
Functions within functions
define
fun square($x: integer) -> integer:
match
let $z = $x * $x;
return first $z;
fun karma_sum_and_sum_squared() -> double, double:
match
$karma isa karma;
let $karma-squared = square($karma);
return sum($karma), sum($karma-squared);
The output type here is not a stream because each tuple component is derived from an aggregation, which produces a single result. |
Refer to Calling functions from patterns for more details on calling functions.
Modifying functions
Function undefinition
To undefine a function from a schema, you only need its name, which uniquely identifies it. The following query removes a function defined earlier:
undefine
fun add_streamed;
Function redefinition
To redefine an existing function, use the same define
syntax with the redefine
keyword.
The query below updates a previously defined function to accept a username
instead of a specific user
:
redefine
fun user_phones($username-value: string) -> { phone }:
match
$user isa user, has username $username, has phone $phone;
$username == $username-value;
return { $phone };
You can only redefine existing functions. The function name must remain the same. To change the name, define a new function and undefine the old one. |
Calling functions from patterns
Functions can be called in queries as other statements. As shown in previous examples, to call a function, reference its name and pass the required arguments (if applicable) within parentheses.
For functions returning a single result, use a simple assignment to bind the output variable to the function’s result:
match
let $answer = add(2016, 9);
The |
For functions returning a stream, use let … in
statements to bind variables:
match
$u isa user;
let $phone in user_phones($u);
To handle tuple results, bind the appropriate number of variables to unpack the function’s output:
match
let $karma, $karma-squared in karma_sum_and_sum_squares();
For further details, refer to the TypeQL documentation.
The arguments in a function call are positional (i.e., arguments in the function call in a pattern are lined up with function arguments in its defined function signature). Assigning a function output tuples to variables in a patterns is also positional. |