Example Interaction Syntax Design Patterns Annotation Visualisation
Example Interaction
The diagram in Figure 1 shows an interaction between three peers: p1, p2 and p3. Each peer knows different things:
- p1 knows that queries asking about p(Y) can be sent to p2 and that queries asking about q(Z) can be sent to p3. We write this as query_from(p(Y),p2) and query_from(q(Z),p3).
- p2 knows that p(a) is true. We write this as know(p2, p(a)).
- p3 knows that q(b) is true. We write this as know(p3, q(b)).
The interaction we require is depicted by the numbered messages in the diagram:
- p1 sends a message ask(p(Y)) to p2.
- p2 sends a message ask(p(a)) to p1.
- p1 sends a message ask(q(Z)) to p3.
- p3 sends a message ask(q(b)) to p1.

Figure 1. Basic Interaction Example
Let us first define an interaction model that does exactly the message passing defined above. There are two roles that agents take in this model: the role of a requester (which asks for information) and the role of an informer (which supplies information). We define a LCC clause for each role as shown below. For the requester (p1) we have simply given the sequence of four messages corresponding to those above. Then we have defined a clause for the role of informer that defines the behaviour expected of p2 and p3.
a(requester, A) ::
ask(X1) => a(informer, p2) <-- query_from(X1, p2) then
tell(X1) <= a(informer, p2) then
ask(X2) => a(informer, p3) <-- query_from(X2, p3) then
tell(X2) <= a(informer, p3) (1.1)
a(informer, B) ::
ask(X) <= a(requester, B) then
tell(X) => a(requester, B) <-- know(X)
The LCC definition above covers the example but suppose we want a more general type of requester that takes a list, L, of the form [q(Query,Peer), ...], where Query is the query we want to make and Peer is an identifier for the peer to which we want to send the query. We want the requester to send an ask(Query) message to the appropriate Peer for each query and receive a tell(Query) reply each time. A standard way to do this is by giving L as a parameter to the requester role (so it becomes requester(L)) and making the definition of this role recursive, taking the first element of L and then applying the same definition to the remainder of the list, Lr, as shown below.
a(requester(L), A) ::
(ask(Query) => a(informer, Peer) <-- L = [q(Query, Peer)|Lr] then
tell(Query) <= a(informer, Peer) then
a(requester(Lr),A))
or
null <-- L = []
a(informer, B) ::
ask(X) <= a(requester(), B) then
tell(X) => a(requester(), B) <-- know(X)
(1.2)
If we were to run the interaction model shown above, starting with the role of requester for the list of queries [q(ask(p(X)),p2),q(ask(q(Y)),p3)], then we get the message sequences shown in Figure 2. On the left is the sequence for a(requester([q(ask(p(X)),p2),q(ask(q(Y)),p3)]), p1). On the right are the sequences for a(informer, p2) and a(informer, p3) which are the roles undertaken by p2 and p3 in response to p1. The dashed lines indicate synchronisation via message passing between peers.

