• Aucun résultat trouvé

An Interpreter for Linda Extensions to GDC

Meta-Interpretation

8.8 An Interpreter for Linda Extensions to GDC

As a final example of a meta-interpreter for a guarded definite clause language, an interpreter is described which adds Linda extensions [Gelernter, 1985] to GDC.

According to Gelerntner, the Linda notation is a set of simple primitives, which may be added to any programming language to introduce concurrency into it. The basis of these notations is that a dynamic database of tuples exists. Actors communicate by asserting, reading and retracting tuples from this database, it may therefore be considered a blackboard system [Engelmore and Morgen, 1988]. The Linda extensions are eval(P), which sets up P as an actor, out(T) which adds the tuple T to the database, in(T) which removes the tuple T from the database and rd(T) which reads the tuple T from the database. In the case of in and rd, the tuple argument may contain unbound variables and there will be a search for a matching tuple in the database; when a match is found the variables in the argument will be bound to the matching values. If no match is found for in or rd the actor which made the call is suspended until another actor adds a matching tuple using out, the suspended in or rd call is then evaluated and the actor which made the in or rd call restarted.

The reason for paying particular attention to implementing Linda extensions to GDC is that Linda has been proposed as a competitor [Carriero and Gelernter, 1989] to GDC. It has achieved popularity in use as a concurrent programming paradigm, though this may be because it can be grafted onto existing languages and thus there is less of a barrier to using it than changing to a novel concurrent language.

Nevertheless, the simplicity of the conceptual model of Linda has led to it being put forward as another way of introducing concurrency into logic programming [Brogi and Cincanini, 1991]. The use of interpreters moves away from “language wars” to the idea of multi-paradigm programming in which different parts of a program may be expressed in whichever paradigm is most suitable for them. Correspondingly, GDC equipped with a range of interpreters is a multi-paradigm language. The next section shows how functional programming may be embedded in GDC using an interpreter while in Chapter 9 an interpreter for an imperative language is given.

Linda extensions may be added to GDC by using an interpreter that passes on a stream of the database handling commands to a database-handling actor. Each actor that is interpreted will produce a stream of requests to the database. Non-primitive actors will merge the streams from the subactors into which they reduce. The Linda primitives will produce a stream consisting of a single database request. Other primitives will produce an empty stream. Since in GDC all actors are concurrent, there is no need for an explicit eval primitive. The following is the interpreter:

reduce(X=Y,S) :- X=Y, S=[].

reduce(in(M),S) :- S=[in(M)].

reduce(rd(M),S) :- S=[rd(M)].

reduce(out(M),S) :- S=[out(M)].

reduce(Actor,S) :- otherwise

| behavior(Actor,Body), reducelist(Body,S).

reducelist([], S) :- S=[].

reducelist([H|T],S)

:- reduce(H, S1), reducelist(T,S2), merge(S1,S2,S).

The top level actor network would be

:- reduce(Actor,Stream), database(Stream).

The database handler needs to keep two lists of tuples. One will be tuples currently in the database. The other will be a list of in and rd requests that are suspended waiting for a tuple to be added. When a tuple is added it is matched against all the suspended in and rd requests. If it matches a suspended in request, it is not taken any further since this in request will cause it to be removed from the database. The matching used is similar to the matching we used in the meta-interpreters previously, except that we need to overcome the problem that matching could fail after binding some channels.

The interpreter is monolingual, so if the binding took place it could not be undone if matching failed later. We reduce the problem by using a version of match which rather than bind any channels returns a list of channels which would be bound and values to which they would be bound if the matching succeeds. If matching does succeed, these bindings take place. This gives the following as the complete database handler:

// Handle an out message by checking against all waiting in and rd // messages. If it has not been matched with a waiting in message, // the tuple is added to the database of tuples.

database([out(M)|S],Waits,Tuples)

:- checkwaits(M, Waits,OutWaits,Found), addtuple(Found,S,M,OutWaits,Tuples).

// Handle an in message by checking against the database of // tuples. If there are no matches, the in message is added to the // list of waiting in and rd requests.

database([in(M)|S],Waits,Tuples)

:- checktuples(M,Tuples,OutTuples,Found), addwait(Found,S,M,Waits,OutTuples).

// Handle a rd message similarly to an in message.

database([rd(M)|S],Waits,Tuples)

:- check(M,Tuples,Found), addrd(Found, S, M, Waits, Tuples).

addtuple(no,S,M,Waits,Tuples) :- database(S,Waits,[M|Tuples]).

addtuple(yes,S,M,Waits,Tuples) :- database(S,Waits,Tuples).

addwait(no,S,M,Waits,Tuples)

:- database(S,[in(M)|Waits],Tuples).

addwait(yes,S,M Waits,Tuples) :- database(S,Waits,Tuples).

