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.
#!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.
#!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.
#!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);
|
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.