Queries as Functions

TypeQL function provides a way to create abstractions over existing data, allowing queries to be expressed at a higher level. They can live in your schema - as a "library" your database provides, or be specified in the preamble of a query using the with keyword.

A function accepts a tuple of concepts as arguments, and returns zero or more tuples of concepts as a result. The body of a function can be a pipeline consisting of any number of read stages.

Find mutual friends
#!test[schema]
#{{
define
relation friendship, relates friend @card(0..2);
entity person, plays friendship:friend;
#}}
#!test[schema]
define
fun mutual_friends($p1: person, $p2: person) -> { person }:
match
    $f1 isa friendship, links (friend: $p1, friend: $pm);
    $f2 isa friendship, links (friend: $p2, friend: $pm);
return { $p2 };

Arguments & returns

Function arguments & returns can be instances or values, but not types. The types of these concepts must be declared in the signature.

Integer division returns quotient & remainder for the dividend & divisor
#!test[schema]
define
fun divide($dividend: integer, $divisor:integer) -> integer, integer:
match
 let $quotient = floor($dividend/$divisor);
 let $remainder = $dividend - $quotient * $divisor;
return first $quotient, $remainder;

A function can either return a single tuple of concepts, or a stream of tuples. For a stream return, the declaration is surrounded by curly braces {…​}. As in the example above, a function returning a single tuple must convert the stream output of the pipeline to a single tuple using either the first or the last modifiers.

Reduce returns

Functions can also return an aggregation over the stream output by the body, using the reduce return modifier.

Find the number of posts by a page
#!test[schema]
#{{
define
relation posting, relates page, relates post;
entity page plays posting:page;
#}}
#!test[schema, rollback]
define
fun post_count($page: page) -> integer:
match
  $posting isa posting, links (posted: $page);
return count($posting);

reduce returns a single answer, since it aggregates all answers into one.

Cyclic functions & tabling

TypeDB tables recursive functions to break cycles. This has implications on the best way to implement the recursion. The classic example is computing the transitive closure of a function. In a tabled setting, the following (left-recursive) formulation is more efficient.

#!test[schema]
#{{
define
relation edge, relates from_, relates to;
entity node plays edge:from_, plays edge:to;
#}}
#!test[schema, rollback]
define
fun reachable($from: node) -> { node }:
match
    { # Base case
        $_ isa edge, links (from_: $from, to: $to);
    } or { # Recursive case
        let $mid in reachable($from);
        $_ isa edge, links (from_: $mid, to: $to);
    };
return { $to };

This will return `node`s in a breadth-first fashion.

References