Like notifications, subscriptions are newline terminated strings that parse
as compound Pedro terms. The functor of each subscription term is always
`subscribe`

and has arity 3. In other words, each subscription term
is of the form
`subscribe(`

*Head*`, `

*Body*`, `

*Rock*`)`

.
Following the similarity between subscriptions and Prolog clauses we refer
to the first argument as the *head* of the subscription and the second
argument as the *body* of the subscription.

The third argument is commonly referred to as a *rock*. This is an integer
and its meaning is determined by each client.
When the Pedro server matches a notification against the subscription,
the server sends the notification string together with the rock to the
subscribing client.
A given client
can use the rock to, for example, refer to a message queue or a thread
and thereby determine how to process the notification.

The Pedro server will match a notification against a subscription if the notification term unifies with the head of the subscription and, with this unifier, the goal in the body of the subscription is satisfied. Readers are referred to Prolog references for descriptions of unification, variable binding, dereferencing, occurs check, backtracking and goal evaluation.

When the server matches a notification against a subscription it will send the string consisting of the subscribers rock followed by a space followed by the notification string (including the newline).

The server will acknowledge an attempt by a client to subscribe with a string consisting of an integer (an ID) followed by a newline. The ID will be 0 if the subscription attempt fails (the string is too long, it doesn't parse, or does not represent a valid subscription term). If the subscription attempt succeeds then the ID will be a unique (for that client) positive integer. This ID is used when the client chooses to unsubscribe.

The following table lists the “basic” valid subscription goals and their semantics that can be used in the body of subscriptions. As with Prolog, whenever a unification is carried out, the variable bindings implied by the unifier are applied.

Note that, unlike most Prologs, unification in Pedro uses the occurs check.

`true`

- Always succeeds
`fail`

- Always fails
`T1 = T2`

- Succeeds if and only if the terms
`T1`

and`T2`

unify. `T1 is T2`

- Succeeds if and only if
`T1`

unifies with the evaluation of the the arithmetic expression`T2`

. The goal produces a type error if`T2`

does not represent an arithmetic expression that can be fully evaluated (to a number). The valid arithmetic expressions are described later. `T1 < T2`

- Succeeds if and only if the arithmetic expression
`T1`

evaluates to a number that is less than the evaluation of the arithmetic expression`T2`

. The goal produces a type error if either term does not represent an arithmetic expression that can be fully evaluated (to a number). `T1 =< T2`

- The same as above, except that a less-or-equal test is applied.
`T1 > T2`

- The same as above, except that a greater-than test is applied.
`T1 >= T2`

- The same as above, except that a greater-or-equal test is applied.
`member(X, L)`

- Succeeds if and only if
`L`

is a “cons” pair and`X`

unifies with the head element of`L`

or`X`

is a member of the tail of`L`

. `split(L1, L2, L3)`

- Succeeds if and only if the concatenation of the lists
`L2`

and`L3`

unifies with`L1`

. The list`L1`

must be supplied. `splitstring(S1, S2, S3)`

- Succeeds if and only if the concatenation of the strings
`S2`

and`S3`

unifies with`S1`

. The string`S1`

must be supplied. `number(T)`

- Succeeds if and only if
`T`

is a number. `atom(T)`

- Succeeds if and only if
`T`

is an atom. `string(T)`

- Succeeds if and only if
`T`

is a string. `list(T)`

- Succeeds if and only if
`T`

is either the empty list or a “cons” pair.

The following table lists the valid meta-level subscription goals – i.e. goals that take valid goals as arguments.

`G1 , G2`

- Conjunction: succeeds if and only if first the goal
`G1`

succeeds and then the goal`G2`

succeeds. `G1 ; G2`

- Disjunction: succeeds if and only if either the goal
`G1`

succeed or the goal`G2`

succeeds. `G1 -> G2 ; G3`

- If-then-else: If
`G1`

succeeds then alternative solutions for`G1`

are removed and the goal succeeds if and only if`G2`

succeeds. Otherwise, the goal succeeds if and only if`G3`

succeeds. `not(G)`

- Negation: succeeds if and only if
`G`

fails. In Prolog this form of negations is called “unsafe negation”. `once(G)`

- Succeeds if and only if
`G`

succeeds. Alternative solutions are removed.

The following are valid arithmetic expressions. Numbers are valid arithmetic
expressions and in the table below, `E1`

and `E2`

are valid arithmetic expressions.

`pi`

- Pi
`e`

- E
`- E1`

- Negation
`E1 + E2`

- Addition
`E1 - E2`

- Subtraction
`E1 * E2`

- Multiplication
`E1 / E2`

- Division
`E1 // E2`

- Integer division
`E1 ** E2`

- Exponentiation
`E1 rem E2`

- Remainder (
`E1`

and`E2`

are integer expressions) `E1 mod E2`

- Modulo (
`E1`

and`E2`

are integer expressions) `E1 /\ E2`

- Bitwise And (
`E1`

and`E2`

are integer expressions) `E1 \/ E2`

- Bitwise Or (
`E1`

and`E2`

are integer expressions) `\E1`

- Bitwise Negation (
`E1`

is an integer expression) `E1 << E2`

- Shift Left (
`E1`

and`E2`

are integer expressions) `E1 >> E2`

- Shift Right (
`E1`

and`E2`

are integer expressions) `abs(E1)`

- Absolute Value
`round(E1)`

- Round
`floor(E1)`

- Floor
`ceiling(E1)`

- Ceiling
`sqrt(E1)`

- Square Root
`sin(E1)`

- Sin
`cos(E1)`

- Cos
`tan(E1)`

- Tan
`asin(E1)`

- Arcsin
`acos(E1)`

- Arccos
`atan(E1)`

- Arctan
`log(E1)`

- Log (base e)

The following are examples of valid subscriptions. In all these examples, the rock is zero but can be any integer (e.g. thread ID).

`subscribe(info(fred, X), true, 0)`

: in this example, the goal is`true`

and will always succeed. Hence, this subscription will match against any notification the unifies with the head term – i.e. any compound term whose functor is`info`

, has arity 2 and has first argument`fred`

`subscribe(data(L), (member(height = H, L), H > 1000), 0)`

: the head of this subscription matches against any notification with functor`data`

and arity 1. The subscription will match a notification if the notifications argument is a list that contains a term of the form`height = H`

and`H`

is a number greater than 1000. Note that, as with Prolog, operators can be used to build terms (even when the operator semantics is not being used). In this case the operator`=`

is just a convenient infix operator used to construct an arity 2 compound term.`subscribe(foo(X, X), (X < 10; X > 20), 0)`

: the head matches against any notification with functor`foo`

, has arity 2, and whose arguments are unifiable. The body succeeds if`X`

is either less than 10 or greater then 20.`subscribe(str(S), (splitstring(S, _, S2), splitstring(S2, "hello", _)), 0)`

: this subscription matches any notification with functor`str`

and arity 1 and whose argument is a string containing`"hello"`

as a substring.`subscribe(foo(X, Y), (atom(X) -> number(Y), Y > 0 ; atom(Y)), 0)`

: this subscription matches any notification with functor`foo`

, has arity 2, and if its first argument (`X`

) is an atom then`Y`

is a number greater then 0, else`Y`

is an atom.

The following example illustrates what happens when type errors occur in an attempted match of a subscription and a notification.

Consider the subscription

`subscribe(foo(X, Y), (X < 0 -> Y > 10 ; Y < 10), 0)`

and the notification

`foo(bar, 0)`

In this case the head of the subscription matches the notification but
`X`

is not a number and so the test `X < 0`

produces a type
error which causes the attempted match to fail.

If the intention of the subscription was to test if X is a number less than 0 then it should be written as follows.

`subscribe(foo(X, Y), (number(X), X < 0 -> Y > 10 ; Y < 10), 0)`

In this case the notification above will match.