[comp.lang.prolog] co-routining

ted@nmsu.edu (Ted Dunning) (12/07/90)

i have two questions about prologs which contain a freeze/2 predicate
such as sicstus prolog.

1) how can one delay a goal until _either_ of two variables are
instantiated?

something like this could be done by the following,

freeze(X,Y,G) :- freeze(X,G), freeze(Y,G).

but this has the nasty consequence that G is called twice.

note that 

freeze(X,freeze(Y,G))

does _not_ work since Y may instantiated before X, and i want G to be
called in that case, too.


2) how can freeze/2 be used to do co-routining?  does this require a
modification of what is normally taken to be co-routining?

chik@icot.or.jp (Chikayama Takashi) (12/07/90)

In article <TED.90Dec6145517@aiaia.nmsu.edu> ted@nmsu.edu (Ted Dunning) writes:
 |1) how can one delay a goal until _either_ of two variables are
 |instantiated?

A standard technique is:

  freeze(X, Y, G) :- freeze(X, V=go), freeze(Y, V=go), freeze(V, G).

 |2) how can freeze/2 be used to do co-routining?  does this require a
 |modification of what is normally taken to be co-routining?

The same technique as in concurrent logic languages like Concurrent
Prolog, Parlog or Guarded Horn Clauses is available.  See, for
example:

    Ehud Shapiro and Akikazu Takeuchi
    "Object Oriented Programming in Concucrrent Prolog"
    in Ehud Shapiro ed. "Concurrent Prolog Collected Papers", Vol. 2
    The MIT Press
    ISBN 0-262-19267-5

Takashi Chikayama

hawley@icot32.icot.or.jp (David John Hawley) (12/07/90)

In article <TED.90Dec6145517@aiaia.nmsu.edu> ted@nmsu.edu (Ted Dunning) writes:
>
>
>i have two questions about prologs which contain a freeze/2 predicate
>such as sicstus prolog.
>
>1) how can one delay a goal until _either_ of two variables are
>instantiated?

How about:

?- freeze(X,foo(Flag,G)), freeze(Y,foo(Flag,G)).

foo(Flag,G) :- var(Flag), !, Flag=done, G.
foo(_,_).

By continuations you can do this to an arbitrarily complex delay condition.
I think.

>2) how can freeze/2 be used to do co-routining?  does this require a
>modification of what is normally taken to be co-routining?

Don't know. I use a concurrent logic language :-). Speaking of which,
our local cll (KL1) runs 3 times slower than our local Prolog (ESP) on the
same hardware. If anyone can answer the above question (2), maybe (s)he can
comment on the performance hit of doing heavy freeze-based coroutining
in Prolog.

David Hawley

lhe@yang (Lars-Henrik Eriksson) (12/07/90)

In article <TED.90Dec6145517@aiaia.nmsu.edu>, ted@nmsu (Ted Dunning) writes:
>
>
>i have two questions about prologs which contain a freeze/2 predicate
>such as sicstus prolog.
>
>1) how can one delay a goal until _either_ of two variables are
>instantiated?
>
>something like this could be done by the following,
>
>freeze(X,Y,G) :- freeze(X,G), freeze(Y,G).
>
>but this has the nasty consequence that G is called twice.

Try

freeze(X, Y, G) :- freeze(X, Flag = 1), freeze(Y, Flag = 1), freeze(Flag, G).

>
>2) how can freeze/2 be used to do co-routining?  does this require a
>modification of what is normally taken to be co-routining?

To implement coroutining in a producer-consumer context, use the
following general scheme.

start :- consume(X), produce(X).

produce([]) :- ......./* Stop condition */.
produce([X|Y]) :- ......./* Construct X*/, produce(Y).

consume(X) :- freeze(X, consume1(X)).

consume1([]).
consume1([X|Y]) :- ......./* Use X */, consume(Y).


In Sicstus prolog you can instead of the consume/consume1-pair write

:- wait consume/1.

consume([]).
consume([X|Y]) :- ......./* Use X */, consume(Y).

