TypeDB 3.0 is live! Get started for free.

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:

  1. We can define functions with “globally” in the schema of your database. Functions defined in the schema can be used in any data pipeline.

  2. 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>. A define query adds a new function to the schema for future use.

  • undefine fun <function_name>. An undefine query removes a function from the schema.

  • redefine <function_name> <function_signature>: <function_body>. A redefine 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.

Example signatures
( $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

Stream arithmetic function definition
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.

Single arithmetic function definition
define
  fun add($x: integer, $y: integer) -> integer:
    match
      let $z = $x + $y;
    return first $z;

Querying for data instances

Stream function definition returning data instances
define
  fun user_phones($user: user) -> { phone }:
    match
      $user has phone $phone;
    return { $phone };

Functions within functions

Tuple function definition with inner function calls
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 let keyword is required for value assignment in this example.

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.