[comp.lang.prolog] What does/should your Prolog make of this ?

jand@maestro.htsa.aha.nl (Jan Derriks) (03/06/90)

In article <1990Mar1.210219.5687@Neon.Stanford.EDU> feldman@Neon.Stanford.EDU (Todd J. Feldman) writes:
>
>Can someone please help me with a bug in the following Prolog fragment:
>
Maybe this problem is related to Feldman's:

In the book 'AI Through Prolog', Neil Rowe uses something like this:

(1)   doo :- agenda(X), assertz(agenda(next)), write(X), fail.
  
When this rule starts there is ONE fact 'agenda(start)' in the database,
so the first query agenda(X) succeeds with X=start. 
Now the problem with all of our Prolog interpreters (Prolog-2 from 
Expert Systems International Ltd, Xprolog, SBprolog) is that they
are so 'smart' as to see that with only ONE agenda() fact in the
database, there is no need to keep a backtrack-point there, so that
fact is interpreted as 

agenda(start) :- !.

And following assertz'ed agenda's are NOT found by backtracking, so
the output is just 'start no', while what was intended is:
'startnextnextnextnextnextnext....' (database full) as a result.

When you start with TWO agenda facts it works as intended (try
calling 'doo' a second time)

My question: Is there ANY prolog interpreter that does what
             I (and apparantly N. Rowe too) expect ?

BTW, the workaround is of course to always have two 'dummy' facts in
     the database for cases like this. (or is there a better way ?)
-- 
Jan Derriks                  |       AHA-TMF (H.T.S. 'Amsterdam'),
jand@maestro.htsa.aha.nl     |       Europaboulevard 23,
(or ..hp4nl!htsa!jand)       |       1079 PC Amsterdam,
phone: +31 20423827          |       the Netherlands.

roland@sics.se (Roland Karlsson) (03/06/90)

> (1)   doo :- agenda(X), assertz(agenda(next)), write(X), fail.

> Now the problem with all of our Prolog interpreters (Prolog-2 from 
> Expert Systems International Ltd, Xprolog, SBprolog) is that they
> are so 'smart' as to see that with only ONE agenda() fact in the
> database, there is no need to keep a backtrack-point there, so that
> fact is interpreted as 

> agenda(start) :- !.

> And following assertz'ed agenda's are NOT found by backtracking, so
> the output is just 'start no', while what was intended is:
> 'startnextnextnextnextnextnext....' (database full) as a result.


In SICStus Prolog there is a sound implementation of assert after call.
In the example above above you will be able to backtrack to all
clauses (instances) of agenda/1 known at the time when calling agenda(X).
That is, no instances asserted after calling agenda(X) will be known.
It dosn't matter if there is 1 or more instances when first calling
agenda(X). The output will always be 'start - no' only.


--
Roland Karlsson
SICS, PO Box 1263, S-164 28 KISTA, SWEDEN	Internet: roland@sics.se
Tel: +46 8 752 15 40	Ttx: 812 61 54 SICS S	Fax: +46 8 751 72 30