addrd(no,S,M,Waits,Tuples)

:- database(S,[rd(M)|Waits],Tuples).

addrd(yes,S,M,Waits,Tuples) :- database(S,Waits,Tuples).

// Check a tuple against waiting in and rd requests. If it is // matched successfully against an in request Found is bound to // “yes”, the in request is removed from the list of waiting // requests and no further checking is done. If it is successfully // matched against a waiting rd request, the request is removed // but checking against further requests continues. If all

// requests are checked and no successful matching with an in // request occurs, Found is bound to “no”.

checkwaits(M,[in(N)|Waits],OutWaits,Found) :- match(M,N,Flag,Matches),

isinmatch(Flag,Matches,M,N,Waits,OutWaits,Found).

checkwaits(M,[rd(N)|Waits],OutWaits,Found) :- match(M,N,Flag,Matches),

isrdmatch(Flag,Matches,M,N,Waits,OutWaits,Found).

checkwaits(M,[],OutWaits,Found) :- Found=no, OutWaits=[].

isinmatch(true,Matches,M,N,Waits,OutWaits,Found) :- Found=yes, domatches(Matches), OutWaits=Waits.

isinmatch(false,Matches,M,N,Waits,OutWaits,Found) :- checkwaits(M,Waits,OutWaits1,Found), OutWaits=[in(N)|OutWaits1].

isrdmatch(true,Matches,M,N,Waits,OutWaits,Found)

:- domatches(Matches), checkwaits(M,Waits,OutWaits,Found).

isrdmatch(false,Matches,M,N,Waits,OutWaits,Found) :- checkwaits(M,Waits,OutWaits1,Found), OutWaits=[rd(N)|Outwaits1].

// Check an in request against the database of tuples.

// If a match is found the matching tuple is taken from the // database of tuples and Found is bound to “yes”. Otherwise // Found is bound to “no”.

checktuples(M,[N|Tuples],OutTuples,Found) :- match(N,M,Flag,Matches),

ismatch(Flag,Matches,M,N,Tuples,OutTuples,Found).

checktuples(M,[],OutTuples,Found) :- Found=no, OutTuples=[].

// Perform the channel binding if matching succeeds.

ismatch(true,Matches,M,N,Tuples,OutTuples,Found)

:- domatches(Matches), OutTuples=Tuples, Found=yes.

ismatch(false,Matches,M,N,Tuples,OutTuples,Found) :- checktuples(M,Tuples,OutTuples1,Found),

OutTuples=[N|OutTuples1].

domatches([Y/X|Matches]) :- Y=X, domatches(Matches).

domatches([]).

// Check a rd request against the database of tuples, bind Found // to “yes” if a match is found, to “no” otherwise.

check(M,[N|Tuples],Found) :- match(N,M,Flag,Matches),

isfound(Flag,Matches,M,Tuples,Found).

check(M,[],Found) :- Found=no.

isfound(true,Matches,M,Tuples,Found) :- Found=yes, domatches(Matches).

isfound(false,Matches,M,Tuples,Found) :- check(M,Tuples,Found).

// A version of match which binds V to “true” if matching // succeeds, “false”otherwise and which returns a list of // channel bindings to be performed only if the complete match // succeeds.

match(X,Y,V,Matches) :- unknown(Y) | Matches=[Y/X], V=true.

match(X,Y,V,Matches) :- X==Y | V=true, Matches=[].

match(X,Y,V,Matches) :- X=/=Y | V=false, Matches=[].

match(X,Y,V,Matches) :- list(X), list(Y)

| matchlist(X,Y,V,Matches).

match(X,Y,V,Matches) :- tuple(X), tuple(Y)

| X=..LX, Y=..LY, matchlist(LX,LY,V,Matches).

matchlist([],[],V,Matches) :- V=true, Matches=[].

matchlist([H1|T1],[H2|T2],V,Matches)

:- match(H1,H2,VH,Matches1), matchlist(T1,T2,VT,Matches2), andp(VH,VT,V), merge(Matches1,Matches2,Matches).

matchlist(X,Y,V,Matches) :- unknown(Y)

| Matches=[Y/X], V=true.

andp(true,true,V) :-V=true.

andp(false,_,V) :- V=false.

andp(_,false,V) :- V=false.

The behavior representation below will reduce the Dining Philosophers problem (Section 4.12) in a way similar to that described in [Carriero and Gelernter, 1989].

The idea is that a chopstick available for use is represented by a tuple in the database.

When a philosopher actor wishes to use a chopstick, an in command for the chopstick is issued. If the chopstick is already in use by another philosopher the philosopher actor will suspend until that other philosopher has finished with the chopstick and issued an out command on it, putting it into the tuple database and awakening the in command. To avoid deadlock, only four philosophers are allowed in the dining room at any time. Initially, this is represented by having four meal tickets in the database.

