[net.lang.prolog] PROLOG Digest V3 #22

RESTIVO@SU-SCORE.ARPA (05/07/85)

From: Chuck Restivo (The Moderator) <PROLOG-REQUEST@SU-SCORE.ARPA>


PROLOG Digest            Tuesday, 7 May 1985       Volume 3 : Issue 22

Today's Topics:
                         Implementation - CP,
                        LP Library - Abstracts
----------------------------------------------------------------------

Date: Sun 5 May 85 19:31:35-EDT
From: Vijay <Vijay.Saraswat@CMU-CS-C.ARPA>
Subject: The  !-annotation revisited. #1]

Some time ago in these channels I had defined a read-only
annotation '!'  which was meant to replace Concurrent
Prolog's '?', which I felt had a messy and unjustifiable
semantics.

In the last issue, "THRIFT%ti-csl.csnet@csnet-relay.arpa"
mentions a '+' annotation, but I fail tosee how it is
different from '!'.  To make matters clear, here is a
more thorough discussion of '!'.  (Note that this is a
substantially revised version of my previous submission.)


In the following, "term" is used to refer to
either an atom or a functional term.

First, assume that unification of a compound term T1 against
a compound term T2 is carried out by checking if T1 and T2
have the same functor and arity and then unifying their
corresponding arguments in PARALLEL.

The '!' annotation can only decorate instances of terms in
the head of a clause.  If a Term (variable OR constant OR
compound term) T! occurs in the head of a clause, then
unification T!-V, where V is a variable will suspend and
remain suspended until V has been instantiated (to a
constant or a possibly non-ground compound term), after
which the unification T-V will be attempted.  Like the `?',
the `!' is not inherited by embedded terms, that is, it
applies only to the term instance textually indicated in the
program.

However, if '!' annotates a term t1 inside a term t, then it
must also annotate all sub-terms of t which contain t1
(including t).  In fact, we will define an embedded
occurrence of a '!' to be shorthand for just such a series
of '!' annotations in the term.  (The atom at the head of a
clause is always !-annotated.)

Note: 1. This restriction is necessary to prevent
occurrences of unify(X, f(a!))  which doesn't make sense
because you cannot require a sub-term to be present unless
you also require the superior term to be present.

2. wait/1 in CP seems to achieve the suspension part of '!'
but cannot be used to simulate it in CP-without-? without a
control primtive to sequence goals.

3. A simple lazy sequential O(N^2) algorithm can implement
!-unification correctly.  (See next message in this Digest)

Note:
   1. unify(Y!, X!) can never occur.
   2. unify(Y, Y!) can, and suspends till Y is instantiated.
   3. There is no 'inheritance' of '!'  via X!-Y
      unifications like there is for X?-Y.
   4. The !-annotation can never 'occur' in any goal call at
      run-time.

With '!' each CLAUSE decides what is to be INPUT to it.
With '?' each CALL decided what would be input to that call.
If all the clauses have the same pattern of input
specifications, then the '!' annotations could be removed in
favor of a mode-specification for the predicate. Since
nested '!' annotations are allowed within a term, in general
it is not possible to remove '!'  annotations in favour of
mode declarations. (Of course, every program annotated with
the Dec-10 Prolog '+' (input) or '?' (dont-know) annotations
can be rewritten using '!' annotations; hence '!' is more
'general'.)

Examples: 1. merge/3. Equivalent mode: merge(+,+,?).
merge([A|X]!, Y, [A|Z]):- merge(X, Y, Z).
merge(X, [A|Y]!, [A|Z]):- merge(X, Y, Z).

2. plus/3. (No single equivalent mode declaration.)
plus(X!, Y!, Z):- Z is X+Y.
plus(X!, Y, Z!):- Y is Z-X.
plus(X, Y!, Z!):- X is Z-Y.

'!' is less powerful than '?' because it cannot be used to
simulate 'var' and hence write a b-merge like program, even
assuming strict-AND fairnes of the scheduler.

Hence I would propose using '!' in conjunction with var/1.

For var/1 I would propose some syntactic sugar: another
annotation (^, say).  Again, the ^-annotation can occur only
in the clause-head, and annotates just the occurrence of the
Term it is textually adjacent to.  The unification Term^-X
succeeds only if X is a variable and results in X being
unified with Term. If X is not a variable, then Term^-X
FAILS, though this must be regarded as a control failure.
Term^-X NEVER suspends.