ok@goanna.oz.au (Richard O'keefe) (03/07/90)

In article <1557@maestro.htsa.aha.nl>, jand@maestro.htsa.aha.nl (Jan Derriks) writes:
> In the book 'AI Through Prolog', Neil Rowe uses something like this:

> (1)   doo :- agenda(X), assertz(agenda(next)), write(X), fail.

> My question: Is there ANY Prolog interpreter that does what
>              I (and apparently N. Rowe too) expect ?
> BTW, the workaround is of course to always have two 'dummy' facts in
>      the database for cases like this. (or is there a better way ?)

No, that workaround does *NOT* work in general.  It won't work in
Quintus or SICStus Prolog, for example.  The question is,

	if a predicate is changed by assert, retract, abolish, &c
	while there is a call active for that predicate,
	what should happen?
	retract/1 and clause/2 count as calls to the predicate.

In DEC-10 Prolog and C Prolog, the answer was the "immediate update" model,
where *every* change to a dynamic predicate must immediately be visible to
all active calls to that predicate.  This makes sense, and Rowe's example
does work under that model.

The trouble with that model is that getting it _right_ means that you
must ALWAYS leave a choice point behind when you call a dynamic predicate
even when you get to the last clause, because another clause *might* be
added while it is running.  DEC-10 Prolog and C Prolog do in fact leave
such choice-points behind.

A lot of Prolog implementors haven't bothered to do anything specific
in this case, thinking that it is unlikely.  Some Prologs will crash if
you retract a clause and then an active call tries to backtrack into it.

Sometime in 1983 or 1984 I proposed what I called the "transaction",
or "logical" model.  In that model, calling a predicate behaves as if
it made a private copy of the relevant clauses which were present at
the instant of the call, and changes to the data base don't affect that
private copy.  Now, that model was invented to solve some other problems,
but Quintus adopted it because it made sense of indexed dynamic predicates:
at long last compiled and interpreted predicates needed cuts in precisely
the same places.  Rowe's hack won't work at all under this model, no
matter how many clauses there are in the agenda to start with:  it will
exhaust the initial clauses and then stop.

A point to appreciate is that because of the Prolog systems which don't
bother to implement any particular model, portable code never could use
this backtrack-into-the-agenda code anyway.  Here's a portable way of
doing it:

	:- dynamic
		agenda/1.

	/*  start(Task) tries to accomplish Task.  It succeeds if
	    process_item(Item) ever succeeds.  It fails if
	    next_agenda_item(_) runs out of agenda items instead.
	*/
	start(Task) :-
		initialise_agenda(Task),
		next_agenda_item(Item),
		process_item(Item),
		!.

	initialise_agenda(Task) :-
		retractall(agenda(_)),
		assert(agenda(Task)).

	agenda_is_empty :-
		\+ Item^agenda(Item).


	/*  next_agenda_item/1 is a hybrid between retract and repeat;
	    think of it as a version of retract/1 where the search
	    for another matching clause starts again from the beginning
	    of the predicate instead of continuing from where the last
	    search left off (as retract does).
	*/
	next_agenda_item(Item) :-
		retract(agenda(I)),
		!,
		(   Item = I
		;   next_agenda_item(Item)
		).

	/* process_item(Item) works on the current agenda item.
	   It may add new items to the front of the agenda with
	   asserta/1 or to the back of the agenda with assertz/1.
	   It may even remove items from the agenda.
	   It should succeed when the over-all task has been
	   accomplished; otherwise it should fail.

	   If you would like to use a different approach where
	   the loop is to stop *successfully* when the agenda
	   runs out, you can just do
		process_item(X) :- ......, agenda_is_empty.
	*/

This approach generalises nicely to a multi-level agenda.  Suppose we
want an agenda with three priority levels.  Then we use

	:- dynamic
		agenda/2.

	initialise_agenda(Task) :-
		retractall(agenda(_,_)),
		assert(agenda(1,Task)).

	agenda_is_empty :-
		\+ Level^Item^agenda(Level, Item).

	next_agenda_item(Item) :-
		(   retract(agenda(1,I))
		;   retract(agenda(2,I))
		;   retract(agenda(3,I))
		),
		!,
		(   Item = I
		;   next_agenda_item(Item)
		).

It should be clear how to generalise this to N levels.

alf@sics.se (Thomas Sj|land) (03/07/90)

Roland Karlsson wrote:
> In SICStus Prolog there is a sound implementation of assert after call.
			       ^^^^^	

Please, do not misuse the terminology like this. This problem has nothing
to do with "soundness" in the logical sense. Use "healthy" or "nice"
or "good" or whatever you like.

----

Another issue that has come up and which could interest this group 
relates to  bagof(setof)-semantics : 

What should be the behaviour of the following queries (in the program 'p(X)') ?

?- freeze(X,write(foo)), bagof(_,(X=1 ; X=2),L).

?- Y=1,
   bagof((X,Y),p(X),L).

?- bagof((X,Y),p(X),L), 
   Y=1.

The behaviour in SICStus seems unintuitive to many. One would perhaps 
want to have a less "operational" view on bagof/setof

Is there a need for more versions of findall/bagof/setof ?
Are there efficiently implementable alternatives ?

	/Thomas Sjvland, 
		(look, I can write my name on a SUN with ISO standard 8859 !)

PS.	
	This is what I get easily into gnu-emacs using C-Q M-<key> !!
	1234567890-=\`	!@#$%^&*()_+|~
	qwertyuyuiop[]	QWERTYUIOP{}
	asdghjklkl;'	ASDFGHJKL:"
	zxcvbnm,./	ZXCVBNM<<>?

	? Doesn't it look nice ?

Ought we not to think about bigger character sets for Prolog 
including Europe and Japan and also FULL (complete, :-)) English 
in the syntax ?
DS.

--
Thomas Sjoeland
SICS, PO Box 1263, S-164 28 KISTA, SWEDEN
Tel: +46 8 752 15 42	Ttx: 812 61 54 SICS S	Fax: +46 8 751 72 30
Internet: alf@sics.se or {mcvax,munnari,ukc,unido}!enea!sics.se!alf

cdsm@sappho.doc.ic.ac.uk (Chris Moss) (03/08/90)

In article <1557@maestro.htsa.aha.nl> jand@maestro.htsa.aha.nl (Jan Derriks) writes:
>In the book 'AI Through Prolog', Neil Rowe uses something like this:
>
>(1)   doo :- agenda(X), assertz(agenda(next)), write(X), fail.
>  
>When this rule starts there is ONE fact 'agenda(start)' in the database,
>so the first query agenda(X) succeeds with X=start. 
...
>And following assertz'ed agenda's are NOT found by backtracking, so

This is a standard problem with the semantics of assert and retract in Prolog.
The problem is of course that when you enter the clause "agenda" the Prolog
system wants to know if it has to set up a backtracking point.  Any sensible
system won't, a naive interpreter will anyway, but will set the address of the
next clause in the backtracking frame. Since this is nil at this point, the
effect is the same. It will only be non-nil if it bothers to do an extra
dereference at backtracking time.

Almost the same thing is happening here:

>From: feldman@Neon.Stanford.EDU (Todd J. Feldman)
>
>Can someone please help me with a bug in the following Prolog fragment:
>
>process_entries:-
>	wait(X),
>	abolish(wait,1),
>	goal1(.....),
>	goal2(.....),
>	goal3(.....),
>	goal4(.....).
>
>The intention I had in writing this code was for backtracking to occur 
>between goals 1 and 4.  Finally, goal1 will no longer be able to succeed.

But this is one of the easiest ways to crash a fragile Prolog system.

Why? The reference to the next "wait" clause has already been stored, when the
abolish goes ahead and deletes the clause. Now one of several things can
happen. If the "orphaned" next clause happens still to be lying around in
store, it will still be executed. If however its space has been reclaimed and
in the meanwhile your process has put something else there which is NOT a
clausehead, then the system will probably crash.

The Edinburgh compiler and its offsprings guarded against this by not
recovering space until the end of the program. 

The better way is make the semantics of assert and retract more logical: that
is to copy the "readers and writers" approach to file consistency. Once you've
started reading (executing) a predicate, it will stay static and any changes
will not be visible. But any subsequent reads (executions) will see the new
copy.

Not all Prologs do this yet. But Quintus started and the Prolog standard has
also incorporated this notion, so hopefully more are following. Does anyone
know of others? Mail to me and I'll post.

In both cases the problem can be solved by backtracking PAST the goal and then
reentering, though some recoding will be needed (e.g. asserta not assertz). 

I'm not convinced that either program should use assert or retract however.

Chris Moss.

ok@goanna.oz.au (Richard O'keefe) (03/08/90)

In article <1990Mar7.104021.28421@sics.se>, alf@sics.se (Thomas Sj|land) writes:
> Another issue that has come up and which could interest this group 
> relates to  bagof(setof)-semantics : 

> What should be the behaviour of the following queries
> (in the program 'p(X)') ?

I don't understand what "in the program 'p(X)'" means.

> ?- freeze(X,write(foo)), bagof(_,(X=1 ; X=2),L).

Ignoring the freeze, the call to bagof/3 would create a list L=[U,U] in
some versions of Prolog, L=[U,V] in others.  When the elements of the
result are not ground, setof/3 and bagof/3 have no non-operational meaning.
In NU Prolog version 1.4, the result is

    2?- prog(L).
    foofoofooL = [_CYIXR] ;
    fooL = [_CYIXU] ;
    fail.

which seems reasonable to me.

> ?- Y=1,
>    bagof((X,Y),p(X),L).

This should be the same as bagof((X,1), p(X), L), and should presumably
act just as it does in non-coroutining Prologs.

> ?- bagof((X,Y),p(X),L), 
>    Y=1.

I repeat that setof/3 and bagof/3 have no non-operational meaning
when the elements of the result would not be ground.  It seems to
me entirely reasonable for a coroutining implementation of these
predicates to compute the set
    V' = {variables in the Template} \ {variables in the Generator}
and do something like

	setof(T, G, L) => setof(T, (G, await_all_ground(V')), L)
	bagof(T, G, L) => bagof(T, (G, await_all_ground(V')), L)

the wait has to be inside the generator in case the generator
binds some constrained variables and the awakened constraints bind
some of the variables in V'.

> Is there a need for more versions of findall/bagof/setof?

Yes.  NU Prolog has some of them.  For reasons entirely unrelated to
coroutining, there are still other versions that would be useful.

> PS.	
> Ought we not to think about bigger character sets for Prolog 
> including Europe and Japan and also FULL (complete, :-)) English 
> in the syntax ?

The document PS/6 was written in mid-1984 and explicitly recommends
that Prolog should accept Kanji, each glpyh being represented by a
single integer code.  Xerox Quintus Prolog does this.  I believe
that the approach taken by Xerox Quintus Prolog is the right one.
Quintus Prolog handles the full 8859/1 character set, and if Debray
didn't make too many changes to the tokeniser I gave him, so does
SB Prolog.

It is quite straightforward to define Prolog token-level syntax in
terms of sets of characters, leaving character set size and coding
implementation defined.  The main difficulty (which my current
specification _just_ manages to cope with) is that some character
sets include several replicates of the same characters (for example
IBM's DBCS apparently includes two copies of each non-control EBCDIC
character) and one might want all the replicates of the digits to
function equivalently, without confusing them elsewhere.  (Xerox
are right: subscript/superscript/width and so on are part of the
"looks" of an INSTANCE of a character and should not be part of
the code of the IDENTITY of the character.  Alas, IBM (RT AIX) and
others don't agree.

jand@maestro.htsa.aha.nl (Jan Derriks) (03/08/90)

In article <1681@gould.doc.ic.ac.uk> cdsm@doc.ic.ac.uk (Chris Moss) writes:
  >In article <1557@maestro.htsa.aha.nl> jand@maestro.htsa.aha.nl (Jan Derriks) 
  >>(1)   doo :- agenda(X), assertz(agenda(next)), write(X), fail.
  >
  >This is a standard problem with the semantics of assert and retract in Prolog
...
[using the 'static|transaction|logical' model]
  >Not all Prologs do this yet. But Quintus started and the Prolog standard has
  >also incorporated this notion, so hopefully more are following. Does anyone
  >know of others? Mail to me and I'll post.
As Roland Karlsson writes: SICStus prolog does.
  >I'm not convinced that either program should use assert or retract however.
  >
Can you tell that to rowe@cs.nps.navy.mil,
who wrote 'AI through Prolog' ? His book is full of this kind of stuff !.
(He used micro-prolog.)
-- 
Jan Derriks                  |       AHA-TMF (H.T.S. 'Amsterdam'),
jand@maestro.htsa.aha.nl     |       Europaboulevard 23,
(or ..hp4nl!htsa!jand)       |       1079 PC Amsterdam,
phone: +31 20423827          |       the Netherlands.