A philosopher must issue an in command on a meal ticket and receive it before entering the dining room, the ticket is put out again when the philosopher finishes eating. Philosophers are represented by the indices of the chopsticks they use to eat.

behavior(hungryphil(hungry,F1,F2),Body) :- Body=[in(ticket(T)),enteringphil(T,F1,F2)].

behavior(enteringphil(ticket,F1,F2),Body)

:-Body=[in(chopstick(F1,T1)),in(chopstick(F2,T2)), eatingphil(T1,T2,F1,F2)].

behavior(eatingphil(chopstick,chopstick,F1,F2), Body) :- Body=[eat(E), exitingphil(E,F1,F2)].

behavior(exitingphil(full,F1,F2), Body) :- Body=[out(chopstick(F1,chopstick)),

out(chopstick(F2,chopstick)),

out(ticket(ticket)), thinkingphil(F1,F2)].

behavior(thinkingphil(F1,F2), Body)

:- Body=[think(H),hungryphil(H,F1,F2)].

behavior(init, Body)

:- Body=[out(chopstick(1,chopstick)),

out(chopstick(2,chopstick)),out(chopstick(3,chopstick)), out(chopstick(4,chopstick)),out(chopstick(5,chopstick)), out(ticket(ticket)),out(ticket(ticket)),

out(ticket(ticket)),out(ticket(ticket)),

thinkingphil(1,2),thinkingphil(2,3),thinkingphil(3,4), thinkingphil(4,5),thinkingphil(5,1)].

Note that here it has been necessary to include explicit sequencing in the interpreted program. For example a meal ticket is represented by the 1-tuple ticket(ticket) rather than the 0-tuple ticket. A message in(ticket(T)) will bind T to ticket when a tuple ticket(ticket) is in the database. The actor enteringphil(T,F1,F2) will suspend until T is bound. Without this sequencing the calls in(ticket) and enteringphil(F1,F2) would proceed in parallel. That is, a philosopher would not wait for the meal ticket to become available. Similarly, the n-th chopstick is represented by the tuple chopstick(n,chopstick) rather than just chopstick(n). It is assumed that think(H) will bind H to hungry after a suitable interval of time and eat(E) will similarly bind E to full.

A more complex form of the interpreter would make the sequencing implicit, using a similar technique to that we used to introduce sequentiality previously. In this case, it

is necessary to introduce the explicit primitive eval for the cases where we want a concurrent actor to be spawned:

reduce(X=Y,S,Flag) :- unify(X,Y,Flag), S=[].

reduce(in(M),S,Flag) :- S=[in(M,Flag)].

reduce(rd(M),S,Flag) :- S=[rd(M,Flag)].

reduce(out(M),S,Flag) :- S=[out(M,Flag)].

reduce(eval(T),S,Flag) :- reduce(T,S,_), Flag=done.

reduce(Actor,S,Flag) :- otherwise

| behavior(Actor,Body), reducelist(done,Body,S,F).

reducelist(Flag1,[],S,Flag2) :- S=[], Flag2=Flag1.

reducelist(done,[H|T],S,Flag) :- reduce(H,S1,Flag1),

reducelist(Flag1,T,S2,Flag), merge(S1,S2,S).

Note that the Flag channel is added to the in, rd and out messages passed to the database handler. It is assumed that the database handler will bind Flag to done when the operation is completed. In the case of in and rd messages if there is no matching tuple in the database, the requests will be queued as before, each with its associated flag and the flag eventually will be bound when a matching tuple is added by an out message.

Using this interpreter the program for the dining philosophers is:

behavior(hungryphil(F1,F2),Body)

:- Body=[in(ticket),enteringphil(F1,F2)].

behavior(enteringphil(F1,F2),Body)

:- Body=[in(chopstick(F1)),in(chopstick(F2)),eatingphil(F1,F2)].

behavior(eatingphil(F1,F2),Body) :- Body=[eat,exitingphil(F1,F2)].

behavior(exitingphil(F1,F2),Body)

:- Body=[out(chopstick(F1)),out(chopstick(F2)),out(ticket), thinkingphil(F1,F2)].

behavior(thinkingphil(F1,F2),Body) :- Body=[think,hungryphil(F1,F2)].

behavior(init,Body)

:- Body=[out(chopstick(1)), out(chopstick(2)), out(chopstick(3)), out(chopstick(4)),

out(chopstick(5)), out(ticket), out(ticket), out(ticket), out(ticket), eval(thinkingphil(1,2)),

eval(thinkingphil(2,3)), eval(thinkingphil(3,4)), eval(thinkingphil(4,5)), eval(thinkingphil(5,1))].