5 Subscriptions

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.