Note that nested occurrences of ^ do not make sense.

Examples:
1.  "foo(foo^)^" is meaningless.
2.  "foo(foo^)" will match any of the following terms:
     X, foo(X).
3. "foo(foo^)!" will match any of the following terms:
    foo(X).

I feel I must also sound this note of warning.  With the
assumption of weak AND-fairness (which, roughly paraphrased
says that every enabled goal will be reduced in a finite
amount of time), var/1 (or ^) is enough to write a program
which exhibits unbounded non-determinism.  That is, the
program always terminates, but for every natural number 'k'
can produces as output y > k.  Here it is:

p(X):- p(X,Stop), stop(Stop).
p(s(X), Stop^):- p(X,Stop).
p(X, stop).
stop(stop).

(Note: this is not a CP program: its a 'pure' (save for ^)
Horn logic program.)  Without going into details suffice to
say that the semantics of constructs which allow unbounded
non-determinism can cause severe headaches because the limit
of arbitrary chains may not exist. My personal feeling is
that '^', or its equivalent, should be allowed in any
language which purports to be a CONCURRENT language; of
course as soon as we introduce '!' or '^' we are not talking
of LOGIC languages any more.

'!' cannot be (because it was not DESIGNED to be) used to
export 'protected' variables.  But the whole thesis here is
that clauses should be forced to specify what their input is
and hence the 'embedded channel' problem in Hellerstein and
Shapiro, ISLP, 1984 can be taken care of by !-protecting at
the consume site instead of ?-protecting at the produce
site.