Figure 2. Event Sequences for the Example
In our earlier example (definition 1.1 above) we made some of the message passing events contingent on constraints. For example sending the message ask(X1) => a(informer, p2) was contingent on satisfying the constraint query_from(X1, p2). These constraints are satisfied by connecting them to methods for computing the constraint. Although some basic methods (such as for basic forms of visualisation) are pre-supplied by OpenKnowledge we expect that most methods will be specific to application domains and so will need to be written or re-used by interaction model developers. To make it possible to share these methods, we allow appropriately packaged methods to be shared, so that peers can accumulate repositories of methods that they find useful. We call these OpenKnowledge Components (OKCs). A more detailed description of OKCs can be found here.
Syntax
Each LCC interaction model is defined by a set of clauses where each clause has the syntax shown in Figure 3. Each clause is a self contained definition of a role, with message passing being the only means of transferring information between roles. Message passing is also the only means of synchronisation between roles.
Clause := Role :: Def
Role := a(Type, Id)
Def := Role | Message | Def then Def | Def or Def
Message := M ⇒ Role | M ⇒ Role ← C | M ⇐ Role | C ← M ⇐ Role
C := Constant | P(T m, ...) | ¬C | C ∧ C | C ∨ C
Type := Term
Id := Constant | V ariable
M := Term
Term := Constant | Variable | P(Term, ...)
Constant := lower case character sequence or number
Variable := upper case character sequence or number
Figure 3. LCC Syntax
In the sections which follow we explain what each element of LCC syntax means from a programming point of view.
- Variables, Constants, Terms, IDs and Roles
- Messages
- Constraints
Variables must start with an upper case letter. The scope of a variable is local to a clause (in other words, if you use the same variable name in different clauses then these names refer to different variables). When it is unnecessary to give a specific name for a variable (because it is not used elsewhere in a clause) you can use an underscore (_) for the variable name. Constants must start with a lower case letter. Numbers also are constants. Terms are tree-structured - that is, they are either a constant or are of the form F(A1, ..., An) where F is a non-numerical constant and each Ai is a term. IDs are unique identifiers for peers which must be non-numerical constants. Roles are terms that describe the type of role played by a peer in a given interaction.
There are two types of messages:
Incoming Messages : are of the form Term ⇐ a(Role,ID), where Term is the content of the message. When using ASCII, the symbol ⇐ is written using <=.
Outgoing Messages : are of the form Term ⇒ a(Role,ID), where Term is the content of the message. When using ASCII, the symbol ⇒ is written using =>.
Constraints can be attached to both incoming and outgoing messages (see below).
Constraints associate message passing events with conditions established by the peer.
Message ← constraint(Arg1, ..., ArgN) (1.3)
Constraints also may be associated with the special null event which represents an event that is not associated with a specific message. This frequently is used in recursive role definition where terminating the role depends on a parameter to the role, rather than a specific message passing event.
When using ASCII the constraint operator ← should be written using <-.
Visual Constraints
A constraint can have a mapping made available to it using the visual(,) operator. The visual operator maps a constraint to a visual term. Visual terms provide an abstract representation for a particular type of user interaction.
visual(constraint(Arg1, ..., ArgN), visualTerm(vArg1, ..., vArgN)) (1.4)
The OpenKnowledge kernel has a number of built-in visual term implementations, listed below:
msg(M〈,T〉) Display a message M to the user, with the optional title T.
text(〈T,〉M) Display a large amount of text in M to the user, with the optional title T.
input(〈Q,〉V) Ask the user to input some value into V, providing optional question text in Q.
List Operations
List operations are a common basis of the recursion techniques available when writing LCC. List operations make use of the bar | operation that delineates the head (H, first element) of a list from the rest of the list (T, the tail); that is, L = [H|T].
In the case that H has some value, you can append this value to the head of the list using the following constraint:
... ← L = [H|T] (1.5)
For example, if before the operation H contained the value 5, and the list, L, contained [6,7,8], after the operation the list would contain the values [5,6,7,8].
In the case that H is not set, the following LCC will extract into H the value of the head of the list.
... ← L = [H|T] (1.6)
Notice that T is itself a list, so that the head of T will be the second element in the list. This allows for recursion on T. If the list is empty, and no value for H can be determined, the constraint will fail. For example, if before the operation H was unset and the list, L, contained [6,7,8], after the operation H would have the value 6 and the T would have the value [7,8].
To test whether a list is empty, use the following LCC:
... ← L = [] (1.7)
This constraint will fail if L is not empty.
Logical Operators
Constraints can be connected by the logical operators and and or:
- C1 and C2 succeeds if both constraints C1 and C2 succeed, with C1 being attempted first.
- C1 and C2 succeeds if one of the constraints C1 and C2 succeeds, with C1 being attempted first.
To comment your LCC you can use the C-like comments //... or /*...*/. The double-slash comment form will make the interpreter ignore the rest of the line. The slash-star comment form will ignore everything until the next star-slash. The following are valid comments:
// A valid single line comment // Another single line comment /* A valid multi-line comment */
The basic operations used in LCC to determine the sequence of messages in a clause are sequence and choice, as defined below (where E1 and E2 are sequence expressions or message passing events):
Sequence : is written as E1 then E2. This sequence is completed if both E1 or E2 is completed, with E1 being completed first.
Choice :is written as E1 or E2. This sequence is completed if either E1 or E2 is completed, with E1 being attempted first. E2 will only be attempted if E1 fails. If E1 succeeds then E2 will not be attempted.
Design Patterns
Perhaps the easiest way to understand LCC programming is through design patterns. These are standard ways of structuring clauses that are
used to obtain specific forms of interaction. The broad idea is similar to design patterns in more traditional languages but the good news for
LCC is that you only need to know a small number of patterns, which you then combine to make more complex programs. The four key patterns
are given below.
- Pattern 1: Interaction
The simplest thing we can do with LCC is to specify a message being sent from one peer to another. To do this we decide the role (r1) being taken by the sender; then write M out a(r2, Y) if C to describe the message, M, being sent out to the recipient, Y, which is expected to receive it in role r2. The constraint C1 is used to determine whether this message can be sent by the sender, and it often is used also to determine values for any variables that appear in M.. In the specification of the recipient's role we write C2 if M in a(r2, X) to describe the message, M, being received, with C2 giving a constraint that should hold as a consequence of receiving it.
a(r1,X) ::
. . .
M ⇒ a(r2, Y ) ← C1
. . .
(1.8)
a(r2, Y ) ::
. . .
C2 ← M ⇐ a(r2,X)
. . .
An example of using this pattern is an interaction that sends a message, M, to a recipient, Y, where the choice on M is made by the constraint message(M) and the choice of recipient is made by the constraint recipient(Y). Acceptance of the message by the recipient is determined by the constraint accept(M).
a(sender, X) ::
M ⇒ a(recipient, Y) ← message(M) and recipient(Y)
(1.9)
a(recipient, Y) ::
accept(M) ← M ⇐ a(sender, X)
Usually we want to put an ordering on the sequence of events that can occur as part of a role. To do this we use the "then" operator to say that the earlier event, E1, comes before the later event, E2.
a(r,X) ::
. . .
E1 then (1.10)
E2
. . .
An example that uses this pattern twice is when the recipient of the message returns a message to the sender, where response(M1, M2) is a constraint determining the recipient's response message, M2, from the sender's message, M1.
a(sender,X) ::
M1 ⇒ a(recipient, Y ) ← message(M1)and recipient(Y ) then
accept(M2) ← M2 ⇐ a(recipient, Y )
(1.11)
a(recipient, Y ) ::
accept(M1) ← M1 ⇐ a(sender,X) then
M2 ⇒ a(sender,X) ← response(M1,M2)
We may want a peer taking some role, r, in an interaction to make a choice about the course of its interaction with other peers. This is done by writing E1 if C1 or E2 if C2 to say that the interaction described by E1 should be done under the conditions stipulated by constraint C1 or the interaction described by E2 should be done under the conditions stipulated by constraint C2. The choice we are making here is a committed choice, meaning that if C1 is satisfied then the alternative choice (E2 if C2) will not be attempted.
a(r,X) ::
E1 ← C1
or (1.12)
E2 ← C2
. . .
An example of this pattern is when a buyer wants to send a message to a seller accepting some Offer (received earlier in the definition of
the buyer role) if it is acceptable or otherwise it sends a message to the seller rejecting that Offer if it is unacceptable.
a(buyer, X) ::
. . .
accept(Offer) ⇒ a(seller, Y) ← acceptable(Offer) (1.13)
or
reject(Offer) ⇒ a(seller, Y) ← unacceptable(Offer)
Since LCC makes committed choices, we know in this example that if acceptable(Offer) is satisfied then the second option (in which the peer attempts to satisfy unacceptable(Offer)) will not be attempted, so if testing unacceptability is not important then we might shorten this example to:
a(buyer, X) ::
. . .
accept(Offer) ⇒ a(seller, Y) ← acceptable(Offer) (1.14)
or
reject(Offer) ⇒ a(seller, Y)
Often we want an interaction to be controlled by some data structure, for example we might want to have a similar sub-interaction for each
of the elements in a list (as in the basic example above). The pattern below describes this. In the pattern r(A) is a role, r, with the
data structure as its argument, A. Somewhere within the definition of the of the role appears a constraint, R(A, Ar), that reduces A to some "smaller" structure, Ar. Then the role recurses as r(Ar). Normally there also is an alternative choice for when the data structure does not reduce any further but meets some test, P(A), that it is has reached some terminating state.
a(r(A),X) ::
(. . . R(A,Ar) . . .
a(r(Ar),X)) (1.15)
or
(. . . P(A) . . .)
One example of using this pattern is an interaction that sends as a message each element, M, from a list [M1,...] to peer p2 in role r2.
a(r(A), X) ::
(M ⇒ a(r2, p2) ← A = [M|Ar] then
a(r(Ar), X) ) (1.16)
or
(null ← A = [] )
A second example is an interaction that sends N messages to peer p2, each with the same content, M. Here, N and M are parameters to the role, r.
a(r(N, M), X) ::
(M ⇒ a(r2, p2) ← N > 0 and N1 is N - 1 then
a(r(N1, M), X)) (1.17)
or
(null ← N =< 0)
Annotation
An LCC model only defines the abstract exchange of messages between peers, and the preconditions and postconditions on those message. The messages and the constraints are first order predicates, and the arguments are variables.
However, constraints need to be matched against methods in OKCs. The OKC methods are described by the Java Annotation @MethodSemantic:
@MethodSemantic(
language="tag";
args={"title"}
)
public boolean askTitle(Argument t){...}
The arguments can be complex objects:
@MethodSemantic(
language="tag";
args={"email(subject,priority,body)"}
)
public boolean writeEmail(Argument email){...}
In this case, the argument email will contain a structure composed of three elements:
email
|- subject
|- priority
|- body
Interaction Models can be annotated as well. The annotations mechanism is general and can be used for different purposes. In particular, the LCC annotations are used to semantically annotate the variables in roles and to annotate messages and constraints for enabling their logging during an interaction run.
Visualisation
Visualisers are small user interface modules that are used to allow the user to satisfy constraints in an interaction model. They are entirely distinct from the user interface, but the user interface is responsible for providing a means for displaying them on the screen. Visualisers are implemented in the same way as OKCs.
Visualisations can be suggested through the use of annotations in the LCC. For example, the getMyName constraint might be annotated in the LCC as follows:
@annotation( @constraint( getMyName(N) ), visual( qask("Please enter your name",N) ) )
As with other annotations, the first parameter defines the constraint to which the annotation is associated. The second part of the annotation utilises the "visual" annotation label. The definition of the visual annotation refers to a specific visualisation, in this case "qask" which asks the user a question.
How qask is implemented is independent of both the OKC and (potentially) the user interface. It is, however, specific to the platform on which the visualisation is running. For example, an image viewer provided on a mobile phone will be different to that provided on a desktop PC. It could be that specific peers and user interfaces that implement specific applications on top of the OpenKnowledge system will have specific visualisers that provider greater user interface integration, however, these interfaces must be robust to changes in the network that might mean different interaction models are used and therefore different visualisations are requested.
