• Aucun résultat trouvé

GDC Bilingual Interpreters

Meta-Interpretation

8.7 GDC Bilingual Interpreters

The problems with monolingual interpreters became more apparent in Section 8.6.

The mapping of object-level variables to metalevel variables resulted in the need to introduce a variety of extra-logical primitives, culminating in copy in the all-solutions interpreter for general logic programs. The problem is that a single GDC variable with its single-assignment property cannot be used to model a variable in a logic language with non-deterministic backtracking as such a variable can be reassigned its value on backtracking. What is needed is a separate representation of object-level variables by ground terms at the metalevel, that is a bilingual interpreter.

In fact we have already seen a range of bilingual interpreters in Chapter 6. Search programs specifically for the 8-puzzle were generalized generic search programs that could be used for any system where a state is rewritten non-deterministically by a set of rewrite rules. We could therefore consider the rules by which the successors to a state are generated in the successors actor in Chapter 6 to be the object level program and the various search programs to be metalevel programs.

While the object-level rules in Chapter 6 could be simple rules for showing the possible changes of state in something like the 8-puzzle, they could equally well be a complete set of rewrite rules specifying how a set of sentences in logic could be changed using resolution. This would then make these rules themselves a metaprogram, with the logic sentences being the object-level programs and the search programs meta-metaprograms. If the search program used involved priorities and these were implemented using a meta-interpreter as suggested above, this interpreter would be a meta-meta-metalevel interpreter for the logic sentences. Note that this

four-leveled layering of interpreters corresponds to the proposed four layers in the KADS methodology for knowledge-based systems development noted previously.

Below is a simple implementation of Chapter 6’s successors for the case where the state stored in a node in the search tree is a list of Goals in a Prolog-like language, together with an environment giving the values of variables in the Goals. It is assumed that variables in the Goals are represented by ground terms in GDC:

successors(state([Goal|Goals],Env), Succs) :- Goal=..[Functor|Args],

clauses(Functor,Clauses),

expandGoal(Args,Goals,Env,Clauses,Succs).

expandGoal(Args,Goals,Env,[],Succs) :- Succs=[].

expandGoal(Args,Goals,Env,[clause(Head,Body,Vars)|Clauses],Succs) :- append(Vars,Env,Env2),

unify(Args,Head,Env2,Env1,Flag),

expandGoal1(Flag,Args,Goals,Body,Env,Env2,Clauses,Succs).

expandGoal1(false,Args,Goals,Body,Env,Env1,Clauses,Succs) :- expandGoal(Args,Goals,Env,Clauses,Succs).

expandGoal1(true,Args,Goals,Body,Env,Env1,Clauses,Succs) :- append(Body,Goals,Goals1),

expandGoal(Args,Goals,Env,Clauses,Succs1), Succs=[state(Goals1,Env1)|Succs1].

Here, the actor clauses returns a representation of the clauses associated with the input goal name in the form of a list of triples. Each of these contains the head arguments in list form, the body of the clause and a separate environment for the variables in the clause (assuming a mechanism to make these fresh variables) each linked with the value unbound. Clearly the issol required in the search programs in Chapter 6 returns a solution found when the list of outstanding goals becomes empty:

issol(state([],Env),Flag) :- Flag=true.

issol(state([Goal|Goals],Env),Flag) :- Flag=false.

It can be seen that this version of successors always takes the first goal from the list of outstanding goals and appends the body of any clause which it matches to the front of the remaining goals. Therefore the goal ordering of Prolog is built-in. The clause ordering is not, however, built-in since it will depend on the order in which the search tree is searched. The search order also is not built-in and is determined at the level of the search program.

The GDC code for unify sends true in its fifth argument if its first and second arguments unify with the variable bindings of the environment of its the third argument, giving the updated variable bindings as output in the fourth argument. If unification is not possible, false is returned in the fifth argument, otherwise true is returned here. This flag value is passed into expandGoal1, which adds a successor state to the list of successor states if a successful unification was achieved.

If a variable at the object level is represented by the ground term var(<name>) where <name> is some constant unique for each separate variable and environment is a list of <name>/<value> pairs, the following code will implement unify:

unify([H1|T1],[H2|T2],IEnv,OEnv,Flag) :- unify(H1,H2,IEnv,MEnv,Flag1),

unify1(Flag1,T1,T2,MEnv,OEnv,Flag).

unify(X1,X2,IEnv,OEnv,Flag) :- X1==X2 | OVars=IVars, Flag=true.

unify(var(A),var(B),IEnv,OEnv,Flag)

:- lookup(A,IEnv,AVal), lookup(B,IEnv,BVal), unifyvars(A,B,IEnv,AVal,BVal,OVars,Flag).

unify(var(A),X2,IEnv,OEnv,Flag) :- X2=/=var(B)

| lookup(A,IEnv,AVal), setvar(A,AVal,X2,IVars,OVars,Flag).

unify(X1,var(A),IEnv,OEnv,Flag) :- X1=/=var(B)