which is cleaner.
-- 
Lars-Henrik Eriksson				Internet: lhe@sics.se
Swedish Institute of Computer Science		Phone (intn'l): +46 8 752 15 09
Box 1263					Telefon (nat'l): 08 - 752 15 09
S-164 28  KISTA, SWEDEN

mark@adler.philosophie.uni-stuttgart.de (Mark Johnson) (12/07/90)

/* Here's a utility predicate freeze/3 for prologs like SICSTUS and BNR
 * which have freeze/2.  It uses a ``shared variable'' which is bound
 * when one of the frozen goals is run.  It's easy to generalize this
 * to arrange for Goal to be called once whenever *any* variable appearing
 * in a term (or list) is bound.
 *
 * But what I don't know how to do is the following: define a predicate
 * delay(Term, Goal) which calls Goal when Term is further instantiated,
 * possibly by unifying distinct variables occuring in Term.
 * Any ideas?
 *
 * Mark Johnson
 */

freeze(X, Y, Goal) :-               % Delay Goal until either X or Y is bound
	freeze(X, freeze_helper(SharedVar, Goal)),
	freeze(Y, freeze_helper(SharedVar, Goal)).

freeze_helper(SharedVar, _) :-      % If SharedVar is bound
	nonvar(SharedVar),          %  then the frozen goal
	!.                          %  has already been run.
freeze_helper(done, Goal) :-        % Bind SharedVar
	call(Goal).                 %  and call Goal.


test :-
	freeze(X, Y, write('Frozen goal activated')),
	X = a,
	Y = b.

chik@icot.or.jp (Chikayama Takashi) (12/17/90)

In article <MARK.90Dec7130758@adler.philosophie.uni-stuttgart.de> mark@adler.philosophie.uni-stuttgart.de (Mark Johnson) writes:

 * But what I don't know how to do is the following: define a predicate
 * delay(Term, Goal) which calls Goal when Term is further instantiated,
 * possibly by unifying distinct variables occuring in Term.
 * Any ideas?
 *
 * Mark Johnson

What's wrong with "freeze"ing a goal which, in addition to calling the
main "Goal", will traverse the data structure the variable is
instantiated to, hooking up the same goal to all the variables in the
structure on its way?  For example:

    delay(Term, Goal) :-
	freeze(Term, (Goal, traverse(Term, Goal))).

    traverse(X, Goal) :- var(X), !, freeze(X, Goal).
    traverse(X, Goal) :- functor(X, _, N), traverse_args(N, X, Goal).

    traverse_args(0, _, _) :- !.
    traverse_args(N, X, Goal) :- 
	arg(N, X, Xn),
	traverse(Xn, Goal),
	N1 is N-1,
	traverse_args(N1, X, Goal).

which will execute as:

    | ?- delay(X, write(X)), X = f(Y,Z), Y = [a], Z = b.
    f(_92,_108)f([a],_343)f([a],b)

This calls "write" three times and I guess that's exactly what you
want.  Or do you want something completely different?

Takashi Chikayama
ICOT

hawley@icot32.icot.or.jp (David John Hawley) (12/18/90)

In article <CHIK.90Dec17194110@icot21.icot.or.jp> chikayama@icot.or.jp writes:
>In article <MARK.90Dec7130758@adler.philosophie.uni-stuttgart.de> mark@adler.philosophie.uni-stuttgart.de (Mark Johnson) writes:
>
> * But what I don't know how to do is the following: define a predicate
> * delay(Term, Goal) which calls Goal when Term is further instantiated,
> * possibly by unifying distinct variables occuring in Term.
> * Any ideas?
>What's wrong with "freeze"ing a goal which, in addition to calling the
>main "Goal", will traverse the data structure the variable is
>instantiated to, hooking up the same goal to all the variables in the
>structure on its way?  For example:

(context: SICStus Prolog) The problem with this is when you "unify
distinct variables occuring in Term" to each other. The only predicate
I know of that can capture such events is dif/2 (a sound implementation
of \==), but I can't see how to use it to do what the first author wants.
On the other hand freeze/2 waits for "instantiations", i.e. it doesn't
trap Var=Var.

David Hawley

<munch>
<munch>
<munch>

lee@munnari.oz.au (Lee Naish) (12/19/90)

In article <8203@icot32.icot.or.jp> hawley@icot32.icot.or.jp (David John Hawley) writes:
>In article <CHIK.90Dec17194110@icot21.icot.or.jp> chikayama@icot.or.jp writes:
>>In article <MARK.90Dec7130758@adler.philosophie.uni-stuttgart.de> mark@adler.philosophie.uni-stuttgart.de (Mark Johnson) writes:
>>
>> * But what I don't know how to do is the following: define a predicate
>> * delay(Term, Goal) which calls Goal when Term is further instantiated,
>> * possibly by unifying distinct variables occuring in Term.
>> * Any ideas?
>>What's wrong with "freeze"ing a goal...
>
>(context: SICStus Prolog) The problem with this is when you "unify
>distinct variables occuring in Term" to each other. The only predicate
>I know of that can capture such events is dif/2 (a sound implementation
>of \==), but I can't see how to use it to do what the first author wants.

I once hacked up something in MU-Prolog which waited until a term was
further instantiated.  It was used for a tracing utility which printed
the value of variables in a goal whenever they were further
instantiated.  Here is an example of a coroutining perm/2 running
backwards:

?- vtrace(perm(X, [1, 2])).
X = [X_153|Y_153]
X = [1|Z_153]
X = [1, X_234|Y_234]
X = [1, 2|Z_234]
X = [1, 2]

X = [1, 2]  ;
FAIL
FAIL
FAIL
FAIL
X = [X_203, B_206|L_206]
X = [X_203, 1|Y_203]
X = [2, 1|Z_203]
X = [2, 1]

X = [2, 1]  ;
FAIL
FAIL
FAIL
X = [2, B_206|L_206]
FAIL
FAIL
FAIL
 
fail.

?-

The MU-Prolog had a limit on the number of distinct variables it could
handle.  In NU-Prolog you can implement delay/2 by using =/2 inside if.
This is treated in a similar way to inequality, delaying until
sufficiently instantiated (not necessarily ground):

        % Call Goal when Term becomes futher instantiated
        % (even two vars being unified).  Calls Goal
	% immediately if Term is ground (should be an error?).
	%
        % BUG: might not wake if var is bound to a term of
        % the form 'Really different!'(Integer).
        % This can be fixed quite simply using another
        % goal which wakes if any var is bound to a nonvar
        % but I doubt that its worth bothering.
delay(Term, Goal) :-
        listOfVars(Term, Vars),
        sort(Vars, UniqueVars),	% remove duplicates
        list_of_diff(UniqueVars, 42, DiffEls),
        (if UniqueVars = DiffEls then
		% unlikely, unless UniqueVars = []
		call(Goal)
        else
                call(Goal)
        ).

        % Returns list of the same length as first arg.
        % Each element differs from all others; and
        % hopefully all other terms in the program.
        % Second arg is an arbitrary integer seed.
list_of_diff([], _, []).
list_of_diff(_.Us, N, 'Really different!'(N).Ns) :-
        N1 is N + 1,
        list_of_diff(Us, N1, Ns).

Its probably possible to write a much neater solution in Sepia, since
the basic delaying primitive in Sepia allows (in)equality checking.

NU-Prolog if= also allows quantifiers, making it more powerful in some
ways.  For example, the NU-Prolog interpreter uses it to delay until one
term is subsumed by another term or the two terms do not unify.

	lee

micha@ecrc.de (Micha Meier) (12/19/90)

In article <8203@icot32.icot.or.jp> hawley@icot32.icot.or.jp (David John Hawley) writes:
>In article <CHIK.90Dec17194110@icot21.icot.or.jp> chikayama@icot.or.jp writes:
>>What's wrong with "freeze"ing a goal which, in addition to calling the
>>main "Goal", will traverse the data structure the variable is
>>instantiated to, hooking up the same goal to all the variables in the
>>structure on its way?  For example:
>
>(context: SICStus Prolog) The problem with this is when you "unify
>distinct variables occuring in Term" to each other. The only predicate
>I know of that can capture such events is dif/2 (a sound implementation
>of \==), but I can't see how to use it to do what the first author wants.
>On the other hand freeze/2 waits for "instantiations", i.e. it doesn't
>trap Var=Var.

The problem is not only with binding a variable to another one, but also
to delay a goal whose structure is not fixed, i.e. the variables
to delay on have to be extracted from it at run-time. In my opinion,
freeze/2 is not well suited for this. Although it can be used for that,
the resulting program does not look neither declarative nor readable.

In SEPIA it is possible to specify whether a goal is woken only by the
instantiation of a variable or even by its binding, possibly to another
variable. The delay condition is expressed using delay clauses in the form

	delay Pred if Condition.

Condition has the form of a normal clause body, and it can contain
the predicates var/1, nonground/1, \==/2 and user-defined external
predicates. When var/1 and nonground/1 are used, the goal is woken
only by instantiating the appropriate variable because the system
knows that it would re-delay with a mere variable binding. When \==/2
is used, on the other hand, the goal is woken even by a variable binding.

The choice of the predicates allowed in the body of a delay clause
seems deliberate, but in fact it is not. Although we initially intended
to allow any Prolog predicates in the body of a delay clause, we changed
the design because this approach would imply serious semantic problems,
namely mixing meta- and object-level computation. Since we wanted
the delay clauses to be efficient, we decided to restrict the allowed
conditions to these three, and it turned out that for most of the
'normal' applications, this is more than enough. For a more
advanced usage, e.g. to implement constraints, it is much more
difficult to isolate what conditions will be used, and that's why
we allow the users to write their own conditions in C and add them
as external predicates to Sepia.

The user can in fact specify a very complex delay conditions, for example
specify that the goal should delay when first called, unless it contains
less than N variables, but whenever any of its variables is bound,
it will be woken and it will continue, even if it has less than N variables.

To anyone who is interested: the binary of SEPIA is available to
universities for a nominal fee of ~$200 (for any number of machines)
together with a graphical system for X and sunview and now also with
the high-level debugging tool OPIUM.

--Micha
--
E-MAIL  micha@ecrc.de            	MAIL	Micha Meier
						ECRC, Arabellastr. 17
                     				8000 Munich 81
						Germany