'!' seems to cover the vast majority of uncontroversial uses
of the `?' and is far simpler semantically.  For example, it
can be shown that once a goal previously blocked on '!'
becomes unblocked, it remains unblocked through commit time.
No proposal for '?' which does things like unify X with Y?
to make X Y? in all its occurrences can do that.

Finally, for a more formal treatment of the !-annotation,
see next message.

-- Vijay Saraswat.

------------------------------

Date: Sun 5 May 85 15:39:36-EDT
From: Vijay <Vijay.Saraswat@CMU-CS-C.ARPA>
Subject: Semantic of the !-annotation. #2

Here is a more formal discussion of the '!' annotation.
Please see previous post for an informal motivation.  It is
taken from my forthcoming tech rep 'Problems with Concurrent
Prolog'.

There are a number of other alternate formal definitions.
This semantics of '!' has been used to give two formal
semantics to Concurrent Prolog programs.  Please see
succeeding messages.

-- Vijay A. Saraswat


The !-annotation.

In this section, we will think of terms as trees, that is,
as partial functions from the set of all possible paths
(i.e. finite and infinite sequences of natural numbers) to a
co-domain C +{\bot}, where C is the set of node-labels.  If
the function is not defined for a given argument, we will
take its value to be \bot.  Then terms are trees over the
co-domain of function symbols and variables.

We can now define ANNOTATIONS to be simply trees over the
domain {tt}+ {\bot}, with the interpretation that the (node
specified by the) path l is annotated by p iff p(l)=tt.
However, we would also like to insist that if a term is
annotated then all its super-terms are also annotated.
Therefore, we require that if p(l)=tt, then so is p(l'), for
all prefixes l' of l. [Ancestor condition]

An annotation p is APPLICABLE to a term t iff for all l,
t(l) is defined when p(l) is.

We would now like to give the semantics of an annotated
term.  The annotations serve to restrict the set of terms
with which the annotated term can unify.  Consider a term s
annotated by p and a term t.  First we would like to express
the notion of a most general unifier for s and t which
ignores all the annotated nodes in s and the corresponding
nodes in t, if they exist.

The p-RESTRICTED MGU of two terms s and t, denoted by
mgu_r(p,s,t), where p is an annotation applicable to s, is
the most general substitution q such that p(l)# tt =>
(q(s))(l) = (q(t))(l).

According to the intuitive meaning of !, we must ensure that
all annotated terms unify against non-variable terms.  This
means that when unifying a term s against t, if there is a
path l such that p(l)=tt and s(l)=s' and t(l)=t', we cannot
proceed until and unless t' is instantiated.  But if t' is
instantiated, we can remove the annotation on s'.  So we can
find mgu-!(p,s,t) by unifying the terms in s which are not
!-protected by p against the corresponding sub-terms of t,
i.e. by computing mgu_r(p,s,t).  If this leads to
instantiating a variable in t which has a corresponding
sub-term in s that is !-protected, then we can remove this
annotation and start again. If mgu(s,t) exists, then the
process terminates in success iff there are no more
!-annotations left, and then mgu-!(p,s,t)= mgu(s,t) and in
failure iff all nodes !-annotated by the current p in t are
variables.  Hence:

An !-MGU of two terms s, t where p is an annotation
applicable to s, is a substitution q_n such that there
exists a sequence p=p_0, ... p_n=\bot such that for all l.
p_{i+1}(l)=tt <-> p_i=tt & (q_i(s))(l) is a variable, where
q_i=mgu_r(p_i, s, t)

(p_n=\bot means that there are no nodes in p_n which have
the value tt.)

Note that mgu_r(\bot,s,t)=mgu(s,t).

------------------------------

Date: Sun 5 May 85 15:40:22-EDT
From: Vijay <Vijay.Saraswat@CMU-CS-C.ARPA>
Subject: A new commit: & #3

Now that we seem to have recovered from the discussions on
the meaning of '?'  in Concurrent Prolog, let me initiate a
discussion on the role of '|'.

In the following, by CP I mean CP with '!' and without '?'.
The reason is that there doesn't exist (as yet) a consistent
definition of '?' and I consider '!'  to be a perfectly
useful and simple substitute for '?'.

The semantics of CP can be given by specifying which
SLD-derivations are ADMISSIBLE, given a query and a
CP-program.  I contend that the reason CP is not a logic
programming language is because it does not distinguish
failed admissible SLD-derivations from admissible
SLD-refutations. Because of its don't care non-determinism,
committing can only be locally angelic, i.e it chooses
values for its free variables such that its guard executes
successfully, but its body may still fail for the chosen
values.  This means:

1. Validity of unannotated axioms is not sufficient for
partial correctness:    a given query wil assuredly succeed
(or loop) ONLY if all finite admissible   SLD-derivations
are refutations, which is a very strong condition.

2. There can be no notion of negation-as-failure
even with respect to    admissible derivations. (i.e. at
best we can hope that negation means that    no admissible
derivation for the query is a refutation... even that is not
   compatible with '|'.)

3. Many Horn logic axiom definitions cannot be used in CP.
As an illustration, no version of the axioms:

     p(X,Y):- t(X,Y).
     p(X,Y):- t(X,Z), p(Z,Y).

annotated with CP's annotations can be guaranteed to work
correctly (i.e. compute the transitive closure of p/2) for
an arbitrary (Horn) definition of p/2. (For example,
consider the program:

         p(0,1).  p(0,2).  p(1,3).

     I.  t(X!, Y!):- p(X,Y)| true.
     II. t(X!, Y!):- p(X,Z), t(Z,Y)|true.

   The query '?-t(0,3).' may fail because in II, p(X,Z) may
   commit to:
                   p(0,2). )

There are two simple alternate interpretations for 'commit'
which distinguish between successful and unsuccesful
admissible derivations.

1. CP-amb or the '&' annotation: don't-know operator.  (This
corresponds to McCarthy's famous "amb" operator in the
context of HLP.)  Interpretes "commit" as "make global".  It
does not snip off other OR-siblings, but instead continues
to follow them, allowing MULTIPLE commits of OR-siblings.
Each commit is to a different copy of the rest of the
environment.  In effect whereas 'a:-g | b.' extends some
admissible refutation of 'g' by an admissible refutation for
'b' to return ONE (selected from possibly many) refutation
for 'a' (all this talk is modulo multiple environments),
'a:-g & b.'  extends EVERY admissible refutation of 'g' by
an admissible refutation for 'b' and returns ALL of them as
refutations for 'a', thereby avoiding a local commitment to
one refutation of 'a'.  Therefore, an SLD-derivation ends in
failure only if all admissible derivations are finite and
failing, just as for Prolog.

A completely formal description of & is given in my
forthcoming tech rep 'An operational semantics for
Concurrent Prolog'.

2.CP-backtrack or the '\' annotation.  Interpretes "commit"
as "make global and freeze other OR-brothers". Here ONE
admissible SLD-derivation is followed until it terminates.
If it terminates in success, nothing is done. Failure
induces backtracking. For partial correctness, the exact
backtracking scheme used is not important (chronological,
dependency-directed), though of course pragmatically it is
quite important, as long as it can be guaranteed that the
system will not terminate in failure as long as even one
admissible SLD-derivation path has not been pursued.  That
is, no FINITE SLD-derivation is admissible unless it is a
refutation or else all admissible derivations are finite and
failing.

Lemma: Given a CP program and a query, the set of possible
answers to a query is the same if the '|' is interpreted as
'commit', '&' or '\' uniformly in the program.

That is, | and & are equivalent as far as the success
semantics is concerned.

The relationship between interpreting '|' as commit, '&' and
'\' uniformly in a program is as follows:

[CP]
     1.i Execution of a query ALWAYS terminates IFF all
         admissible SLD-derivations are finite.
     1.ii Execution may terminate in failure even if there
         is an admissible refutation.

[CP-backtrack]
     2.i As in 1.i.
     2.ii Execution terminates in failure ONLY IF no
        admissible SLD-derivation is a refutation. If a
        query terminates in failure  under CP-backtrack, it
        will always terminate in failure under CP.

[CP-amb]
     3.i Execution of a query always terminates IFF either
        there is an admissible SLD-refutation or all
        admissible SLD-derivations are finite.
     3.ii As in 2.ii.

Caveat:

As in Prolog, allowing implicit search allows the user to
write very inefficient programs. On the other hand, for
whatever it is worth, much of Guy Steele's thesis work on
constraints can be recast in such a logic framework.

I would like to retain '|' much for the same reason that the
'!' is used in Prolog: sometimes it helps to signal
determinate situations and avoid redundant solutions.  When
mixing '&' and '|', executing a '|' does not affect any
previous '&' that may have been executed: it just kills off
any remaining OR-siblings.

I leave you with the following example: (& represents the
"amb-commit").

  prod(X!,Y!,Z):- Z is X*Y | true.
  prod(X!, Y, Z!):- X =/= 0 | Y is Z/X.
  prod(X, Y!, Z!):- Y =/= 0 | X is Z/Y.
  prod(X, Y, Z!):- less(X,Z), less(Y, Z), Z := X*Y | true.
  Z := X!*Y! :- Z is X*Y.

This can "solve" prod(X,X,16) to give X=4 provided that
less/2 is assumed defined as if by the collection of clauses
less(i#,j#):- true & true.  for each value of i# and j# such
that i# < j#, so that it serves as a generator.

-- Vijay Saraswat

------------------------------

Date: Sun 5 May 85 15:41:48-EDT
From: Vijay <Vijay.Saraswat@CMU-CS-C.ARPA>
Subject: A meta-interpreter for CP[!,|,&]   #4.

Here is a simple meta-interpreter for CP[!,|,&] in
CP[!,|,&].  Note that it is not possible to give as simple
an interpreter for CP[!,|] in CP[!,|].  This interpreter can
be extended to give an interpreter for CP[!,^,|,&] in
CP[!,^,|,&].

We define a predicate cp/1 which takes as input a goal and
solves it.  The user-program is added in as clauses of the
form:

       clause(Head:-Guard | Body):- true & true.
or
       clause(Head:- Guard & Body):- true & true.
where:

1. All instances of '!' in Head are replaced by '$', which will
   be regarded as a unary post-fix function symbol,

2. Guard and Body are sequences of goals of the form '{g_1, ...,
   g_n}'.

In addition we also have the following axioms (which can be
added automatically):

For every functor f/n, (n>= 0) in the user-program, the
axioms:

     f(X1,...Xn)! = f(Y1,...Yn)$:- X1 = Y1,...Y1=Yn | true.
     f(X1,...Xn)$ = f(Y1,...Yn)!:- X1 = Y1,...Y1=Yn | true.

Then the interpreter is the following:

X=X:- X =\= G$.

unify(X!, Y):- X =Y | true.

execute(G!):-   clause((Goal :- Guard | Body)),
                unify(Goal, G),
                execute_all(Guard) | execute_all(Body).
execute(G!):-   clause((Goal :- Guard & Body)),
                unify(Goal, G),
                execute_all(Guard) & execute_all(Body).

execute_all({One|Rest}!):- true | execute(One), execute_all(Rest).
execute_all( true!):- true | true.

There are two cheats involved in this simple interpreter:

1. The term {g_1, ... g_n} (analogous to Dec-10 Prolog's [g_1,
... g_n]) is actually shorthand for the term g_1@(g_2@...
g_n)))..), where the @/2 operator is ASSOCIATIVE and
COMMUTATIVE. Hence {g_1,... g_n} represents a SET (actually
multi-set) of goals, rather than a list of goals, and when
unified against {X_1,... X_m|X} succeeds if 1 <=m <=n, and
unifies SOME m goals (not necessarily the FIRST m) from g_1
... g_n with X_1 ... X_m and the set of the rest of them
with X.  We use sets instead of lists in order to avoid
giving priority to goals that might textually occur first in
the body of a clause; the same effect could be achieved by
using some random selection function. So the first cheat is
assuming that CP has a notion of associative-commutative
unification built in.

2. Second, we cheat in using the binary predicate =/=, which
has a weak form of var/1 built into it. Of course, given
=/=, we can simulate =/=, so we still have a complete
meta-interpreter.  I do not know how to avoid using some
such device and still get an elegant program. Possibly by
representing user-program terms in a special fashion, one
may define a var/1 for user-program terms, but it is sure to
be messy.

The reason one cannot write as simple an interpreter for
CP[!,|] is that in CP[!,|] one cannot add the user-program
as a list of clauses to the interpreter as we have done
because then a call to clause(Head:-Guard|Body) would
succeed at most once, selecting some clause/1 clause at
random whereas we would like to select all clauses in
parallel and execute their guards concurrently,i.e. we DON'T
KNOW which clause we want.  Hence we are forced to represent
the program explicitly as an argument to the interpreter, as
a list of clauses and that makes any meta-interpreter very
messy.  But of course using the & in the body of clause/1
clauses achieves just the desired effect.

-- Vijay A. Saraswat

------------------------------

Date: Sun 5 May 85 15:42:29-EDT
From: Vijay <Vijay.Saraswat@CMU-CS-C.ARPA>
Subject: An operational semantics for CP. #5

Here is the abstract from a forthcoming tech rep "An
operational semantics for Concurrent Prolog".

-- Vijay A. Saraswat

Abstract. In this paper we present a simple
operational semantics for CP[!,|,&] using the notion of
Plotkin-style labelled transition system, which we believe
offers a simple, general framework for issues of concurrency
in Horn logic programming.  The space of configurations we
work with naturally reflects the AND-OR process structure of
an executing concurrent logic program.  The problems solved
in giving this semantics were the representation and
manipulation of a distributed environment and the effect of
two different kinds of commits: the don't care commit
originally proposed in \ref{Cp} and the don't-know commit
proposed in \ref{problems-cp}, together with their
interactions.

From the transition system, we extract various
`meaning-functions' for a program.  We show that with
respect to one of these (the `success-semantics') a
CP[!,&] is the same as the corresponding CP[!, |]
program.

Essentially the transition system is a formalisation of a
derivability predicate in pure Horn logic, i.e. the
semantics can be looked upon as the specification in pure
Horn logic of a high-level interpreter for CP which
represents precisely the sequence of computations possible
for a given query and a given CP program.

------------------------------

Date: Sun 5 May 85 15:46:13-EDT
From: Vijay <Vijay.Saraswat@CMU-CS-C.ARPA>
Subject: A scenario semantics for CP.   #6

Here is the abstract from my forthcoming CMU Tech report: "A
scenario semantics for Concurrent Prolog".

-- Vijay A. Saraswat

Abstract: In this paper we present a simple bottom-up
semantics for CP which captures the set of all possible
answer-substitutions for a query, given a CP[!,|,&] program.
The basic idea is to define a transformation analogous to
T_p:I rightarrow I (which defines the semantics of a pure
Horn logic program as lfp(T_p)). Because of CP's
annotations, however, we cannot interprete processes as
predicates over the underlying Herbrand base; we need more
operational information.  We define the notion of {it
scenarios}, adapted from the semantics given in cite{brock}
for non-deterministic data-flow languages, and give the
success-semantics of a CP[!,|,&] program as lfp(S_p) of a
transformation S_p:I_s rightarow I_s from sets of scenarios
to sets of scenarios.

------------------------------

End of PROLOG Digest
********************