| lookup(A,IVars,AVal), setvar(A,AV,X1,IEnv,OEnv,Flag).

unify(X1,X2,IEnv,OEnv,Flag) :- X1=/=var(A), X2=/=var(B), X1=/=X2

| Flag:=false, OEnv=IEnv.

unify1(false,X1,X2,IEnv,OEnv,Flag) :- Flag=false.

unify1(true,X1,X2,IEnv,OEnv,Flag) :- unify(X1,X2,IEnv,OEnv,Flag).

unifyvars(A,B,IEnv,unbound,BVal,OEnv,Flag) :- BVal=/=unbound

| bind(A,BVal,IEnv,OEnv), Flag=true.

unifyvars(A,B,IEnv,AVal,unbound,OEnv,Flag) :- AVal=/=unbound

| bind(B,AVal,IEnv,OEnv), Flag=true.

unifyvars(A,B,IEnv,unbound,unbound,OEnv,Flag) :- A<B

| bind(B,var(A),IEnv,OEnv), Flag=true.

unifyvars(A,B,IVars,unbound,unbound,OEnv,Flag) :- A>B

| bind(A,var(B),IEnv,OEnv), Flag=true.

unifyvars(A,B,IEnv,AVal,BVal,OEnv,Flag) :- AVal=/=unbound, BVal=/=unbound

| unify(AVal,BVal,IEnv,OEnv,Flag).

lookup(A,[B/Val|Env],AVal) :- A==B | AVal=Val.

lookup(A,[B/Val|Env],AVal) :- A

=/=

B | lookup(A,Env,AVal).

setvar(A,unbound,X,IEnv,OEnv,Flag) :- Flag=true, bind(A,X,IEnv,OEnv).

setvar(A,var(B),X,IEnv,OEnv,Flag)

:- lookup(B,IVars,BVal), setvar(B,BVal,X,IEnv,OEnv,Flag).

setvar(A,V,X,IEnv,OEnv,Flag) :- V==X

| Flag=true, OEnv=IEnv.

setvar(A,[HV|TV],[HX|TX],IEnv,OEnv,Flag) :- unify(HV,HX,IEnv,MEnv,Flag1),

unify1(Flag1,TV,TX,MEnv,OEnv,Flag).

setvar(A,[H|T],X,IEnv,OEnv,Flag) :- X=/=[HX|TX]

| Flag=false, OEnv=IEnv.

setvar(A,V,[HX|TX],IEnv,OEnv,Flag) :- V=/=[HV|TV], V=/=var(B)

| Flag=false, OEnv=IEnv.

setvar(A,V,X,IEnv,OEnv,Flag) :- X=/=[HX|TX], X=/=V, V=/=var(B)

| Flag=false, OEnv=IEnv.

bind(A,AVal,[B/BVal|Env],Env1) :- A==B

| Env1=[A/AVal|Env].

bind(A,AVal,[B/BVal|Env],Env1) :- A

=/=

B

| bind(A,AVal,Env,Env2), Env1=[B/BVal|Env2].

The cost of the bilingual interpreter in having to implement unification completely rather than inheriting any unification from the underlying system is apparent.

However, the division into layers means that once we have constructed this layer implementing the resolution and unification, it may be incorporated with any of the search programs in Chapter 6 which will add more control and also give the precise nature of the output. For example, it could be used to give a single solution or all solutions. One point to note is that the bilingual nature of the interpreter means that it avoids use of extra-logical primitives, such as the copy that was needed in our all-solutions interpreter above and is also needed in the OR-parallel Prolog interpreter proposed by Shapiro [1987]. The importance of this will become more apparent when partial evaluation of meta-interpreters is considered in Chapter 9.

In order to duplicate exactly Prolog’s search order though, a search program is required that searches in depth-first order on demand, which was not given in Chapter 6. The following version of search will do this:

search(State,[],OutSlots) :- OutSlots=[].

search(State,[Slot|InSlots],OutSlots) :- issol(State,Flag),

expand(Flag,State,[Slot|InSlots],OutSlots).

expand(true,State,[Slot|InSlots],OutSlots) :- solution(State,Slot), OutSlots=InSlots.

expand(false,State,InSlots,OutSlots) :- successors(State,States),

branch(States,InSlots,OutSlots).

branch([],InSlots,OutSlots) :- OutSlots=InSlots.

branch([H|T],InSlots,OutSlots) :- search(H,InSlots,MidSlots),

branch(T,MidSlots,OutSlots).

Here the second input to search is a list of unbound channels or slots. Prolog-like sequential clause will occur because no expansion of the right branch of the search tree will take place until search of the left branch has completed and failed to fill the slots available by binding them. An OR-parallel effect can be gained if this condition is relaxed and search of the right-branch is allowed when it is not known whether a solution will be found in the left branch. Adding the following clause:

search(State,InSlots,OutSlots) :- unknown(InSlots)

| issol(State,Flag), expand(Flag,State,InSlots,OutSlots).

will achieve this, since it means that a node in the search tree will be expanded when the binding status of the slots passed to it is unknown.