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.
truefailT1 = T2T1 and T2 unify.
T1 is T2T1 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 < T2T1 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 =< T2T1 > T2T1 >= T2member(X, L)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)L2 and L3
unifies with L1. The list L1 must be supplied.
splitstring(S1, S2, S3)S2 and S3 unifies with S1. The string S1 must be supplied.
number(T)T is a number.
atom(T)T is an atom.
string(T)T is a string.
list(T)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 , G2G1 succeeds and
then the goal G2 succeeds.
G1 ; G2G1 succeed or
the goal G2 succeeds.
G1 -> G2 ; G3G1 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)G fails. In Prolog this form of
negations is called “unsafe negation”.
once(G)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.
pie- E1E1 + E2E1 - E2E1 * E2E1 / E2E1 // E2E1 ** E2E1 rem E2E1 and E2 are integer expressions)
E1 mod E2E1 and E2 are integer expressions)
E1 /\ E2E1 and E2 are integer expressions)
E1 \/ E2E1 and E2 are integer expressions)
\E1E1 is an integer expression)
E1 << E2E1 and E2 are integer expressions)
E1 >> E2E1 and E2 are integer expressions)
abs(E1)round(E1)floor(E1)ceiling(E1)sqrt(E1)sin(E1)cos(E1)tan(E1)asin(E1)acos(E1)atan(E1)log(E1)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.