Skip to content
This site is for a previous version of RelationalAI.
Partial Relational Application

Partial Relational Application

Expr := Expr "[" (Expr {"," Expr}*)? "]"

The square bracket syntax is used to project and select a slice of the relation. It is related to currying in functional languages in the sense that relations can be partially applied.

Unlike in an application written with parentheses, in a partial relational application the number of arguments can be smaller than the arity of the relation. For instance, if p is a binary relation then all applications of p must have two arguments: p(x, y) is an example. A relational application with parentheses is a formula, that is, a nullary relation.

This is not always the case for partial relational applications, for example, p[x]. The syntax mimics that of array and matrix indexing from other languages. If relation p is binary, then p[x] is a unary relation, unless it is empty. Note, however, that p[x, y] — or, equivalently, p[x][y] — is a nullary relation. In this case p[x, y] is equivalent to p(x, y), i.e., a partial relational application can sometimes be a relational application.

How you evaluate a relational application also applies largely to how you evaluate a partial relational application. The main difference is that if the number of arguments is M, and some of the selected tuples are longer than M, then the result will be a relation containing the selected tuples stripped of their first M elements. This is further discussed in the remainder of this section.

In a partial relational application, the part of the expression that is evaluated in the square brackets will not be a part of the result. This behavior is useful, in particular, for accessing information that is stored in a key-value format.

It is also useful for evaluating functions, even those that are not stored in the database. For example, consider the following relation:

   def square_add[x, y] = x * x + y

The result of evaluating square_add[2, 3] is the relation {(7,)}.

The result of a partial relational application is always a relation. For example, p[_] does not just strip away the first element of each triple, but forms a relation from the resulting tuples. As a result, the number of tuples may be decreased.

// read query
def p = (1, 2, 3); (10, 2, 3); (10, 2, 4)
def output = p[_]

You can think of a partial relational application as a combination of two operations: filtering and projection. Here is an example:

// read query
{(1, 3); (2, 4); (1, 5); (2, 6)}[2]

You can also use the syntax with square brackets to define relations:

  def name[1] = "Noether", "Emmy"
  def name[2] = "Hopper", "Grace"
  def name[3] = "Curie", "Marie"
  def age[1] = 53
  def age[2] = 85
  def age[3] = 66

This defines a ternary relation name with tuples such as (3, Curie, Marie) and a binary relation age with tuples such as (1, 53).

The relations can be queried as follows:

name[2]{"(Hopper", "Grace")}.
name[3, "Curie"]{"Marie"}.
name[x-1 from x in {1; 2}]{("Noether", "Emmy")}.
(name[p], age[p] from p)[_]{("Emmy", 53); ("Grace", 85); ("Marie", 66)}.
(age[p], name[p] for p)[(x : x%2 = 1), (x: x>60)]{("Curie", "Marie")}.

In the fourth example (name[p], age[p] from p) has tuples such as ("Noether", "Emmy", 53), and the [_] just drops the first element.

The last example is equivalent to (age[p], name[p] for p)[x : x%2 = 1][x: x>60]. The second tuple of (age[p], name[p] for p), namely (2, 85, "Hopper", "Grace"), is filtered away by x: x%2 = 1, so the remaining tuples lose their first element and now begin with age. They are then filtered by x > 60, and age is removed from the remaining tuple.

Some partial relational applications can be written without square brackets. These are the applications of relations whose initial elements are Symbols. For example, person:address:city is shorthand for the partial relational application person[:address][:city].

Partial relational applications are convenient and allow you to write more elegant models. But, strictly speaking, they do not extend the expressive power of the language. It is always possible to rewrite an expression involving a partial relational application as an equivalent expression that does not include a partial relational application.

Here is an example:

// read query
def square_add[x in Number, y in Number] = x * x + y
def output:A = square_add[square_add[2, 3], square_add[3.0, 4]]
def output:B(result) = square_add(2, 3, one) and square_add(3.0, 4, two)
                       and square_add(one, two, result)
                       from one, two

The example clearly shows the advantages of using partial relational applications.

If you do not know the arity of a relation, or if the arity is not fixed, you can simulate partial relational application by using varargs:

// read query
def r = (1); (2, 3); (4, 5, 6)
def output = r[_]
// read query
def r = (1); (2, 3); (4, 5, 6)
def output(x...) = r(_, x...)

A partial relational application consists of two elements: the expression that identifies the applied relation, and the arguments in square brackets. The two are combined with an implicit “application operator.” The precedence of this invisible operator is lower than that of the composition operator, .. So an expression such as R . Q[x] is equivalent to (R . Q)[x]. If that is not what you want, you should put the partial relational application in parentheses: R . (Q[x]).

Next: Cartesian Product and Conjunction

Was this doc helpful?