Disjunctive Delimited Control
aa r X i v : . [ c s . P L ] S e p Disjunctive Delimited Control
Alexander Vandenbroucke and Tom Schrijvers KU Leuven [email protected]
Abstract.
Delimited control is a powerful mechanism for programminglanguage extension which has been recently proposed for Prolog (andimplemented in SWI-Prolog). By manipulating the control flow of a pro-gram from inside the language, it enables the implementation of powerfulfeatures, such as tabling, without modifying the internals of the Prologengine. However, its current formulation is inadequate: it does not cap-ture Prolog’s unique non-deterministic nature which allows multiple waysto satisfy a goal.This paper fully embraces Prolog’s non-determinism with a novel inter-face for disjunctive delimited control, which gives the programmer notonly control over the sequential (conjunctive) control flow, but also overthe non-deterministic control flow. We provide a meta-interpreter thatconservatively extends Prolog with delimited control and show that itenables a range of typical Prolog features and extensions, now at thelibrary level: findall, cut, branch-and-bound optimisation, probabilisticprogramming, . . .
Keywords: delimited control, disjunctions, Prolog, meta-interpreter, branch-and-bound
Delimited control is a powerful programming language mechanism for controlflow manipulation that was developed in the late ’80s in the context of functionalprogramming [5,2]. Schrijvers et al. [10] have recently ported this mechanism toProlog.This port enables powerful applications in Prolog, such as high-level imple-mentations of both tabling [3] and algebraic effects & handlers [7]. Yet, at thesame time, their work leaves much untapped potential, as it fails to recognise theunique nature of Prolog when compared to functional and imperative languagesthat have previously adopted delimited control.Indeed, computations in other languages have only one continuation , i.e., oneway to proceed from the current point to a result. In contrast, at any point in aProlog continuation, there may be multiple ways to proceed and obtain a result.More specifically, we can distinguish 1) the success or conjunctive continuationwhich proceeds with the current state of the continuation; and 2) the failure or disjunctive continuation which bundles the alternative ways to proceed, e.g., ifthe conjunctive continuation fails.he original delimited control only accounts for one continuation, whichSchrijvers et al. have unified with Prolog’s conjunctive continuation. More specif-ically, for a given subcomputation, they allow to wrest the current conjunctivecontinuation from its track, and to resume it at leisure, however many times asdesired. Yet, this entirely ignores the disjunctive continuation, which remains asand where it is.In this work, we adapt delimited control to embrace the whole of Prolog andcapture both the conjunctive and the disjunctive continuations. This makes itpossible to manipulate Prolog’s built-in search for custom search strategies andenables clean implementations of, e.g., findall/3 and branch-and-bound. Thisnew version of delimited control has an executable specification in the form ofa meta-interpreter (Section 3), that can run both the above examples, amongstothers.
In earlier work, Schrijvers et al. [10] have introduced a Prolog-compatible inter-face for delimited control that consists of two predicates: reset/3 and shift/1 .To paraphrase their original description, reset(Goal,ShiftTerm,Cont) exe-cutes
Goal , and, 1. if
Goal fails, reset/3 also fails; 2. if
Goal succeeds, then reset/3 also succeeds and unifies
Cont and
ShiftTerm with ; 3. if Goal calls shift(Term) , then the execution of
Goal is suspended and reset/3 succeedsimmediately, unifying
ShiftTerm with
Term and
Cont with the remainder of
Goal . The shift/reset pair resembles the more familiar catch/throw predi-cates, with the following differences: shift/1 does not copy its argument (i.e.,it does not refresh the variables), it does not delete choice points, and also com-municates the remainder of
Goal to reset/3 . Obliviousness to Disjunctions
This form of delimited control only captures theconjunctive continuation. For instance reset((shift(a),G1),Term,Cont) cap-tures in
Cont goal G1 that appears in conjunction to shift(a) . In a low-leveloperational sense this corresponds to delimited control in other (imperative andfunctional) languages where the only possible continuation to capture is thecomputation that comes sequentially after the shift. Thus this approach is veryuseful for enabling conventional applications of delimited control in Prolog.In functional and imperative languages delimited control can also be char-acterised at a more conceptual level as capturing the entire remainder of acomputation. Indeed, in those languages the sequential continuation coincideswith the entire remainder of a computation. Yet, the existing Prolog approachfails to capture the entire remainder of a goal, as it only captures the con-junctive continuation and ignores any disjunctions. This can be illustrated bythe reset((shift(a),G1;G2),Term,Cont) which only captures the conjunctivecontinuation G1 in Cont and not the disjunctive continuationG2. In other words,only the conjunctive part of the goal’s remainder is captured.his is a pity because disjunctions are a key feature of Prolog and manyadvanced manipulations of Prolog’s control flow involve manipulating those dis-junctions in one way or another.
This paper presents an approach to delimited control for Prolog that is in linewith the conceptual view that the whole remainder of a goal should be captured,including in particular the disjunctive continuation.For this purpose we modify the reset/3 interface, where depending on
Goal , reset(Pattern,Goal,Result) has three possible outcomes:1. If Goal fails, then the reset succeeds and unifies
Result with failure . Forinstance, ?- reset(_,fail,Result).Result = failure.
2. If
Goal succeeds, then
Result is unified with success(PatternCopy,DisjCont) and the reset succeeds. Here
DisjCont is a goal that representsthe disjunctive remainder of
Goal . For instance, ?- reset(X,(X = a; X = b),Result).X = a, Result = success(Y,Y = b).
Observe that, similar to findall/3 , the logical variables in
DisjCont havebeen renamed apart to avoid interference between the branches of the com-putation. To be able to identify any variables of interest after renaming, weprovide
PatternCopy as a likewise renamed-apart copy of
Pattern .3. If
Goal calls shift(Term) , then the reset succeeds and
Result is unifiedwith shift(Term,ConjCont,PatternCopy,DisjCont) . This contains in ad-dition to the disjunctive continuation also the conjunctive continuation. Thelatter is not renamed apart and can share variables with
Pattern and
Term .For instance, ?- reset(X,(shift(t),X = a; X = b),Result).Result = shift(t,X = a, Y, Y = b).
Note that reset(P,G,R) always succeeds if R is unbound and never leaves choi-cepoints. Encoding findall/3
Section 4 presents a few larger applications, but our en-coding of findall/3 with disjunctive delimited control already gives some ideaof the expressive power: findall(Pattern,Goal,List) :-reset(Pattern,Goal,Result),findall_result(Result,Pattern,List).indall_result(failure,_,[]).findall_result(success(PatternCopy,DisjCont),Pattern,List) :-List = [Pattern|Tail],findall(PatternCopy,DisjCont,Tail).
This encoding is structured around a reset/3 call of the given
Goal followed bya case analysis of the result. Here we assume that shift/1 is not called in
Goal ,which is a reasonable assumption for plain findall/3 . Encoding !/0
Our encoding of the !/0 operator illustrates the use of shift/1 : cut :- shift(cut).scope(Goal) :-copy_term(Goal,Copy),reset(Copy,Copy,Result),scope_result(Result,Goal,Copy).scope_result(failure,_,_) :-fail.scope_result(success(DisjCopy,DisjGoal),Goal,Copy) :-Goal = Copy.scope_result(success(DisjCopy,DisjGoal),Goal,Copy) :-DisjCopy = Goal,scope(DisjGoal).scope_result(shift(cut,ConjGoal,DisjCopy,DisjGoal),Goal,Copy) :-Copy = Goal,scope(ConjGoal). The encoding provides cut/0 as a substitute for !/0 . Where the scope of regularcut is determined lexically, we use scope/1 here to define it dynamically. Forinstance, we encode p(X,Y) :- q(X), !, r(Y).p(4,2). as p(X,Y) :- scope(p_aux(X,Y)).p_aux(X,Y) :- q(X), cut, r(Y).p_aux(4,2). The logic of cut is captured in the definition of scope/1 ; all the cut/0 predicatedoes is request the execution of a cut with shift/1 .In scope/1 , the
Goal is copied to avoid instantiation by any of the branches.The copied goal is executed inside a reset/3 with the copied goal itself as thepattern. The scope result/3 predicate handles the result: – failure propagates with fail ; – success creates a disjunction to either unify the initial goal with the nowinstantiated copy to propagate bindings, or to invoke the disjunctive contin-uation; shift(cut) discards the disjunctive continuation and proceeds with the con-junctive continuation only. We provide an accessible definition of disjunctive delimited control in the formof a meta-interpreter. Broadly speaking, it consists of two parts: the core inter-preter, and a top level predicate to initialise the core and interpret the results.
Figure 1 defines the interpreter’s core predicate, eval(Conj, PatIn, Disj,PatOut, Result) . It captures the behaviour of reset(Pattern,Goal,Result) where the goal is given in the form of a list of goals,
Conj , together with thealternative branches,
Disj . The latter is renamed apart from
Conj to avoidconflicting instantiations.The pattern that identifies the variables of interest (similar to findall/3 )is present in three forms. Firstly,
PatIn is an input argument that shares thevariables of interest with
Conj (but not with
Disj ). Secondly,
PatOut outputs theinstantiated pattern when the goal succeeds or suspends on a shift/1 . Thirdly,the alternative branches
Disj are of the form alt(BranchPatIn,BranchGoal) with their own copy of the pattern.When the conjunction is empty (1–4), the output pattern is unified withthe input pattern, and success/2 is populated with the information from thealternative branches.When the first conjunct is true/0 (5–6), it is dropped and the meta-interpreterproceeds with the remainder of the conjunction. When it is a composite conjunc-tion (G1,G2) (7–8), the individual components are added separately to the listof conjunctions.When the first conjunct is fail/0 (9–10), the meta-interpreter backtracksexplicitly by means of auxiliary predicate backtrack/3 . backtrack(Disj,PatOut,Result) :-( empty_alt(Disj) ->Result = failure; Disj = alt(BranchPatIn,BranchGoal) ->empty_alt(EmptyDisj),eval([BranchGoal],BranchPatIn,EmptyDisj,PatOut,Result)).empty_alt(alt(_,fail)). If there is no alternative branch, it sets the
Result to failure . Otherwise, it re-sumes with the alternative branch. Note that by managing its own backtracking, eval/5 is entirely deterministic with respect to the meta-level Prolog system. eval([],PatIn,Disj,PatOut,Result) :- !, PatOut = PatIn, Disj = alt(BranchPatIn,BranchGoal), Result = success(BranchPatIn,BranchGoal). eval([true|Conj],PatIn,Disj,PatOut,Result) :- !, eval(Conj,PatIn,Disj,PatOut,Result). eval([(G1,G2)|Conj],PatIn,Disj,PatOut,Result) :- !, eval([G1,G2|Conj],PatIn,Disj,PatOut,Result). eval([fail|_Conj],_,Disj,PatOut,Result) :- !, backtrack(Disj,PatOut,Result). eval([(G1;G2)|Conj],PatIn,Disj,PatOut,Result) :- !, copy_term(alt(PatIn,conj([G2|Conj])),Branch), disjoin(Branch,Disj,NewDisj), eval([G1|Conj],PatIn,NewDisj,PatOut,Result). eval([conj(Cs)|Conj],PatIn,Disj,PatOut,Result) :- !, append(Cs,Conj,NewConj), eval(NewConj,PatIn,Disj,PatOut,Result). eval([shift(Term)|Conj],PatIn,Disj,PatOut,Result) :- !, PatOut = PatIn, Disj = alt(BranchPatIn,Branch), Result = shift(Term,conj(Conj),BranchPatIn,Branch). eval([reset(RPattern,RGoal,RResult)|Conj],PatIn,Disj,PatOut,Result):- !, copy_term(RPattern-RGoal,RPatIn-RGoalCopy), empty_alt(RDisj), eval([RGoalCopy],RPatIn,RDisj,RPatOut,RResultFresh), eval([RPattern=RPatOut,RResult=RResultFresh|Conj] ,PatIn,Disj,PatOut,Result). eval([Call|Conj],PatIn,Disj,PatOut,Result) :- !, findall(Call-Body,clause(Call,Body), Clauses), ( Clauses = [] -> backtrack(Disj,PatOut,Result) ; disjoin_clauses(Call,Clauses,ClausesDisj), eval([ClausesDisj|Conj],PatIn,Disj,PatOut,Result) ). Fig. 1.
Meta-Interpreter Core hen the first conjunct is a disjunction (G1;G2) (11–14), the meta-interpreteradds (a renamed apart copy of) (G2,Conj) to the alternative branches with disjoin/3 and proceeds with [G1|Conj] . disjoin(alt(_,fail),Disjunction,Disjunction) :- !.disjoin(Disjunction,alt(_,fail),Disjunction) :- !.disjoin(alt(P1,G1),alt(P2,G2),Disjunction) :-Disjunction = alt(P3, (P1 = P3, G1 ; P2 = P3, G2)). Note that we have introduced a custom built-in conj(Conj) that turns a list ofgoals into an actual conjunction. It is handled (15–17) by prepending the goals tothe current list of conjuncts, and never actually builds the explicit conjunction.When the first goal is shift(Term) (18–21), this is handled similarly to anempty conjunction, except that the result is a shift/4 term which contains
Term and the remainder of the conjunction in addition the branch information.When the first goal is a reset(RPattern,RGoal,RResult) (22–27), the meta-interpreter sets up an isolated call to eval/5 for this goal. When the call returns,the meta-interpreter passes on the results and resumes the current conjunction
Conj . Notice that we are careful that this does not result in meta-level failureby meta-interpreting the unification.Finally, when the first goal is a call to a user-defined predicate (28–33), themeta-interpreter collects the bodies of the predicate’s clauses whose head unifieswith the call. If there are none, it backtracks explicitly. Otherwise, it builds anexplicit disjunction with disjoin clauses , which it pushes on the conjunctionstack. disjoin_clauses(_G,[],fail) :- !.disjoin_clauses(G,[GC-Clause],(G=GC,Clause)) :- !.disjoin_clauses(G,[GC-Clause|Clauses], ((G=GC,Clause) ; Disj)) :-disjoin_clauses(G,Clauses,Disj).
An example execution trace of the interpreter can be found in C.
Toplevel
The toplevel(Goal) -predicate initialises the core interpreter with aconjunction containing only the given goal, the pattern and pattern copy set to(distinct) copies of the goal, and an empty disjunction. It interprets the result bynon-deterministically producing all the answers to
Goal and signalling an errorfor any unhandled shift/1 . toplevel(Goal) :-copy_term(Goal,GoalCopy),PatIn = GoalCopy,empty_alt(Disj),eval([GoalCopy],PatIn,Disj,PatOut,Result),( Result = success(BranchPatIn,Branch) ->( Goal = PatOut ; Goal = BranchPatIn, toplevel(Branch)) Result = shift(_,_,_,_) ->write(’toplevel: uncaught shift/1.\n’), fail; Result = failure ->fail). To illustrate the usefulness and practicality of our approach, we present two casestudies that use the new reset/3 and shift/1 . Branch-and-bound is a well-known general optimisation strategy, where the so-lutions in certain areas or branches of the search space are known to be bounded.Such branches can be pruned, when their bound does not improve upon a pre-viously found solution, eliminating large swaths of the search space in a singlestroke.We provide an implementation of branch-and-bound (see Figure 2) that isgeneric, i.e., it is not specialised for any application. In particular it is not specificto nearest neighbour search, the problem on which we demonstrate the branch-and-bound approach here.The framework requires minimal instrumentation: it suffices to begin everyprunable branch with bound(V) , where V is a lower bound on the values in thebranch.
1. If the
Goal succeeds normally (i.e.,
Result is success ), then Data contains anew solution, which is only accepted if it is an improvement over the existing
Value . The handler then tries the next
Branch .2. If the
Goal calls bound(V) , V is compared to the current best Value : – if it is less than the current value, then Cont could produce a solutionthat improves upon the current value, and thus must be explored. Thealternative
Branch is disjoined to
Cont , and
DataCopy is restored to
Data (ensuring that a future reset/3 copies the right variables); – if it is larger than or equal to the current value, then Cont can be safelydiscarded.3. Finally, if the goal fails entirely,
Min is the current minimum
Value . More examples are available at https://people.cs.kuleuven.be/~alexander.vandenbroucke/publications/disjunctive-continuations.tgz . The framework searches for a minimal solution. ound(V) :- shift(V).bb(Value,Data,Goal,Min) :-reset(Data,Goal,Result),bb_result(Result,Value,Data,Min).bb_result(success(BranchCopy,Branch),Value,Data,Min) :-( Data @< Value -> bb(Data,BranchCopy,Branch,Min); bb(Value,BranchCopy,Branch,Min)).bb_result(shift(ShiftTerm,Cont,BranchCopy,Branch),Value,Data,Min) :-( ShiftTerm @< Value ->bb(Value,Data,(Cont ; (BranchCopy = Data,Branch)),Min); bb(Value,BranchCopy,Branch,Min)).bb_result(failure,Value,_Data,Min) :- Value = Min.
Fig. 2.
Branch-and-Bound Effect Handler.
Nearest Neighbour Search
The code in Figure 4 shows how the branch and boundframework efficiently solves the problem of finding the point (in a given set) thatis nearest to a given target point on the Euclidean plane.The run nn/3 predicate takes a point (X,Y) , a Binary Space Partitioning(BSP)-tree that represents the set of points, and returns the point, nearest to (X,Y) . The algorithm implemented by nn/3 recursively descends the BSP-tree.At each node it first tries the partition to which the target point belongs, thenthe point in the node, and finally the other partition. For this final step we cangive an easy lower bound: any point in the other partition must be at least asfar away as the (perpendicular) distance from the given point to the partitionboundary.As an example, we search for the point nearest to (1 , .
1) in the set { (0 . , . , (0 , , ( − . , , ( − . , − . } . Figure 3 shows a BSP-tree containing thesepoints, the solid lines demarcate the partitions. The algorithm visits the points(0 . , .
5) and (0 , ,
0) is greater than thedistance to (0 . , .
5) (1 and about 0.64). The corresponding call to run nn/3 is: ?- BSP = xsplit((0,0),ysplit((-0.5,0),leaf,xsplit((-0.75,-0.5),leaf,leaf)),ysplit((0.5,0.5),leaf,leaf)), A BSP-tree is a tree that recursively partitions a set of points on the Euclidean plane,by picking points and alternately splitting the plane along the x- or y-coordinate ofthose point. Splitting along the x-coordinate produces an xsplit/3 node, along they-coordinate produces a ysplit/3 node. un_nn((1,0.1),BSP,(NX,NY)).NX = NY, NY = 0.5. (1,0.1)(0,0)(0.5,0.5)(-0.5,0.5)(-0.75,-0.5)
Fig. 3.
Nearest-Neighbour Search using a BSP-tree
Probabilistic programming languages (PPLs) are programming languages de-signed for probabilistic modelling. In a probabilistic model, components behavein a variety of ways—just like in a non-deterministic model—but do so with acertain probability.Instead of a single deterministic value, the execution of a probabilistic pro-gram results in a probability distribution of a set of values. This result is pro-duced by probabilistic inference [15,6], for which there are many strategies andalgorithms, the discussion of which is out of scope here. Here, we focus on oneconcrete probabilistic logic programming languages: PRISM [9].A PRISM program consists of Horn clauses, and in fact, looks just like aregular Prolog program. However, we distinguish two special predicates: – values x(Switch,Values,Probabilities) This predicate defines a proba-bilistic switch
Switch , that can assume a value from
Values with the prob-ability that is given at the corresponding position in
Probabilities (thecontents of
Probabilities should sum to one). – msw(Switch,Value) This predicate samples a value
Value from a switch
Switch . For instance, if the program contains a switch declared as values x(coin, [h,t], [0.4,0.6]) , then msw(coin,V) assigns h (for heads) to V with probability 0.4, and t (for tails) with probability 0.6. Remark thateach distinct call to msw leads to a different sample from that switch. Forinstance, in the query msw(coin,X),msw(coin,Y) , the outcome could beeither (h,h) , (t,t) , (h,t) or (t,h) .Consider the following PRISM program, the running example for this section: n((X,Y),BSP,D-(NX,NY)) :-( BSP = xsplit((SX,SY),Left,Right) ->DX is X - SX,branch((X,Y), (SX,SY), Left, Right, DX, D-(NX,NY)); BSP = ysplit((SX,SY),Up,Down) ->DY is Y - SY,branch((X,Y), (SX,SY), Up, Down, DY, D-(NX,NY))).branch((X,Y), (SX,SY), BSP1, BSP2, D, Dist-(NX,NY)) :-( D < 0 -> % Find out which partition contains (X,Y).TargetPart = BSP1, OtherPart = BSP2, BoundaryDistance is -D; TargetPart = BSP2, OtherPart = BSP1, BoundaryDistance is D),( nn((X,Y), TargetPart, Dist-(NX,NY)); Dist is (X - SX) * (X - SX) + (Y - SY) * (Y - SY),(NX,NY) = (SX,SY); bound(BoundaryDistance-nil),nn((X,Y), OtherPart,Dist-(NX,NY))).run_nn((X0,Y0),BSP,(NX,NY)) :-toplevel(bb(10-nil,D-(X,Y),nn((X0,Y0),BSP,D-(X,Y)),_-(NX,NY))). Fig. 4.
2D Nearest Neighbour Search with Branch-and-Bound. values_x(coin1,[h,t],[0.5,0.5]).values_x(coin2,[h,t],[0.4,0.6]).twoheads :- msw(coin1,h),msw(coin2,h).onehead :- msw(coin1,V), (V = t, msw(coin2,h) ; V = h).
This example defines two predicates: twoheads which is true if both coins areheads, and onehead which is true if either coin is heads. However, note the spe-cial structure of onehead : PRISM requires the exclusiveness condition , that is,branches of a disjunction cannot be both satisfied at the same time. The simplergoal msw(coin1,heads) ; msw(coin2, heads) violates this assumption.The code in Figure 5 interprets this program. Line 1 defines msw/2 as a simpleshift. Lines 6–9 install a reset/3 call over the goal, and analyse the result. Theresult is analysed in the remaining lines: A failure never succeeds, and thushas success probability 0.0 (line 9). Conversely, a successful computation has asuccess probability of 1.0 (line 10). Finally, the probability of a switch (lines11-15) is the sum of the probability of the remainder of the program given eachpossible value of the switch multiplied with the probability of that value, andsummed with the probability of the alternative branch.he predicate msw prob , finds the joint probability of all choices. It iteratesdown the list of values, and sums the probability of the continuation for eachone. msw_prob(_,_,[],[],Acc,Acc).msw_prob(V,C,[Value|Values],[Prob|Probs],Acc,ProbOfMsw) :-prob((V = Value,C),ProbOut),msw_prob(V,C,Values,Probs,Prob*ProbOut + Acc,ProbOfMsw).
Now, we can compute the probabilities of the two predicates above: ?- toplevel(prob(twoheads)).twoheads: 0.25?- toplevel(prob(onehead)).onehead: 0.75
In Appendix B.3 we show how to implement the semantics a definite, non-loopingfragment of ProbLog [6], another logic PPL, on top of the code in this section. msw(Key,Value) :- shift(msw(Key,Value)). prob(Goal) :- prob(Goal,ProbOut), write(Goal), write(’: ’), write(ProbOut), write(’\n’). prob(Goal,ProbOut) :- copy_term(Goal,GoalCopy), reset(GoalCopy,GoalCopy,Result), analyze_prob(GoalCopy,Result,ProbOut). analyze_prob(_,failure,0.0). analyze_prob(_,success(_,_),1.0). analyze_prob(_,shift(msw(K,V),C,_,Branch),ProbOut) :- values_x(K,Values,Probabilities), msw_prob(V,C,Values,Probabilities,0.0,ProbOfMsw), prob(Branch,BranchProb), ProbOut is ProbOfMsw + BranchProb.
Fig. 5.
An implementation of probabilistic programming with delimited control.
In this section we establish two important correctness properties of our meta-interpreter. The proofs of these properties are in the corresponding AppendicesA.1 and A.2he first theorem establishes the soundness of the meta-interpreter, i.e., ifa program (not containing shift/1 or reset/3 ) evaluates to success, then anSLD-derivation of the same answer must exist. Theorem 1 (Soundness)
For all lists of goals [ A , . . . , A n ] , terms α, β, γ, ν ,variables P, R conjunctions B , . . . , B m ; C , . . . , C k and substitutions θ , if ? − eval ([ A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, C , . . . , C k ) . and the program contains neither shift/1 nor reset/3 , then SLD-resolution finds the following derivation: ← ( A , . . . , A n , true ); ( α = β, B , . . . , B m ) ... (cid:3) (with solution θ s.t. αθ = ν ) Conversely, we want to argue that the meta-interpreter is complete, i.e., ifSLD-derivation finds a refutation, then meta-interpretation—provided that itterminates—must find the same answer eventually. The theorem is complicatedsomewhat by the fact that the first answer that the meta interpreter arrives atmight not be the desired one due to the order of the clauses in the program. Todeal with this problem, we use the operator ?- p , which is like ?-, but allows adifferent permutation of the program in every step. Theorem 2 (Completeness)
For all goals ← A , . . . , A n , if ← A , . . . , A n ... (cid:3) (with solution θ )then ?- p eval ([ A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = success ( γ, ( C , . . . , C k )) , R = αθ. Together, these two theorems show that our meta-interpreter is a conservativeextension of the conventional Prolog semantics.
Conjunctive Delimited Control
Disjunctive delimited control is the culminationof a line of research on mechanisms to modify Prolog’s control flow and search, Standard SLD-resolution, augmented with disjunctions and conj/1 goals. hich started with the hook-based approach of
Tor [11] and was followed bythe development of conjunctive delimited control for Prolog [10,12].The following listing shows that disjunctive delimited control entirely sub-sumes conjunctive delimited control. The latter behaviour is recovered by dis-joining again the captured disjunctive branch. We believe that
Tor is similarlysuperseded. nd_reset(Goal,Ball,Cont) :-copy_term(Goal,GoalCopy),reset(GoalCopy,GoalCopy,R),( R = failure -> fail; R = success(BranchPattern,Branch) ->( Goal = GoalCopy, Cont = 0; Goal = BranchPattern, nd_reset(Branch,Ball,Cont)); R = shift(X,C,BranchPattern,Branch) ->( Goal = GoalCopy, Ball = X, Cont = C; Goal = BranchPattern, nd_reset(Branch,Ball,Cont))).
Abdallah [1] presents a higher-level interface for (conjunctive) delimited con-trol on top of that of Schrijvers et al. [10]. In particular, it features prompts ,first conceived in a Haskell implementation by Dyvbig et al. [4], which allowshifts to dynamically specify up to what reset to capture the continuation. Webelieve that it is not difficult to add a similar prompt mechanism on top of ourdisjunctive version of delimited control.
Interoperable Engines
Tarau and Majumdar’s Interoperable Engines [14] pro-pose engines as a means for co-operative coroutines in Prolog. An engine is anindependent instance of a Prolog interpreter that provides answers to the maininterpreter on request.The predicate new engine(Pattern,Goal,Interactor) creates a new en-gine with answer pattern
Pattern that will execute
Goal and is identified by
Interactor .The predicate get(Interactor,Answer) has an engine execute its goal untilit produces an answer (either by proving the
Goal , or explicitly with return/1 ).After this predicate returns, more answers can be requested, by calling get/2 again with the same engine identifier. The full interface also allows bi-directionalcommunication between engines, but that is outside the scope of this article.Figure 6 shows that we can implement the get/2 engine interface in termsof delimited control (the full code is available in Appendix B.2). The opposite,implementing disjunctive delimited control with engines, seems impossible asengines do not provide explicit control over the disjunctive continuation. Indeed, get/2 can only follow Prolog’s natural left-to-right control flow and thus we can-not, e.g., run the disjunctive continuation before the conjunctive continuation,which is trivial with disjunctive delimited control. et(Interactor,Answer) :-get_engine(Interactor,Engine), % get engine staterun_engine(Engine,NewEngine,Answer), % run up to the next answerupdate_engine(Interactor,NewEngine). % store the new engine statereturn(X) :- shift(return(X)).run_engine(engine(Pattern,Goal),NewEngine,Answer) :-reset(Pattern,Goal,Result),run_engine_result(Pattern,NewEngine,Answer,Result).run_engine_result(Pattern,NewEngine,Answer,failure) :-NewEngine = engine(Pattern,fail),Answer = no.run_engine_result(Pattern,NewEngine,Answer,success(BPattern,B)) :-NewEngine = engine(BPattern,B),Answer = the(Pattern).run_engine_result(Pattern,NewEngine,Answer,S) :-S = shift(return(X),C,BPattern,B)BPattern = Pattern,NewEngine = engine(Pattern,(C;B)),Answer = the(X).
Fig. 6.
Interoperable Engines in terms of delimited control.
Tabling without non-bactrackable variables
Tabling [13,8] is a well-known tech-nique that eliminates the sensitivity of SLD-resolution to clause and goal or-dering, allowing a larger class of programs to terminate. As a bonus, it mayalso improve the run-time performance (at the expense of increased memoryconsumption).One way to implement tabling—with minimal engineering impact to theProlog engine—is the tabling-as-a-library approach proposed by Desouter etal. [3]. This approach requires (global) mutable variables that are not erased bybacktracking to store their data structures in a persistent manner. With the new reset/3 predicate, this is no longer needed, as (non-backtracking) state can beimplemented in directly with disjunctive delimited control.
We have presented disjunctive delimited control , an extension to delimited controlthat takes Prolog’s non-deterministic nature into account. This is a conservativeextension that enables implementing disjunction-related language features andextensions as a library.In future work, we plan to explore a WAM-level implementation of disjunc-tive delimited control, inspired by the stack freezing functionality of tablingengines, to gain access to the disjunctive continuations efficiently. Similarily, theuse of copy term/2 necessitated by the current API has a detrimental impact onperformance, which might be overcome by a sharing or shallow copying scheme. eferences
1. Abdallah, S.: More declarative tabling in Prolog using multi-prompt delimitedcontrol. CoRR abs/1708.07081 (2017), http://arxiv.org/abs/1708.07081
2. Danvy, O., Filinski, A.: Abstracting control. pp. 151–160. LFP ’90 (1990). https://doi.org/10.1145/91556.91622, http://doi.acm.org/10.1145/91556.91622
3. Desouter, B., van Dooren, M., Schrijvers, T.: Tabling asa library with delimited control. TPLP (4-5), 419–433 (2015). https://doi.org/10.1017/S1471068415000137, https://doi.org/10.1017/S1471068415000137
4. Dyvbig, R.K., Jones, S.P., Sabry, A.: A monadic framework for delimited continu-ations. Tech. Rep. 615, Computer Science Department Indiana University (2005)5. Felleisen, M.: The theory and practice of first-class prompts. pp.180–190. POPL ’88 (1988). https://doi.org/10.1145/73560.73576, http://doi.acm.org/10.1145/73560.73576
6. Fierens, D., den Broeck, G.V., Renkens, J., Shterionov, D.S., Gutmann, B., Thon,I., Janssens, G., Raedt, L.D.: Inference and learning in probabilistic logic programsusing weighted boolean formulas. TPLP (3), 358–401 (2015)7. Saleh, A.H., Schrijvers, T.: Efficient algebraic effect handlers for Prolog.TPLP (5-6), 884–898 (2016). https://doi.org/10.1017/S147106841600034X, https://doi.org/10.1017/S147106841600034X
8. Santos Costa, V., Rocha, R., Damas, L.: The YAP Prolog system. TPLP (1-2),5–34 (2012)9. Sato, T.: Generative modeling by PRISM. In: ICLP. Lecture Notes in ComputerScience, vol. 5649, pp. 24–35. Springer (2009)10. Schrijvers, T., Demoen, B., Desouter, B., Wielemaker,J.: Delimited continuations for Prolog. TPLP (4-5),533–546 (2013). https://doi.org/10.1017/S1471068413000331, https://doi.org/10.1017/S1471068413000331
11. Schrijvers, T., Demoen, B., Triska, M., Desouter, B.: Tor: Modular search withhookable disjunction. Sci. Comput. Program. , 101–120 (2014)12. Schrijvers, T., Wu, N., Desouter, B., Demoen, B.: Heuristics entwinedwith handlers combined: From functional specification to logic program-ming implementation. In: Chitil, O., King, A., Danvy, O. (eds.) Proceed-ings of PPDP 2014, Kent, Canterbury, United Kingdom, September 8-10, 2014. pp. 259–270. ACM (2014). https://doi.org/10.1145/2643135.2643145, http://doi.acm.org/10.1145/2643135.2643145
13. Swift, T., Warren, D.S.: XSB: Extending Prolog with tabled logic programming.TPLP (1-2), 157–187 (Jan 2012)14. Tarau, P., Majumdar, A.K.: Interoperating logic engines. In: Gill, A.,Swift, T. (eds.) PADL 2009, Savannah, GA, USA, January 19-20,2009. Proceedings. Lecture Notes in Computer Science, vol. 5418, pp.137–151. Springer (2009). https://doi.org/10.1007/978-3-540-92995-6 10, https://doi.org/10.1007/978-3-540-92995-6_10
15. Wood, F.D., van de Meent, J., Mansinghka, V.: A new approach to probabilisticprogramming inference. In: AISTATS. JMLR Workshop and Conference Proceed-ings, vol. 33, pp. 1024–1032. JMLR.org (2014) ppendix Table of Contents
A Correctness Proofs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18A.1 Evaluation Is Sound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18A.2 Evaluation Is Complete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24B Additional Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25B.1 Negation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25B.2 Interoperable Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25B.3 ProbLog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26C Example Meta-Interpreter Trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28D Full Meta-Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Correctness Proofs
A.1 Evaluation Is SoundTheorem 1 (Soundness)
For all lists of goals [ A , . . . , A n ] , terms α, β, γ, ν ,variables P, R conjunctions B , . . . , B m ; C , . . . , C k and substitutions θ , if ?- eval ([ A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, C , . . . , C k ) . and the program contains neither shift/1 nor reset/3 , then SLD-resolution to finds the following derivation: ← ( A , . . . , A n , true ); ( α = β, B , . . . , B m ) ... (cid:3) (with solution θ s.t. αθ = ν )Proof. By induction on the Prolog derivation.
Case [] In this case the only possible result is P = α :?- eval ([] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = α, R = success ( β, ( B , . . . , B m )) . = ⇒ ← true ; ( α = β, B , . . . , B m ) ← true (= (cid:3) )(with solution ǫ )where ǫ is the empty substitution. Case [true|
Conj ] In this case the body contains only a direct recursive call,we have:?- eval ([ true , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . ⇐⇒ ?- eval ([ A . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . Definition and Deter-minism of eval/5 = ⇒ ← ( A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) Induction = ⇒ ← ( true , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) SLD-resolution Standard SLD-resolution, augmented with disjunctions and conj/1 goals. ase [(G1,G2)|
Conj ] A straightforward calculation gives the desired result:?- eval ([( G , G ) , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . Definition and Determinism of eval/5 ⇐⇒ ?- eval ([ G , G , A . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . = ⇒ ← ( G , G , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) Induction = ⇒ ← (( G , G ) , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) Associativity
Case [fail|
Conj ] This case immediately calls backtrack/3 :?- eval ([ fail , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . ⇐⇒ ?- backtrack ( alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . Definition and Deter-minism of eval/5 t this point, we can see that alt ( β, ( B , . . . , B m )) cannot be empty, for otherwise R = failure . Execution of the else branch then gives:?- eval ([ B , . . . , B m ] , β, alt ( δ, fail ) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . δ is fresh = ⇒ ← ( B , . . . , B m , true ); ( β = δ, fail )... (cid:3) (with solution θ s.t. βθ = ν ) Induction = ⇒ ← ( B , . . . , B m , true )... (cid:3) (with solution θ s.t. βθ = ν ) ← β = δ, fail is irrefutable = ⇒ ← ( α = β, B , . . . , B m , true )... (cid:3) (with solution θ s.t. αθ = ν ) SLD-resolution = ⇒ ← ( fail , A , . . . , A n , true ); ( α = β, B , . . . , B m , true )... (cid:3) (with solution θ s.t. αθ = ν ) SLD-resolution
Case [(G1;G2),
Conj ] Assume that the result of the copy term/2 call is alt ( δ, conj ([ D , . . . , D n ])),where all the variables are fresh, such that δσ = ασ ⇒ ( D , . . . , D n ) σ = ( G , A , . . . , A n ) σ (1)Then the result of disjoining this with alt ( β, ( B , . . . , B m )) is the new disjunction ξ = alt ( F, (( δ = F, conj ([ D , . . . , D n ])); ( β = F, B , . . . , B m )))ith F fresh. Now we reason as follows:?- eval ([( G ; G ) A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . ⇐⇒ ?- eval ([ G , A , . . . , A n ] , α, ξ, P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . Definition and Determinism of eval/5 = ⇒ ← ( G , A , . . . , A n , true ); ( α = F, (( δ = F, conj ([ D , . . . , D n ])); ( β = F, B , . . . , B m )))... (cid:3) (with solution θ s.t. αθ = ν ) Associativity, Distributivity, resolution of conj/1 , F is local variable = ⇒ ← ( G , A , . . . , A n , true ); ( α = δ, D , . . . , D n ); ( α, β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν )Note that if σ refutes ← α = δ, D , . . . , D n ; then δσ = ασ , hence (1) ( D , . . . , D n ) σ =( G , A , . . . , A n ) σ . Thus, σ also refutes ← G , A , . . . , A n .So, we may write the above as: ← ( G , A , . . . , A n , true ); ( G , A , . . . , A n ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) Distributivity = ⇒ ← (( G ; G ) , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) ase [conj(Cs)| Conj ] ?- eval ([ conj ([ D , . . . , D l ]) , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . Definition and Determinism of eval/5 ⇐⇒ ?- eval ([ D , . . . , D l , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = success ( γ, ( C , . . . , C k )) . Induction = ⇒ ← ( D , . . . , D l , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) Induction = ⇒ ← ( conj ([ D , . . . , D l ]) , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ s.t. αθ = ν ) Case [C|
Conj ] In this case there are two possibilities: if no clauses match withthe head,
Clauses is empty, and the procedure backtracks. In this case thereasoning is identical to the case [fail|
Conj ] .Otherwise, assume we have a list [( H : − Body ) , . . . , ( H : − Body ) l ] of clausesthat match C . After disjoin clauses/3 , this list becomes a disjunction ( C = H , Body ); · · · ; ( C = H l , Body l ).From the recursive call we get:?- eval ([ C | A , · · · , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = ν, R = result ( γ, ( C , . . . , C k )) Definition and Determinism of eval/5 ⇐⇒ ?- eval ([( C = H , Body ); · · · ; ( C = H l , Body l ) | A , · · · , A n ] , α, alt ( β ( B , . . . , B m )) , P, R ) .P = ν, R = result ( γ ( C , . . . , C k )) Induction = ⇒ ← (( C = H , Body ); · · · ; ( C = H l , Body l )) , A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ , s.t. αθ = ν )If ← (( C = H , Body ); · · · ; ( C = H l , Body l )) , A , . . . , A n , true ) has a refu-tation θ , then let θ = θ i θ ′ such that θ i refutes ← C = H i , Body i and θ ′ refutes A , . . . , A n : ← C = H i , Body i ... (cid:3) (with solution θ i ) = ⇒ ← Body i σ ... (cid:3) (with solution θ ′ i ) s.t. Cσ = H i σ = ⇒ ← C ← Body i σ ... (cid:3) (with solution θ i = σθ ′ i )Then θ also refutes ← C, A , . . . , A n , so we can conclude: ← ( C, A , . . . , A n , true ); ( α = β, B , . . . , B m )... (cid:3) (with solution θ , s.t. αθ = ν ) .2 Evaluation Is CompleteTheorem 2 For all goals ← A , . . . , A n , if ← A , . . . , A n ... (cid:3) (with solution θ )then ?- p eval ([ A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = success ( γ, ( C , . . . , C k )) , R = αθ. where ?- p is defined as ?- , but each step is allowed to use a different permutationof the program.Proof. By induction on the length of the derivation.
Base n = 0 : ← A , . . . , A n = ← true = (cid:3) then θ is empty, and?- p eval ([] , α, alt ( β, ( B , . . . , B m )) , P, R ) .P = success ( β, ( B , . . . , B m )) , R = α by definition. Induction
Let ← A , A , . . . , A n ← G , . . . , G f , A , . . . , A n θ (clause H ← G , . . . , G f ; A θ = Hθ )... (cid:3) (with substitution θ = θ θ )By induction we have,?- p eval ([ G , . . . , G f , A , . . . , A n θ ] , αθ , alt ( δ, ( D , . . . , D l )) , P, R ) P = success ( γ, ( C , . . . , C k )) , S = αθ θ . ⇐⇒ ?- p eval ([( A = H, G , . . . , G f ; . . . ) , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) P = success ( γ, ( C , . . . , C k )) , S = αθ θ . = ⇒ ?- p eval ([ A , A , . . . , A n ] , α, alt ( β, ( B , . . . , B m )) , P, R ) P = success ( γ, ( C , . . . , C k )) , S = αθ θ . Additional Examples
B.1 Negation not(G) :-copy_term(G,GC),reset(GC,GC,R),R = failure.
B.2 Interoperable Engines engines(G) :-engines(G,[]).new_engine(Pattern,Goal,Interactor) :-shift(new_engine(Pattern,Goal,Interactor)).get(Interactor,Answer) :-shift(get(Interactor,Answer)).return(X) :-shift(return(X)).engines(G,EngineList) :-copy_term(G,GC),reset(GC,GC,R),engines_result(G,GC,EngineList,R).engines_result(_,_,_,failure) :-fail.engines_result(G,GC,EngineList,success(BC,B)) :-(G = GC ; G = BC, engines(B,EngineList)).engines_result(G,GC,EngineList,S) :-S = shift(new_engine(Pattern,Goal,Interactor),C,BC,B),length(EngineList,Interactor),copy_term(Pattern-Goal,PatternCopy-GoalCopy),NewEngineList = [Interactor-engine(PatternCopy,GoalCopy)|EngineList],G = GC,G = BC,engines((C;B),NewEngineList).engines_result(G,GC,EngineList,S) :-S = shift(get(Interactor,Answer),C,BC,B),member(Interactor-Engine,EngineList),run_engine(Engine,NewEngine,Answer),update(Interactor,NewEngine,EngineList,NewEngineList),G = GC, = BC,engines((C;B),NewEngineList).update(K,NewV,[K-_|T],[K-NewV|T]).update(K,NewV,[OtherK-V|T],[OtherK-V|T2]) :-K \== OtherK,update(K,NewV,T,T2).run_engine(engine(Pattern,Goal),NewEngine,Answer) :-reset(Pattern,Goal,Result),run_engine_result(Pattern,NewEngine,Answer,Result).run_engine_result(Pattern,NewEngine,Answer,failure) :-NewEngine = engine(Pattern,fail),Answer = no.run_engine_result(Pattern,NewEngine,Answer,success(BPattern,B)) :-NewEngine = engine(BPattern,B),Answer = the(Pattern).run_engine_result(Pattern,NewEngine,Answer,shift(return(X),C,BPattern,B)) :-BPattern = Pattern,NewEngine = engine(Pattern,(C;B)),Answer = the(X).
B.3 ProbLog fact(F) :-shift(fact(F,V)),V = t.is_true(F,Pc) :- member(F-t,Pc).is_false(F,Pc) :-member(F-f,Pc).problog(Goal) :- problog(Goal,[]).problog(Goal,Pc) :-reset(Goal,Goal,Result),analyze_problog(Result,Pc).analyze_problog(success(_,_),_Pc).analyze_problog(shift(fact(F,V),C,_,Branch),Pc) :-is_true(F,Pc),V = t,problog((C;Branch),Pc).analyze_problog(shift(fact(F,V),C,_,Branch),Pc) :-is_false(F,Pc),V = f,problog((C;Branch),Pc).nalyze_problog(shift(fact(F,V),C,_,Branch),Pc) :-not(is_true(F,Pc)),not(is_false(F,Pc)),msw(F,V),problog((C;Branch),[F-V|Pc]).analyze_problog(failure,_Pc) :- fail.% NEGATION DOES NOT WORK.
Example usage: % 0.5 :: f1.values_x(f1,[t,f],[0.5,0.5]).f1 :- fact(f1).% 0.5 :: f2.values_x(f2,[t,f],[0.5,0.5]).f2 :- fact(f2).p :- f1.p :- f2?- solutions(prob(problog((f1,f1)))).problog((f1,f1)): 0.5?- solutions(prob(problog(p))).problog(p): 0.75
Example Meta-Interpreter Trace
Consider the simple program: p(1).p(2) :- shift(2).
The following table shows the values of each of the arguments of the meta-interpreter, while evaluating the goal p(X) . PatIn Conj DisjX [p(X)] alt(Y,fail)X [(X=1,true; alt(Y,fail)X = 2,shift(2))]X [(X=1,true)] alt(Z1,(Z1=X1,X1=2,shift(2) ; Z1=Y,fail))X [X=1,true] alt(Z1,(Z1=X1,X1=2,shift(2) ; Z1=Y,fail))1 [true] alt(Z1,(Z1=X1,X1=2,shift(2) ; Z1=Y,fail))1 [] alt(Z1,(Z1=X1,X1=2,shift(2) ; Z1=Y,fail))PatOut=1 , Result=success(Z1,(Z1=X1,X1=2,shift(2) ; Z1=Y,fail))
We can then evaluate the alternative branches:
PatIn Conj DisjZ1 [(Z1=X1,X1=2,shift(2) ; Z1=Y,fail))] alt(A,fail)Z1 [(Z1=X1,X1=2,shift(2))] alt(B,(Z2=B,Z2=Y,fail;A=B,fail)Z1 [Z1=X1,X1=2,shift(2)] alt(B,(Z2=B,Z2=Y,fail;A=B,fail)X1 [X1=2,shift(2)] alt(B,(Z2=B,Z2=Y,fail;A=B,fail)2 [shift(2)) alt(B,(Z2=B,Z2=Y,fail;A=B,fail)2 [] alt(B,(Z2=B,Z2=Y,fail;A=B,fail)Result=shift(2,conj([]),B,(Z2=B,Z2=Y,fail;A=B,fail)) and again [(Z2=B,Z2=Y,fail;A=B,fail)] alt(C,fail) ... ... ...Y [fail] alt(D,B1=D,A=B1,fail ; C=D,fail)backtracking D [(B1=D,A=B1,fail ; C=D,fail)] alt(E,fail) ... ... ...D [fail] alt(E,fail) backtracking fails: Result=failure
Full Meta-Interpreter
The following code has been tested on SWI-Prolog versions 7.6.3 and 8.0.3. Ituses the type check package to type-check the Prolog code. The type-checkingannotations can be commented out, without loss of functionality on systems thatdo not support type-checking.package to type-check the Prolog code. The type-checkingannotations can be commented out, without loss of functionality on systems thatdo not support type-checking.