Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

An ambiguous case of setup_call_cleanup/3

91 views
Skip to first unread message

Ulrich Neumerkel

unread,
Nov 28, 2009, 5:30:51 PM11/28/09
to

2009-01-14, I posted the first request for input to ISO's WG17
standardization of a cleanup mechanism. In the meantime we have a
draft and three systems supporting setup_call_cleanup/3: SWI, YAP, B.
The current draft is post-N215:

http://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup.html

Thanks to their availability it is now possible to compare the systems
and reconsider the specification in the light of actual
implementations. In fact, we went through this iteration already
several times - also in Pasadena. This is the latest case:


While comparing the behavior for setup_call_cleanup/3 for different
systems I came today to the following case which might be seen as
ambiguous.

If your systems supports call_cleanup/2 but not setup_call_cleanup/3
take

setup_call_cleanup(S, G, C) :- once(S), call_cleanup(G, C).

For this example it is sufficient.

?- setup_call_cleanup(true, (X=1;X=2), write(semel)), setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.

We have here two goals protected independently with cleanup semel and
bis. The cleanups cannot be executed prior to ! since both have a
further solution awaiting (X = 2 and Y = 2).

One moment of calling the cleanup prior to failure is (7.8.11.1 c 1):
"after the last solution..."

Now when executing ! both cleanups should be executed due to (7.8.11.1
c 3).

The precise order is defined (7.8.11.1 d) "Several cleanup goals
triggered simultaneously are executed in reversed order of
installation."

That means, the output shall be 'semelbis'. And the query succeeds,
unifying X = 1, Y = 1.

An output 'bissemel' would be not conformant.

Currently I have slight doubts: Is this an overspecification?
Couldn't both be accepted?

1mo Arguments in favor of the current specification:

+ Cleanup bis and the associated goal might take certain resources for
granted that were allocated by semel.

+ By demanding to execute first cleanup bis and then semel, a system
might recover memory earlier.

+ One could even simply the specification to "Cleanup goals are
executed in reversed order of installation".

2do Arguments that this is an overspecification

- In contrast to an exception (which might be a resource error due to
a memory overflow), there is no pressing need for space efficiency so
implementations should not be restricted unnecessarily.

- Systems might have some "selective" cut as an implementation
specific extension that cuts one choice point "in the middle" away. I
don't know if such a beast exists - just in case.


For other current activities of WG17 please refer to:

http://www.sju.edu/~jhodgson/wg17/Drafts/

We need everyone who cares about Prolog!

Jan Wielemaker

unread,
Nov 29, 2009, 2:46:47 PM11/29/09
to
On 2009-11-28, Ulrich Neumerkel <ulr...@mips.complang.tuwien.ac.at> wrote:
<snip>

> ?- setup_call_cleanup(true, (X=1;X=2), write(semel)), setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.

<snip>

> The precise order is defined (7.8.11.1 d) "Several cleanup goals
> triggered simultaneously are executed in reversed order of
> installation."
>
> That means, the output shall be 'semelbis'. And the query succeeds,
> unifying X = 1, Y = 1.

? semel is installed prior to bis, so the reverse is bissemel ...
What am I missing?

Specifying this order (= bissemel) makes some sense. On the other
hand, there may be low-level reasons in some systems to reverse and
it might be a bit of an overspecification to force them to comply.

I guess most systems cancel choice-points in reverse order, but systems are
likely to use different mechanisms to insert the execution of the cleanup goal
into their scheduled work. E.g. SWI-Prolog does this using a call-back to the
Prolog engine from the routine that does the choice-point pruning. Others may
use a queue or stack of goals to be inserted at the first opportunity, etc.

Cheers --- Jan

Ulrich Neumerkel

unread,
Nov 30, 2009, 12:48:02 PM11/30/09
to

Your viewpoint is from the side of the implementor.

But there is the other direction as well.

Is the guarantee that all cleanups will be executed in reversed order
useful for a programmer or not?

Chip Eastham

unread,
Nov 30, 2009, 1:26:11 PM11/30/09
to
On Nov 30, 12:48Ā pm, ulr...@mips.complang.tuwien.ac.at (Ulrich
Neumerkel) wrote:

Hi, Ulrich:

Speaking as a programmer, I'd noticed the same
thing Jan did... that the spec calls for the
cleanup actions to be performed in the reverse
order of installation (and opposite to how you
first sketched the "ambiguity"), and I wondered
(as Jan seems to have) if I were missing the
point.

From a broader programming language perspective
this is the order (inside-out) that I'd expect
cleanup code to be called. When "destructing"
objects (class instances), the "cleanup" code
is generally inside-out with respect to the
inheritance hierarchy, at least for non-virtual
classes. That's an analogy, possibly a poor
one, but as I excuse myself, from a programming
point of view you want to expect something, and
this makes for a nice simple rule to remember
(always wise to keep things simple for coders!).

regards, chip

Ulrich Neumerkel

unread,
Nov 30, 2009, 1:39:54 PM11/30/09
to
Chip Eastham <hard...@gmail.com> writes:

>On Nov 30, 12:48=A0pm, ulr...@mips.complang.tuwien.ac.at (Ulrich Neumerkel) wrote:
>> Jan Wielemaker <j...@hppc323.few.vu.nl> writes:
>> >On 2009-11-28, Ulrich Neumerkel <ulr...@mips.complang.tuwien.ac.at> wrote:
>> ><snip>
>>
>> >> ?- setup_call_cleanup(true, (X=3D1;X=3D2), write(semel)), setup_call_cleanup(true,(Y=3D1;Y=3D2),write(bis)), !.

>>
>> ><snip>
>>
>> >> The precise order is defined (7.8.11.1 d) "Several cleanup goals
>> >> triggered simultaneously are executed in reversed order of
>> >> installation."
>>
>> >> That means, the output shall be 'semelbis'. And the query succeeds,
>> >> unifying X =3D 1, Y =3D 1.
>>
>> >? =A0semel is installed prior to bis, so the reverse is bissemel ...
>> >What am I missing?
>>
>> >Specifying this order (=3D bissemel) makes some sense. =A0On the other

>> >hand, there may be low-level reasons in some systems to reverse and
>> >it might be a bit of an overspecification to force them to comply.
>>
>> >I guess most systems cancel choice-points in reverse order, but systems are
>> >likely to use different mechanisms to insert the execution of the cleanup goal
>> >into their scheduled work. =A0E.g. SWI-Prolog does this using a call-back to the
>> >Prolog engine from the routine that does the choice-point pruning. =A0Others may

>> >use a queue or stack of goals to be inserted at the first opportunity, etc.
>>
>> Your viewpoint is from the side of the implementor.
>>
>> But there is the other direction as well.
>>
>> Is the guarantee that all cleanups will be executed in reversed order
>> useful for a programmer or not?
>
>Hi, Ulrich:
>
>Speaking as a programmer, I'd noticed the same
>thing Jan did... that the spec calls for the
>cleanup actions to be performed in the reverse
>order of installation (and opposite to how you
>first sketched the "ambiguity"), and I wondered
>(as Jan seems to have) if I were missing the
>point.

You both are right! That's what the current specification says. But
then, *I* have written it - it could be demanding too much or it could
be even inconsistent. It is by comparing different implementations (4
in the meantime...) that my doubts were raised. From the
specification's point of view any reduction of its size can only be
welcomed. For implementors the situation is not that clear. But any
reduction of special cases is a win - ceteris paribus indeed. It is
the programmer's perpective, that is still not clear.

>From a broader programming language perspective
>this is the order (inside-out) that I'd expect
>cleanup code to be called. When "destructing"
>objects (class instances), the "cleanup" code
>is generally inside-out with respect to the
>inheritance hierarchy, at least for non-virtual
>classes. That's an analogy, possibly a poor
>one, but as I excuse myself, from a programming
>point of view you want to expect something, and
>this makes for a nice simple rule to remember
>(always wise to keep things simple for coders!).

I am not sure that this analogy holds. We have different forms of
nestings of cleanups that correspond to two related notions of
termination whose accidental confusion is quite a source of sorrow.

Were do you have this with objects?

With a little bit of phantasy you could consider the termination
properties of an iterator as an analogon: So existential terminaion
corresponds somehow (not exactly) to the termination of the next
message. In fact termination of next is a stronger property. And
universal termination is given if such a for-loop terminates always.

I would rather distinguish here languages without yield - let's ignore
them - and those with yield. Those with yield might give us some
insight - I cannot fathom currently.

The other source of insight I tapped was Common LISP and Scheme. They
faced these issues a little bit longer. unwind-protect is the
corresponding construct. It turned out that the CL standard has some
ambiguity that is not explicitly flagged. So a reading alone will not
reveal the ambiguity. I want to avoid that. Of course that means
that the specification could have some more implementation
dependent/undefined parts - which would make it longer and more
unwieldy - but then leaving well known ambiguities unflagged would be
a service.

It's all about specifying something which is at its heart
non-functional.

Ulrich Neumerkel

unread,
Nov 30, 2009, 2:59:38 PM11/30/09
to
ulr...@mips.complang.tuwien.ac.at (Ulrich Neumerkel) writes:
>unwieldy - but then leaving well known ambiguities unflagged would be
>a service.

would *NOT* be a service.

Chip Eastham

unread,
Nov 30, 2009, 6:36:42 PM11/30/09
to
On Nov 30, 1:39Ā pm, ulr...@mips.complang.tuwien.ac.at (Ulrich
Neumerkel) wrote:

Okay, hearing from a specifier then, do you see
an ambiguity in the current standard? Of course
the standard could be other than it is, but the
most I get from your later phrasing is perhaps
in "concurrent" Prolog there may be ambiguity
about the order of installation & thus cleanup.

But concurrent Prolog introduces all sorts of
ambiguities of this kind, it seems to me. Can
threads access the same dynamic predicates? If
so, do asserts and retracts affect pending goals
(this would be bad) across threads?

As a programmer I like to maintain a very serial
outlook on how I expect Prolog code to work.

regards, chip

Ulrich Neumerkel

unread,
Nov 30, 2009, 6:58:06 PM11/30/09
to
Chip Eastham <hard...@gmail.com> writes:
>On Nov 30, 1:39=A0pm, ulr...@mips.complang.tuwien.ac.at (Ulrich

>Neumerkel) wrote:
>> It's all about specifying something which is at its heart
>> non-functional.
>
>Okay, hearing from a specifier then, do you see
>an ambiguity in the current standard? Of course
>the standard could be other than it is, but the
>most I get from your later phrasing is perhaps
>in "concurrent" Prolog there may be ambiguity
>about the order of installation & thus cleanup.

What do you mean by "current standard"? 13211-1, and 13211-2? And
what do you mean by ambiguity?

Everything that is implementation dependent or undefined in 13211-1
and 2 is such an ambiguity. To a lesser degree parts that are
implementation defined. That's inevitable, BTW. Think of variable
ordering which is implementation dependent. Lots of people criticised
that it is so, but there is not a single way to specify it without
hindering implementors to do the implementation they want to do. I do
not know how often this was discussed through literally years.

If you meant by current standard this one tiny draft concerning
setup_call_cleanup/3 (tiny with respect to all the other current
material - lets say it amounts to 1% or so) - let me first make clear
that this is a draft and it has therefore no actual normative
relevance. Member bodies one day (when all other things are ready)
will vote - and then things become relevant or not.

Of course, by collecting the feedback of various implementations,
discussing and testing actual implementations the current draft
reflects the intention at least of those involved. So far we have one
perfect implementation (as far as I can see) SWI, one that is very
close to perfection YAP (only microscopic differences), and two that
are usable for the normal uses but have some of the "exotic" cases
missing: B, Qu. Compare this to January, when there was none.

http://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup

There are lots of *inherent* ambiguities in setup_call_cleanup/3.

For example you may have noted that there are many cases labelled
either-or. These cases are due to the way how an implementation is
able to determine "determinism" which currently depends on the
indexing techniques employed. All these cases are not specified
exactly: Instead an interval is given, where the cleanup will be
executed. So there is some ambiguity which is inevitable. In the
future, things might become even less determinate as detection of
determinism might (or not) be delegated to or augmented by some GC
like phrase. At least I am trying to put things such that such an
implementation would not be unnecessarily hindered. So far there was
no extra complication due to this rather long term perspective.

There are also ways to be 100% sure what happens, but then you would
have to use once/1, ! or throw/1.

>But concurrent Prolog introduces all sorts of
>ambiguities of this kind, it seems to me. Can
>threads access the same dynamic predicates? If
>so, do asserts and retracts affect pending goals
>(this would be bad) across threads?

setup_call_cleanup/3 is partially concerned with such aspects. It can
at least guarantee certain properties in the presence of asynchronuous
events. That was one of the reason to extend the traditional
call_cleanup/2 (supported by other 4 implementations) to the current
setup_call_cleanup/3 (supported by abovementioned 4 implementations).

I cannot comment much on threads except there is a thread proposal by
Paulo Moura, but input from others was so sparse and unconstructive
that he gave up. The only stable implementation seems to be SWI
(including dynamic stack management), but maybe there are others I am
not aware of. I looked at the proposal and concluded that we simply
have not enough manpower to make it. As long as there is not more
input - particularly from users - things will develop quite slowly,
but steadily. In case you are interested, consider first the
comparatively simple efforts we have now.

Just to give you one impression: Feedback concerning the state of
further built-ins and arithmetic functions by implementors:

http://www.complang.tuwien.ac.at/ulrich/iso-prolog/evaluable_functors

http://www.complang.tuwien.ac.at/ulrich/iso-prolog/built-in_predicates

The standard development for 13211-1 was very much supported by
industry, BTW. Otherwise it would have been impossible. Even if
people criticise it for being not fast "enough", I think it was quite
fast: Beginning 1987, and in vigor 1995-06-01. That is fast! In
particular if you consider all the diversity of sytems in the
beginning. And the deep problems that were resoveld and that nobody
dared to touch before.

Here are the current drafts - any comment is welcome - it's a way to
keep Prolog alife and healthy!

http://www.sju.edu/~jhodgson/wg17/Drafts/

http://www.complang.tuwien.ac.at/ulrich/iso-prolog/

Joachim Schimpf

unread,
Nov 30, 2009, 8:45:45 PM11/30/09
to
Ulrich Neumerkel wrote:
...

> ?- setup_call_cleanup(true, (X=1;X=2), write(semel)), setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.
>
> We have here two goals protected independently with cleanup semel and
> bis. The cleanups cannot be executed prior to ! since both have a
> further solution awaiting (X = 2 and Y = 2).
...

>
> The precise order is defined (7.8.11.1 d) "Several cleanup goals
> triggered simultaneously are executed in reversed order of
> installation."
>
> That means, the output shall be 'semelbis'. And the query succeeds,
> unifying X = 1, Y = 1.
>
> An output 'bissemel' would be not conformant.
>
> Currently I have slight doubts: Is this an overspecification?

Yes, it is an overspecification. How can you possibly rely on
the order in the cut case, when even in the cut-independent
cases you can get any order you want:

?- X=0, <code above>.
semel

?- X=2, Y=0, <code above>.
semelbis

?- Y=2, <code above>.
bissemel

?- X=1, Y=2, <code above>.
bissemel % with bad indexing
semelbis % with good indexing

?- Y=0, <code above>.
bissemelbis


> 1mo Arguments in favor of the current specification:
>
> + Cleanup bis and the associated goal might take certain resources for
> granted that were allocated by semel.

Sounds contrived, since Cleanup is supposed to release resources.
Anyway, when you use such invisible dependencies in your code,
you deserve what you get. The proper way to deal with this is
to have dependencies visible, e.g. by the use of resource handles.


>
> + By demanding to execute first cleanup bis and then semel, a system
> might recover memory earlier.

Or vice versa - what kind of argument is that?


>
> + One could even simply the specification to "Cleanup goals are
> executed in reversed order of installation".
>
> 2do Arguments that this is an overspecification
>
> - In contrast to an exception (which might be a resource error due to
> a memory overflow), there is no pressing need for space efficiency so
> implementations should not be restricted unnecessarily.
>
> - Systems might have some "selective" cut as an implementation
> specific extension that cuts one choice point "in the middle" away. I
> don't know if such a beast exists - just in case.

Yes, it is called a soft cut (if/3 in Sicstus, *->/2 in SWI and ECLiPSe,
and probably others).


Jan Wielemaker wrote:
> I guess most systems cancel choice-points in reverse order, but systems are
> likely to use different mechanisms to insert the execution of the cleanup goal
> into their scheduled work. E.g. SWI-Prolog does this using a call-back to the
> Prolog engine from the routine that does the choice-point pruning. Others may
> use a queue or stack of goals to be inserted at the first opportunity, etc.

Thanks for making this point. A lot of grief (and cost without benefit) can
be caused by specifications that assume a certain naive implementation model.


-- Joachim

Ulrich Neumerkel

unread,
Nov 30, 2009, 8:58:13 PM11/30/09
to
Joachim Schimpf <jsch...@users.sourceforge.net> writes:
>Ulrich Neumerkel wrote:
>...
>> ?- setup_call_cleanup(true, (X=1;X=2), write(semel)), setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.
>> Currently I have slight doubts: Is this an overspecification?
>
>Yes, it is an overspecification. How can you possibly rely on
>the order in the cut case, when even in the cut-independent
>cases you can get any order you want:
>
>?- X=0, <code above>.
>semel
>
>?- X=2, Y=0, <code above>.
>semelbis
>
>?- Y=2, <code above>.
>bissemel
>
>?- X=1, Y=2, <code above>.
>bissemel % with bad indexing
>semelbis % with good indexing
>
>?- Y=0, <code above>.
>bissemelbis

In the cases you give you are not much interested in guaranteeing
any order - as shows the ambiguous case quite clearly. So we
agree here, that for such cases the order would be qua si random.
True!

There are however cases where people explicitly leave things
open *on purpose* so they do not want to depend on the cleverness
of the implementation. For example some Erlang like network protocol
uses this. In the beginning the essential part was like this:

..., setup_call_cleanup(Nimporte, (true ; false), Cleanup), ....

So the intention of this code is to leave that choice point open.
(I tried to question that, but they wanted it like that, ok?)
First I was completely ruined: If we would demand that this
choicepoint remains open, essentially all kinds of optimizations
would be voided. But then luckily I realized that we can describe
this as follows:

..., setup_call_cleanup(Nimporte, ( X = 1; X = 2 ), Cleanup), X = 1, ...

In this manner the choicepoint has to remain open due to 7.8.11.1 c1.
We have to wait for the last solution X = 2 before the cleanup
can be executed (or one of the other events c2 or c3 happens).
Of course that is a little bit of a workaround, but they were happy
and there is no interference with whatever clever or not clever
indexing schema or dynamic GC based indexing.

So if people are that "picky" on the moment when Cleanup is executed,
above ambiguity will never happen to them.

>> 1mo Arguments in favor of the current specification:
>>
>> + Cleanup bis and the associated goal might take certain resources for
>> granted that were allocated by semel.
>
>Sounds contrived, since Cleanup is supposed to release resources.
>Anyway, when you use such invisible dependencies in your code,
>you deserve what you get. The proper way to deal with this is
>to have dependencies visible, e.g. by the use of resource handles.

You seem to think at a certain pattern of usage which is grosso
modo setup_call_cleanup(open_whatever(S), operate(S), close_somehow(S)).
That is *one* usage. And I would say that this is the most important
for the moment. open might be file opening but also opening a
connection. But there are much more interesting uses.
Apart from that networking, it seems also to be of relevance for things
like pure output (I sent you some esquisse for that).
In that case a cut might trigger a cleanup that "squeezes" out all
information needed further on.

>> + By demanding to execute first cleanup bis and then semel, a system
>> might recover memory earlier.
>
>Or vice versa - what kind of argument is that?

For the case of exceptions the order is entirely fixed - already
due to the bindings. We are interested in handling the exception with
the minimal binding, since this allows to recover a maximum of
memory which increases the chances that recovery will be successful.
I am always thinking of resource errors due to memory overflows..
So here we have a clear memory dependency. But in the
case of cut ... hm ... well that's open for the moment.

>> + One could even simply the specification to "Cleanup goals are
>> executed in reversed order of installation".
>>
>> 2do Arguments that this is an overspecification
>>
>> - In contrast to an exception (which might be a resource error due to
>> a memory overflow), there is no pressing need for space efficiency so
>> implementations should not be restricted unnecessarily.
>>
>> - Systems might have some "selective" cut as an implementation
>> specific extension that cuts one choice point "in the middle" away. I
>> don't know if such a beast exists - just in case.
>
>Yes, it is called a soft cut (if/3 in Sicstus, *->/2 in SWI and ECLiPSe,
>and probably others).

This cut cannot cut away a choicepoint that would trigger a Cleanup.
Or can it? That was what I wanted to ask for - the question was a
bit too general, I admit.

Joachim Schimpf

unread,
Nov 30, 2009, 10:51:49 PM11/30/09
to
Ulrich Neumerkel wrote:
> Joachim Schimpf <jsch...@users.sourceforge.net> writes:
>> Ulrich Neumerkel wrote:
>> ...
>>> ?- setup_call_cleanup(true, (X=1;X=2), write(semel)), setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.
>>> Currently I have slight doubts: Is this an overspecification?
>> Yes, it is an overspecification. How can you possibly rely on
>> the order in the cut case, when even in the cut-independent
>> cases you can get any order you want:
>>
>> ?- X=0, <code above>.
>> semel
>>
>> ?- X=2, Y=0, <code above>.
>> semelbis
>>
>> ?- Y=2, <code above>.
>> bissemel
>>
>> ?- X=1, Y=2, <code above>.
>> bissemel % with bad indexing
>> semelbis % with good indexing
>>
>> ?- Y=0, <code above>.
>> bissemelbis
>
> In the cases you give you are not much interested in guaranteeing
> any order - as shows the ambiguous case quite clearly. So we
> agree here, that for such cases the order would be qua si random.

No we don't agree - you are only reading what you want to read.
My point is that if you have
p(...),
setup_call_cleanup(true, q(...), write(one)),
setup_call_cleanup(true, r(...), write(two)),
!.
you can't rely on 'one' and 'two' to appear in any particular
order, whether you specify anything for the cut or not. So don't.


> There are however cases where people explicitly leave things
> open *on purpose* so they do not want to depend on the cleverness
> of the implementation. For example some Erlang like network protocol
> uses this. In the beginning the essential part was like this:
>
> ..., setup_call_cleanup(Nimporte, (true ; false), Cleanup), ....
>
> So the intention of this code is to leave that choice point open.
> (I tried to question that, but they wanted it like that, ok?)
> First I was completely ruined: If we would demand that this
> choicepoint remains open, essentially all kinds of optimizations
> would be voided. But then luckily I realized that we can describe
> this as follows:
>
> ..., setup_call_cleanup(Nimporte, ( X = 1; X = 2 ), Cleanup), X = 1, ...
>
> In this manner the choicepoint has to remain open due to 7.8.11.1 c1.
> We have to wait for the last solution X = 2 before the cleanup
> can be executed (or one of the other events c2 or c3 happens).
> Of course that is a little bit of a workaround, but they were happy
> and there is no interference with whatever clever or not clever
> indexing schema or dynamic GC based indexing.
>
> So if people are that "picky" on the moment when Cleanup is executed,
> above ambiguity will never happen to them.

This is all quite ridiculous. When something gets that unwieldy
and hacky, don't you think it's time to stop and think
"Maybe this is the wrong approach to the problem?"

You chose to ignore the related comment about resource handles in
my previous reply. Let me try to be a bit clearer and inject an
idea into this debate, that could explain why all this is so messy:

We are trying to solve a problem that is essentially a garbage
collection type of problem (freeing a resource that is used by some
subgoals of Goal). The resource can be freed when no such subgoals
of Goal exist any longer (or will ever exist on backtracking).
However, setup_call_cleanup/3 is trying to solve this GC like problem
via a plain control flow solution. It tries to guess something
about reachability of the resource by looking at choicepoints.
This just doesn't work very well. And worse, since the programmer
knows that this is how it works, they start hacking with dummy
choicepoints in order to convey some reachability information.

It's like doing stack memory management via failure driven loops
all over again.


-- Joachim

Joachim Schimpf

unread,
Dec 1, 2009, 2:33:38 AM12/1/09
to
Joachim Schimpf wrote:
> Ulrich Neumerkel wrote:
>> There are however cases where people ...

>
> This is all quite ridiculous. When something gets that unwieldy
> and hacky, ...

Before this comes across in the wrong way: I greatly appreciate
Ulrichs effort to get something done. My experience is only that
one has to be wary of feature requests of the form
"I need [exotic feature X] because I want to use
[clunky method Y] to implement [sensible thing Z]"
In 99% of cases there is a better way to get there. If there
wasn't I'd long have given up computer science ;-)

-- Joachim

Ulrich Neumerkel

unread,
Dec 1, 2009, 5:41:32 AM12/1/09
to

I clearly disagree here: It depends on the definition of q/1 and r/1!
So let me fill in the ellipsis:

p(_).

q(1).
q(2).

r(10).
r(20).

And the query is:

p(_),
setup_call_cleanup(true, q(Q), write(one)),
setup_call_cleanup(true, r(R), write(two)),
!.

Then - in this case - and clearly in contrast to your claim -
we can rely on the fact that none of the cleanups will be
executed prior to the !. And it is only that ! that will cause
them to be executed! Thus - in this situation here - the
specification of an order would be very much of relevance.

>> There are however cases where people explicitly leave things
>> open *on purpose* so they do not want to depend on the cleverness
>> of the implementation. For example some Erlang like network protocol
>> uses this. In the beginning the essential part was like this:
>>
>> ..., setup_call_cleanup(Nimporte, (true ; false), Cleanup), ....
>>
>> So the intention of this code is to leave that choice point open.
>> (I tried to question that, but they wanted it like that, ok?)
>> First I was completely ruined: If we would demand that this
>> choicepoint remains open, essentially all kinds of optimizations
>> would be voided. But then luckily I realized that we can describe
>> this as follows:
>>
>> ..., setup_call_cleanup(Nimporte, ( X = 1; X = 2 ), Cleanup), X = 1, ...
>>
>> In this manner the choicepoint has to remain open due to 7.8.11.1 c1.
>> We have to wait for the last solution X = 2 before the cleanup
>> can be executed (or one of the other events c2 or c3 happens).
>> Of course that is a little bit of a workaround, but they were happy
>> and there is no interference with whatever clever or not clever
>> indexing schema or dynamic GC based indexing.
>>
>> So if people are that "picky" on the moment when Cleanup is executed,
>> above ambiguity will never happen to them.
>
>This is all quite ridiculous. When something gets that unwieldy
>and hacky, don't you think it's time to stop and think
>"Maybe this is the wrong approach to the problem?"

Hey, I want to encourage discussion "even" with people who might
get offended by your conjecture that this is all quite ridiculous!
This can be very intimidating to people. Think of it: They
are working on a system - and then their manager reads your
statement or their imperative cow-orkers read that what they
do is all ridiculous. Not funny.

What I learned since January is that there is one further usage
of setup_call_cleanup/3 that I have not thought about before,
but that is actually in use. Oh yes, I even tried to deny
it for some weeks.

Before, I thought the purpose would be guarding some goal, and
relying on the cleverness of a system for indexing to cleanup
the mess as fast as possible. What I had in mind (for the
more complex cases - in most situations things are much
simpler) was library(pio).

Well, wrong I was - or rather narrow minded. Since there
is more.

Some use it in a way such that the execution of the cleanup is
entirely predetermined. Their programs would executed exactly
the same way whether or not the initial Goal is replaced
by ( Goal ; false ) or another trick to get a choicepoint in.

>You chose to ignore the related comment about resource handles in
>my previous reply. Let me try to be a bit clearer and inject an
>idea into this debate, that could explain why all this is so messy:
>
>We are trying to solve a problem that is essentially a garbage
>collection type of problem (freeing a resource that is used by some
>subgoals of Goal). The resource can be freed when no such subgoals
>of Goal exist any longer (or will ever exist on backtracking).
>However, setup_call_cleanup/3 is trying to solve this GC like problem
>via a plain control flow solution. It tries to guess something
>about reachability of the resource by looking at choicepoints.
>This just doesn't work very well. And worse, since the programmer
>knows that this is how it works, they start hacking with dummy
>choicepoints in order to convey some reachability information.
>
>It's like doing stack memory management via failure driven loops
>all over again.

A garbage collection type problem - yes - that what is called
usually "nonfunctional". I.e. a nonfunctional specification
depends on some external imponderabilities like execution time,
quality of service or in this case here the capability to
detect determinism.

This capability is now solved via a "plain control flow solution".
Yes! Thanks to the ingenuity of Mats Carlsson who invented
it in Autumn 1987!

Compare this to the situation before! How was it possible
to execute a goal with certain resources open? Prior to this
cleanup mechanism we had no other chance than to forcefully
cut alternative solutions of a goal. Something along the line
(without the exception handeling and unanticipated failure):

with_file_open(G, S, F) :-
open(F, read, S),
G,
!, % Ouch!
close(S).

And in fact there were such libraries (one by Richard O'Keefe)
that forcefully and entirely unnecessarily (from the viewpoint
of the user - not the system) *had* to cut into the flesh
of Prolog.

Now, we are over this. Finally!

And it turns out, that this new construct is now used for things
a little bit unanticipated - as we were completely bound
looking in one direction. Quelle bonne surprise !

A.L.

unread,
Dec 1, 2009, 9:02:21 AM12/1/09
to

There was discussion on comp.lang.lisp about standarization of Lisp.
Common agreement was that too much standarization and too many fancy
features effectively killed Lisp.

General advice: if something is not broken, don't fix it.

A.L.

Joachim Schimpf

unread,
Dec 1, 2009, 9:26:37 AM12/1/09
to

Good grief. Do I really not make myself clear? I try one last time.

Of course you can work out what happens IF you analyse the program.
That's not my point. My point is: If you want to protect the two
consecutive goals q and r with setup_call_cleanups, say

setup_call_cleanup(alloc1(R), q(..,R,..), free1(R)),
setup_call_cleanup(alloc2(S), r(..,S,..), free2(S)),

then you better write free1/1 and free2/1 in such a way that they
work independently of the order in which they are executed,
because this order will depend on:
- the instantiations when entering the whole code sequence
- the delayed goals/constraints that are in place
- the internals of p and q
- the properties of your compiler
All of which means it would be highly irresponsible to assume a
particular order (even without any cuts). Whether now the use of
a cut adds one additional source of uncertainty about the order
doesn't really matter, so you may as well leave it unspecified.

Ok, I'm half sorry for that formulation, but note that it did not
refer to the application writers, but solely to your argument.

You want to introduce a standard builtin with the stated purpose
of supporting a rather clunky hack - that's what I found funny.


> What I learned since January is that there is one further usage
> of setup_call_cleanup/3 that I have not thought about before,
> but that is actually in use. Oh yes, I even tried to deny
> it for some weeks.
>
> Before, I thought the purpose would be guarding some goal, and
> relying on the cleverness of a system for indexing to cleanup
> the mess as fast as possible. What I had in mind (for the
> more complex cases - in most situations things are much
> simpler) was library(pio).

Maybe it's time that you say precisely what this mysterious
requirement of lib(pio) is, because I looked at the library
and couldn't figure it out.

All very nice, but could you comment on what I actually wrote?


> This capability is now solved via a "plain control flow solution".

No, it's not solved because, as I tried to explain, reachability
cannot be inferred from the presence of choicepoints. Example:

?- setup_call_cleanup(open(foo,write,S),freeze(X,write(S,X)),close(S)), X=2.
ERROR: write/2: stream `$stream(1070183496)' does not exist


> Yes! Thanks to the ingenuity of Mats Carlsson who invented
> it in Autumn 1987!

I'm sure Mats has a more measured appreciation of his idea.


> Compare this to the situation before! How was it possible
> to execute a goal with certain resources open?

I'm not saying that there is no problem to be solved, I'm saying
that setup_call_cleanup/3 is a poor way of addressing it.
The Prolog-like system I'm working on uses external resources
_a_lot_, but thanks to the way it implements and garbage-collects
handles for these resources, we have been happily living without
such a builtin so far. [i/o streams have for historic reasons
not been implemented using these handles, but we'll do it in the
upcoming release].


> And it turns out, that this new construct is now used for things
> a little bit unanticipated - as we were completely bound
> looking in one direction. Quelle bonne surprise !

Rather a nightmare: the primitive leaks implementation-level
information about presence of choicepoints into the program logic:
setup_call_cleanup(true, Goal, Det=yes).
Yes, I too want to have that information for implementing a
debugger or toplevel, but in a standard language primitive?

Even worse, a cut can now magically trigger a variable binding:
?- setup_call_cleanup(true, (true;true), Det=surprise),
writeln(Det), !, writeln(Det).
_G835
surprise
Hardly desirable, and not something your average program analyzer
would expect to happen!

So my suggestions: if you want to have setup_call_cleanup/3 anyway,
please at least get rid of these two nasty effects. This can be done
by requiring that the Cleanup goal has only the instantiation at the
end of Setup, and does not share variables with the context. The
basic open/close examples all work with such a semantics.
[I have put such a variant into ECLiPSe 6.0_102, lib(iso)]

If you want a primitive to tell you about choicepoints, use one that
doesn't spontaneously bind a variable, I can suggest something.
But anyway, I don't think it belongs in the Prolog standard.


-- Joachim

Jan Wielemaker

unread,
Dec 1, 2009, 10:05:42 AM12/1/09
to
On 2009-12-01, A.L <alew...@aol.com> wrote:
> There was discussion on comp.lang.lisp about standarization of Lisp.
> Common agreement was that too much standarization and too many fancy
> features effectively killed Lisp.
>
> General advice: if something is not broken, don't fix it.

(ISO) Prolog is broken: there is just about no real-world program that
stays within the standard. I agree that standards can be too big. They
can also be too small and that is the problem with the current ISO
standard. Luckily there is some work to enhance portability.


Cheers --- Jan

Jan Wielemaker

unread,
Dec 1, 2009, 10:24:36 AM12/1/09
to
On 2009-12-01, Joachim Schimpf <jsch...@users.sourceforge.net> wrote:
> No, it's not solved because, as I tried to explain, reachability
> cannot be inferred from the presence of choicepoints. Example:
>
> ?- setup_call_cleanup(open(foo,write,S),freeze(X,write(S,X)),close(S)), X=2.
> ERROR: write/2: stream `$stream(1070183496)' does not exist

That is a pretty good point. Not really for setup_call_cleanup, because
it is about ISO Prolog that doesn't have coroutining, but as many
real systems do have coroutining, it is a fair point.

> I'm not saying that there is no problem to be solved, I'm saying
> that setup_call_cleanup/3 is a poor way of addressing it.
> The Prolog-like system I'm working on uses external resources
> _a_lot_, but thanks to the way it implements and garbage-collects
> handles for these resources, we have been happily living without
> such a builtin so far. [i/o streams have for historic reasons
> not been implemented using these handles, but we'll do it in the
> upcoming release].

Here I'm getting curious. Do you do this in normal heap-GC? That would
exclude handles asserted into the database and requires you to inspect
the holes in the stack. It also requires scanning for handles that
become unreachable due to backtracking. The alternative (which is
supported by SWI-Prolog but (still) not used much) is to use things that
behave as atoms and rely on atom-gc. The problem with this type of GC
however is that it requires synchronising threads and it is difficult to
schedule.

Let us look at I/O handles. Typically, you do not have that many of
them, so you should collect them in time. How do you ensure that? Call
the GC if you are out of I/O handles? Second issue is that you sometimes
simply want to close an I/O channel. Ideally, that means forcing that
all pending computation (choice-points and constraints) are `finished'.
How do you do that?

> Rather a nightmare: the primitive leaks implementation-level
> information about presence of choicepoints into the program logic:
> setup_call_cleanup(true, Goal, Det=yes).
> Yes, I too want to have that information for implementing a
> debugger or toplevel, but in a standard language primitive?

Being deterministic or not is of such importance to (advanced) Prolog
programmers that it can hardly be called `implementing-level' :-)
Nevertheless, I agree that setup_call_cleanup/3 isn't the way to find
out for most practical usage. That should be declarations that are
checked at compile- or (optionally) runtime.

Cheers --- Jan

Ulrich Neumerkel

unread,
Dec 1, 2009, 9:34:38 AM12/1/09
to

Please consider how an out-of-context quote like yours above would
look like in a meeting. We really need a little bit more toned
down language.

>You want to introduce a standard builtin with the stated purpose
>of supporting a rather clunky hack - that's what I found funny.

With this alleging ad-hominem language you destroy broad
and open discussion in comp.lang.prolog. How many of actual
users will now dare to say a single word - when the main
developer of a Prolog system called their work a clunky
hack? I can only hope you do not treat users of your system
in the same manner. In any case it will remain a dialogue
between you and me - at best. And my aim here was to get a
discussion going that also includes actual users - whose
contribution would be primarily them telling us about their
experience.

We now have a lot of users of setup_call_cleanup/3. It did
solve some of the open issues which are important in the context
of threads and lots of stability issues for programs running
for weeks.

Read my posting of 2009-01-14 to see my stated purpose.
*That* is my stated purpose.

http://groups.google.at/group/comp.lang.prolog/tree/browse_frm/month/2009-01/475c5fa4ab7c06ca?rnum=71&_done=%2Fgroup%2Fcomp.lang.prolog%2Fbrowse_frm%2Fmonth%2F2009-01%3F#doc_475c5fa4ab7c06ca

Would you be more happy if I would not state all purposes people
told me in the meantime? We can consider those
and weigh them against the effort to implement, what that would
mean to the development of the language, their semantic
impact - whatever. We could dismiss some of the parts. But
in any case we would need more justification than judgmental
phrases. And we can do this without going on a level that
neither befits scholars nor professional programmers alike.

There are some points in your posting nevertheless. I will answer
them sometime - but if you continue with your choice of language
it would be futile.


In any case, should some other system developer be interested
to implement the current approach - don't hesitate to contact
me. The test cases cover all the problems I know of.

http://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup

Joachim Schimpf

unread,
Dec 1, 2009, 7:49:51 PM12/1/09
to
Jan Wielemaker wrote:
> On 2009-12-01, Joachim Schimpf <jsch...@users.sourceforge.net> wrote:
>>...

>> I'm not saying that there is no problem to be solved, I'm saying
>> that setup_call_cleanup/3 is a poor way of addressing it.
>> The Prolog-like system I'm working on uses external resources
>> _a_lot_, but thanks to the way it implements and garbage-collects
>> handles for these resources, we have been happily living without
>> such a builtin so far. [i/o streams have for historic reasons
>> not been implemented using these handles, but we'll do it in the
>> upcoming release].
>
> Here I'm getting curious. Do you do this in normal heap-GC?

That's part of it.

> That would exclude handles asserted into the database

These are treated by reference counting.

> and requires you to inspect the holes in the stack.

Not sure what you refer to here??

> It also requires scanning for handles that
> become unreachable due to backtracking.

No scanning, it's done via trailing.

...


> Let us look at I/O handles. Typically, you do not have that many of
> them, so you should collect them in time. How do you ensure that? Call
> the GC if you are out of I/O handles?

That would be the solution, except you would probably trigger
the GC when you are _almost_ out of handles.

> Second issue is that you sometimes
> simply want to close an I/O channel. Ideally, that means forcing that
> all pending computation (choice-points and constraints) are `finished'.
> How do you do that?

I can't comment without seeing a clearer specification of that.

>
>> Rather a nightmare: the primitive leaks implementation-level
>> information about presence of choicepoints into the program logic:
>> setup_call_cleanup(true, Goal, Det=yes).
>> Yes, I too want to have that information for implementing a
>> debugger or toplevel, but in a standard language primitive?
>
> Being deterministic or not is of such importance to (advanced) Prolog
> programmers that it can hardly be called `implementing-level' :-)
> Nevertheless, I agree that setup_call_cleanup/3 isn't the way to find
> out for most practical usage. That should be declarations that are
> checked at compile- or (optionally) runtime.

Cheers,
Joachim

Jan Wielemaker

unread,
Dec 2, 2009, 3:29:26 AM12/2/09
to
On 2009-12-02, Joachim Schimpf <jsch...@users.sourceforge.net> wrote:
> Jan Wielemaker wrote:
>> On 2009-12-01, Joachim Schimpf <jsch...@users.sourceforge.net> wrote:
>>>...
>>> I'm not saying that there is no problem to be solved, I'm saying
>>> that setup_call_cleanup/3 is a poor way of addressing it.
>>> The Prolog-like system I'm working on uses external resources
>>> _a_lot_, but thanks to the way it implements and garbage-collects
>>> handles for these resources, we have been happily living without
>>> such a builtin so far. [i/o streams have for historic reasons
>>> not been implemented using these handles, but we'll do it in the
>>> upcoming release].
>>
>> Here I'm getting curious. Do you do this in normal heap-GC?
>
> That's part of it.
>
>> That would exclude handles asserted into the database
>
> These are treated by reference counting.
>
> > and requires you to inspect the holes in the stack.
>
> Not sure what you refer to here??

I mean, normal GC only pays attention to the life data on the stacks. Now you
must also look at the dead data and take action if you GC'ed a handle.

>> It also requires scanning for handles that
>> become unreachable due to backtracking.
>
> No scanning, it's done via trailing.

Bu what if you are creating a term with a handle above the top-of-stack
marker of the last choice-point. Normally you wouldn't trail that, but
you simply chop-off the top-part of the stack.

>> Let us look at I/O handles. Typically, you do not have that many of
>> them, so you should collect them in time. How do you ensure that? Call
>> the GC if you are out of I/O handles?
>
> That would be the solution, except you would probably trigger
> the GC when you are _almost_ out of handles.

:-)

> > Second issue is that you sometimes
>> simply want to close an I/O channel. Ideally, that means forcing that
>> all pending computation (choice-points and constraints) are `finished'.
>> How do you do that?
>
> I can't comment without seeing a clearer specification of that.

What I'm referring to is that using setup_call_clean/3, you can make
sure all work is being completed and the I/O channel is being closed
by forcing determinism. You want to deal with coroutining. That
will will make it pretty hard to force termination.

A scenario I had in mind is that we had Prolog generating a file that was then
passed to an external application. I used setup_call_cleanup/3, but the code
was non-deterministic. As a result, the file remained open and on some
unnamed OS we got a sharing violation. With setup_call_cleanup/3 and
only choice-points to worry about, this is still quite tracktable, but
with arbitrary coroutining?

Cheers --- Jan

bart demoen

unread,
Dec 2, 2009, 3:11:20 PM12/2/09
to
On Sat, 28 Nov 2009 22:30:51 +0000, Ulrich Neumerkel wrote:


> ?- setup_call_cleanup(true, (X=1;X=2), write(semel)),
> setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.

[..]


> The precise order is defined (7.8.11.1 d) "Several cleanup goals
> triggered simultaneously are executed in reversed order of
> installation."
>
> That means, the output shall be 'semelbis'. And the query succeeds,
> unifying X = 1, Y = 1.
>
> An output 'bissemel' would be not conformant.

???

I have skimmed through the resulting conversation, and I got the impression
that some people said that actually bissemel would be the required output, but
you never confirmed that - so before attempting into any "to the point" reaction,
please, according to the order defined in 7.8.11.1.d - the reverse order - should
it be semelbis or bissemel ?

Cheers

Bart Demoen

ps. reacting anyway :-) does anyone think we should in Prolog repeat the
mistake Java made with the finalizer ?

Ulrich Neumerkel

unread,
Dec 2, 2009, 3:27:23 PM12/2/09
to
bart demoen <b...@cs.kuleuven.be> writes:
>On Sat, 28 Nov 2009 22:30:51 +0000, Ulrich Neumerkel wrote:
>
>
>> ?- setup_call_cleanup(true, (X=1;X=2), write(semel)),
>> setup_call_cleanup(true,(Y=1;Y=2),write(bis)), !.
>[..]
>> The precise order is defined (7.8.11.1 d) "Several cleanup goals
>> triggered simultaneously are executed in reversed order of
>> installation."
>>
>> That means, the output shall be 'semelbis'. And the query succeeds,
>> unifying X = 1, Y = 1.
>>
>> An output 'bissemel' would be not conformant.
>
>???
>
>I have skimmed through the resulting conversation, and I got the impression
>that some people said that actually bissemel would be the required output, but
>you never confirmed that - so before attempting into any "to the point" reaction,
>please, according to the order defined in 7.8.11.1.d - the reverse order - should
>it be semelbis or bissemel ?

That was wrong, and I confirmed it twice in response to Jan and
Chip Eastham.

>ps. reacting anyway :-) does anyone think we should in Prolog repeat the
>mistake Java made with the finalizer ?

It is not the same situation. Java does always depend on GC.
But setup_call_cleanup/3 has very precise intervals
defined that an implementatin must respect.
That is, indexing, GC or whatever other approach is chosen
is of relevance between those intervals not outside.

The spec defines the intervals - the programmer can decide
how he uses it: Such that he has complete control (actually
we are discussing this - there was this tiny little ambiguity)
- or that he gives some part of the control to the system.

So far, we have both - the analogy you mention to Java
thus does not hold. Also, could you please for the sake
of the discussion give use some evidence by third party
that this finalizer issue in Java is actually considered
a mistake? Does Sun share this view? Or any other involved
or opposing party?

bart demoen

unread,
Dec 2, 2009, 5:29:51 PM12/2/09
to
On Wed, 02 Dec 2009 20:27:23 +0000, Ulrich Neumerkel wrote:

> bart demoen <b...@cs.kuleuven.be> writes:

>>ps. reacting anyway :-) does anyone think we should in Prolog repeat the
>>mistake Java made with the finalizer ?
>
> It is not the same situation. Java does always depend on GC. But
> setup_call_cleanup/3 has very precise intervals defined that an
> implementatin must respect.


I was not referring to setup_call_cleanup/3 but to the subsequent
exchange of postings between Joachim and Jan (and maybe you).


> So far, we have both - the analogy you mention to Java thus does not
> hold.

As I just said and confirming now: not related to any specs
of setup_call_cleanup/3.

> Also, could you please for the sake of the discussion give use
> some evidence by third party that this finalizer issue in Java is
> actually considered a mistake? Does Sun share this view? Or any other
> involved or opposing party?

ISMM 1998 Vancouver - lots of it. Also some proponents of course, but
lots of critisism. Enough for me to conclude "let's not go there in Prolog".

But maybe you want to form your own opinion about finalizers - I suggest you
think a bit (really a bit is enough) about to what extent it helps the
programmer in constructing more reliable software and what constraints it
puts on the collector. Whether Sun agrees is not an issue. Read the
Lindholm/Yellin book: you will find all the facts there - no Sun opinion,
but enough for anyone with a mind to make up his/her own mind.


Cheers

Bart Demoen

Joachim Schimpf

unread,
Dec 3, 2009, 7:31:21 AM12/3/09
to

The main idea is that an external object has a representative on the
global stack (called the anchor). All references from Prolog data
go to this anchor. When the anchor becomes garbage, the external
object can be freed. When the anchor is created, it is always above
the last choicepoint, but it is trailed anyway (with a special undo-trail
frame). On failure, this trail frame causes the object to be freed.
On GC, when the GC discovers an undo-trail frame that points to an
otherwise unreferenced anchor, it frees the external object and collects
anchor and trail frame. I have written a little bit about it in the
TPLP submission that you should have access to, or see the comment in
this file
http://eclipse-clp.cvs.sourceforge.net/viewvc/eclipse-clp/Eclipse/Kernel/src/handle.c?revision=1.1&view=markup


>
>>> Let us look at I/O handles. Typically, you do not have that many of
>>> them, so you should collect them in time. How do you ensure that? Call
>>> the GC if you are out of I/O handles?
>> That would be the solution, except you would probably trigger
>> the GC when you are _almost_ out of handles.
>
> :-)
>
>>> Second issue is that you sometimes
>>> simply want to close an I/O channel. Ideally, that means forcing that
>>> all pending computation (choice-points and constraints) are `finished'.
>>> How do you do that?
>> I can't comment without seeing a clearer specification of that.
>
> What I'm referring to is that using setup_call_clean/3, you can make
> sure all work is being completed and the I/O channel is being closed
> by forcing determinism.

There are special cases where the liveness of a handle indeed
coincides with the lifetime of a choicepoint. For example in the
database interface, we have a nondeterministic retrieve_tuple
primitive which uses a database cursor. The cursor is only used
locally in that predicate, and is not needed after the predicate's
choicepoint is cut. Since cursors are precious, we want to free
them asap, rather than waiting for GC. So, yes, here we use
call_cleanup's idea of doing something at cut time, but it's done
optionally in addition to the default untrail and GC mechanism.


> You want to deal with coroutining.

That was just an example to illustrate the problem of carrying
some copy of the handle out of the scope of call_cleanup. It
could simply be that handle copies end up inside complex data
structures that are passed out of call_cleanup's Goal. My point
was just to say that in general there is no connection between
choicepoints and the lifetime of a handle.


> You want to deal with coroutining. That
> will will make it pretty hard to force termination.

Termination of what?


>
> A scenario I had in mind is that we had Prolog generating a file that was then
> passed to an external application. I used setup_call_cleanup/3, but the code
> was non-deterministic. As a result, the file remained open and on some
> unnamed OS we got a sharing violation. With setup_call_cleanup/3 and
> only choice-points to worry about, this is still quite tracktable, but
> with arbitrary coroutining?
>
> Cheers --- Jan

Cheers,
Joachim

Jan Wielemaker

unread,
Dec 3, 2009, 8:12:42 AM12/3/09
to
On 2009-12-03, Joachim Schimpf <jsch...@users.sourceforge.net> wrote:

> haiJan Wielemaker wrote:
>> Bu what if you are creating a term with a handle above the top-of-stack
>> marker of the last choice-point. Normally you wouldn't trail that, but
>> you simply chop-off the top-part of the stack.
>
> The main idea is that an external object has a representative on the
> global stack (called the anchor). All references from Prolog data
> go to this anchor. When the anchor becomes garbage, the external
> object can be freed. When the anchor is created, it is always above
> the last choicepoint, but it is trailed anyway (with a special undo-trail
> frame). On failure, this trail frame causes the object to be freed.
> On GC, when the GC discovers an undo-trail frame that points to an
> otherwise unreferenced anchor, it frees the external object and collects
> anchor and trail frame. I have written a little bit about it in the
> TPLP submission that you should have access to, or see the comment in
> this file
> http://eclipse-clp.cvs.sourceforge.net/viewvc/eclipse-clp/Eclipse/Kernel/src/handle.c?revision=1.1&view=markup

I got the idea. Thanks.

> > You want to deal with coroutining.
>
> That was just an example to illustrate the problem of carrying
> some copy of the handle out of the scope of call_cleanup. It
> could simply be that handle copies end up inside complex data
> structures that are passed out of call_cleanup's Goal. My point
> was just to say that in general there is no connection between
> choicepoints and the lifetime of a handle.

That was pretty clear and a very valid critisism on
setup_call_cleanup/3.

> > You want to deal with coroutining. That
>> will will make it pretty hard to force termination.
>
> Termination of what?

I/O isn't closed as long as there are delayed goals on which the
termination depends. I guess I'm refering to call_residue and friends,
which you need to use to deal with possibly remaining goals. Of course
that is a general problem with coroutining.

Cheers --- Jan

P.s. Given threading, things will have to be more complicated. Handing
I/O handles to another thread is not uncommon (e,g., a server where
one thread does the accept() on a socket and then passes the new
handle to another thread for dealing with the new connection).

That was one of the reasons to use something similar to atom-handling
for handles, but atom-gc has other not-do-desirable features.

Ulrich Neumerkel

unread,
Dec 3, 2009, 1:54:11 PM12/3/09
to

Thank you for your suggestion. The book you mention comprises about 473
pages (2nd edition). And a cursory look on its cover shows a
Java coffee cup on the right, and a Sun logo on the left side. It
is very well possible that as you say "no Sun opinion" is
inside. But on the cover I read "... from the Source (TM)". That does
not suggest to be the kind of third party opinion I am searching
for. And a cursory search for finalization does not show me any
critical remark in the book that could help me understand your
position.

I guess the ISMM proceedings might be even more voluminous.
And weighing there the proponents against the opponents
goes beyond anything I could do here in reasonable time.

So as long as I do not get any concrete citations of
statements of experts in this field - I can only note this
as your opinion on Java.

The hearsay I got was that many Java or .net programmers are
still in this phrase of transition giving up control on
memory. But that's just hearsay.

bart demoen

unread,
Dec 3, 2009, 3:46:56 PM12/3/09
to
On Thu, 03 Dec 2009 18:54:11 +0000, Ulrich Neumerkel wrote:


> So as long as I do not get any concrete citations of statements of
> experts in this field - I can only note this as your opinion on Java.

You underestimate my expertise in this field :-)
I still hope you will find the time to form your own opinion.

Bart Demoen

Ulrich Neumerkel

unread,
Dec 3, 2009, 4:56:07 PM12/3/09
to
bart demoen <b...@cs.kuleuven.be> writes:
>On Thu, 03 Dec 2009 18:54:11 +0000, Ulrich Neumerkel wrote:
>> So as long as I do not get any concrete citations of statements of
>> experts in this field - I can only note this as your opinion on Java.
>
>You underestimate my expertise in this field :-)

Maybe that helps: I do not even consider myself competent
enough to estimate your expertise in the field of Java
finalization - or .net for that matter. I try to collect
arguments that can help us to get a better view of the
subject - actually of setup_call_cleanup/3.

I do remember vaguely that some years ago - when you first
saw at that time call_cleanup/2 - you compared it with finialization
and put exactly the same argument forward, namely that in Java
it is something mistaken. That's why I reacted here.

Reference to expertise alone isn't it. We need the actual
arguments! Imagine a meeting:

A: So we shall vote on subclause 3.2.5.

B: Why?

A: Because 5 is from C, and C is an expert.

B: What are C's arguments?

A: C is a very big expert in the field of 3.2 and
of quinquesian subjects in general!

B: What properties does C consider desirable?

A: We shall not underestimate C's expertise in the field!

It does not work.

It could have been a mistake of me to implicitly assume
that you still believe that the analogy between
setup_call_cleanup/3 and finalization in Java is of relevance.

Nevertheless, for completeness please note that there might
be analoguous sitations! I am not speaking about a concrete
implementation. But it is imaginable that an implementation
triggers the cleanup due to a garbage collection that also
performs indexing. So far there is not a single system
doing this. But I keep an eye on the draft that such will
not be forbidden. Maybe sometime it will be done - maybe
that takes another 20 years. Maybe earlier.

>I still hope you will find the time to form your own opinion.

I am very wary of forming an opinion where I do not expect
to gain sufficient expertise to have something I would call
opinion. I rather prefer to get more experts in, who
can be questioned. Unfortunately many real experts
are pretty hesitant to help out. Some of them say: yes,
I use that feature - but I am no expert.

Ulrich Neumerkel

unread,
Dec 10, 2009, 9:27:22 AM12/10/09
to
Ok, I am trying to comment on some of your points.
I try it one by one:

Joachim Schimpf <jsch...@users.sourceforge.net> writes:
>> This capability is now solved via a "plain control flow solution".
>
>No, it's not solved because, as I tried to explain, reachability
>cannot be inferred from the presence of choicepoints. Example:
>
>?- setup_call_cleanup(open(foo,write,S),freeze(X,write(S,X)),close(S)), X=2.
>ERROR: write/2: stream `$stream(1070183496)' does not exist

How much semantics information do you expect the control construct
shall handle? You think of freeze/2. What else? All kinds of
constraints? Or all constraints somehow attached to the stream?

How shall we specify which constraint is of relevance and which is not?

Clearly you have in mind here a much more versatile construct
that requires some kind of specification languages for the
relevant constraints.

setup_call_cleanup/3 is significantly simpler than that.

Ulrich Neumerkel

unread,
Dec 10, 2009, 9:35:27 AM12/10/09
to
Joachim Schimpf <jsch...@users.sourceforge.net> writes:
>Ulrich Neumerkel wrote:

>> Compare this to the situation before! How was it possible
>> to execute a goal with certain resources open?
>
>I'm not saying that there is no problem to be solved, I'm saying
>that setup_call_cleanup/3 is a poor way of addressing it.
>The Prolog-like system I'm working on uses external resources
>_a_lot_, but thanks to the way it implements and garbage-collects
>handles for these resources, we have been happily living without
>such a builtin so far. [i/o streams have for historic reasons
>not been implemented using these handles, but we'll do it in the
>upcoming release].

In your approach - if I correctly understand it - you entirely rely on
garbage collection. There is thus no way for the caller to guarantee
somehow that a cleanup has been executed.

With setup_call_cleanup/3 the temporal interval where to call the
Cleanup is - relatively small. Most Cleanups can be executed
without ever invoking GC.

But as far as I understood you, you always have to wait for GC to
decide what will happen.

With setup_call_cleanup/3 things are simpler, but then your
generality cannot be covered by it.

>> And it turns out, that this new construct is now used for things
>> a little bit unanticipated - as we were completely bound
>> looking in one direction. Quelle bonne surprise !
>
>Rather a nightmare: the primitive leaks implementation-level
>information about presence of choicepoints into the program logic:
> setup_call_cleanup(true, Goal, Det=yes).
>Yes, I too want to have that information for implementing a
>debugger or toplevel, but in a standard language primitive?

The resources managed are often files. Files are not simply
read/written once, but sometimes they might be reopenend and
rewritten. Similarly for connections. Here you sometimes have to
guarantee that a file or a connection is closed. With the property
above you get that information easily.

If bindings of the cleanup are not shared with the outside,
programmers will find their own way of guessing. Like looking into
the list of open streams or some other unreliable way. They might
also use a global variable to effectively log the cleanups. So they
would reimplement part of the mechanism. That does not sound very
reliable to me.

>Even worse, a cut can now magically trigger a variable binding:
>?- setup_call_cleanup(true, (true;true), Det=surprise),
> writeln(Det), !, writeln(Det).
>_G835
>surprise
>Hardly desirable, and not something your average program analyzer
>would expect to happen!

That can happen. True. The sequence

... var(V), !, nonvar(V), ...

is even more to that point.

But which "average program analyzer" are you referring to? Average,
that means at least two, no?

>So my suggestions: if you want to have setup_call_cleanup/3 anyway,
>please at least get rid of these two nasty effects. This can be done
>by requiring that the Cleanup goal has only the instantiation at the
>end of Setup, and does not share variables with the context. The
>basic open/close examples all work with such a semantics.
>[I have put such a variant into ECLiPSe 6.0_102, lib(iso)]

That is great! Now we have 5 systems!

I only do not understand why

setup_call_cleanup(X=throw(ex), true, X).

succeeds with X = throw(ex) in ECLiPSe while I rather expected an
error due to the uncaught ex. How can you transmit bindings from
the Setup to the Cleanup?

>If you want a primitive to tell you about choicepoints, use one that
>doesn't spontaneously bind a variable, I can suggest something.
>But anyway, I don't think it belongs in the Prolog standard.

Here is another way of separating things:

One primitive that ignores constraints but shows the bindings. And
the other primitive allows to perform the complex - but absolutely
interesting operations you have in mind. I fear only that the second
primitive will not be accepted by many systems.

Compare just the weight of implementations! setup_call_cleanup/3 is
already complex - but in 2 weeks or so it can be implemented. (For
you it was probably much easier since you had all the mechanisms
already there.)

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 6:22:42 AM2/26/16
to
redo_call_cleanup/3 is the wrong name.. What should the name be?

Does any implementations contain a predicate like this already?




redo_call_cleanup(SetupRedo,Goal,Undo):-
oncely(SetupRedo),
catch(
(Goal,
(deterministic(true)
-> oncely(Undo)
; (oncely(Undo);(oncely(SetupRedo),fail)))),
E,
(oncely(Undo),throw(E))).


%% must(:Goal)
%
% like assertion/1 but runs even when in release mode
% thus it may create bindings and be nondet

must(Goal) :- Goal *-> true ; assertion_failed(fail, Goal).

%% must_det(:Goal)
%
% must leave no choice points

must_det(Level,Goal) :- Goal,
(deterministic(true) -> true ;
(print_message(Level, assertion_failed(deterministic, Goal)),
(member(Level,[informational,warn]) -> !
; assertion_failed(deterministic, Goal)))).


% Whenever I do a setup_* I wrap it in oncely/1
oncely(Goal):- must('$sig_atomic'(must_det(warn,Goal))).


/ *Example usages: */

with_prolog_flag(Flag,Value,Goal):-
current_prolog_flag(Flag,Was),
redo_call_cleanup(
set_prolog_flag(Flag,Value),
Goal,
set_prolog_flag(Flag,Was)).


% Trace that is not like once/1
no_trace(Goal):-
(
notrace((tracing,notrace))
->
('$leash'(OldL, OldL),
'$visible'(OldV, OldV),
redo_call_cleanup(
notrace((visible(-all),leash(-all),
leash(+exception),visible(+exception)))
Goal,
notrace(('$leash'(_, OldL),'$visible'(_, OldV),trace))))
;
Goal).


% Trace non interactively
with_trace_non_interactive(Goal):-
( tracing-> Undo=trace ; Undo = notrace ),

'$leash'(OldL, OldL),
'$visible'(OldV, OldV),
redo_call_cleanup(
notrace((visible(+all),leash(-all),leash(+exception),trace)),
Goal,
notrace(('$leash'(_, OldL),'$visible'(_, OldV),Undo)))


Jan Burse

unread,
Feb 26, 2016, 8:37:11 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> Does any implementations contain a predicate like this already?

I only see a lot of code, how about a little verbalization
of the specification of the predicate. I.e. the functional
and non-functional requirements in some words?

I guess you know what you want, but for an outsider
interpreting a pile of code without some specifications
is really hard. Similar feat to telepathy.

Bye

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 8:55:12 AM2/26/16
to
Sorry, just the very first predicate..

This one:

redo_call_cleanup(SetupRedo,Goal,Undo):-
oncely(SetupRedo),
catch(
(Goal,
(deterministic(true)
-> oncely(Undo)
; (oncely(Undo);(oncely(SetupRedo),fail)))),
E,
(oncely(Undo),throw(E))).


What should it be named?

Jan Burse

unread,
Feb 26, 2016, 9:00:36 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> What should it be named?

What does it do in your opinion? I mean it could
still be the case that your code is errorneous
relative to your own intentions.

So a description of your intention, aka the
specifiction, aka the requirements, would be
very helpful.

Bye

Jan Burse

unread,
Feb 26, 2016, 9:16:08 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> % Whenever I do a setup_* I wrap it in oncely/1
> oncely(Goal):- must('$sig_atomic'(must_det(warn,Goal))).

Usually in the implementation of setup_call_cleanup/3
the '吱ig_atomic' is not used this way. I can assure
you, it is NOT used this way.

It is used in the following way:

preparation_step(S, C) :-
sys_atomic((S,
sys_undo(C))).

See the spec of setup_call_cleanup/3 here:

Draft proposal for setup_call_cleanup/3
https://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup

Read clause a):

a) once(S) is executed while being protected from interrupts:
1. either once(S) is executed entirely and
the cleanup handler C is installed upon success;

So basically installing the cleanup handler C is also
protected from interrupts. This is not 100% clear from
the spec a), the wording is not extremely clear here.
But a little thought makes it clear.

The thinking goes as follows, why protecting once(S)
from interupts. Doesn't make any sense. Its the courtesy
of S itself whether it allows interrupts or not. For
example an open/3 might or might not allow interupts.

But what we want to assure, is that no interrupt appears
inbetween completing S and installing the cleanup handler
C. Otherwise the effect of S wouldn't follow an undo
operation C in all situations.

Namely in the code this position is protected:

S
, /* <<< here >>> */
sys_undo(C)

Or in the specification text this position is protected:

a) once(S) is executed while being protected from interrupts:
1. either once(S) is executed entirely
and /* <<< here >>> */
the cleanup handler C is installed upon success;

Using sys_atomic/1 is also a little cludge. For example
when S allows interrupts, and if we would like to
realize the second variant of clause a), namely:

a) once(S) is executed while being protected from interrupts:
2. or once(S) is interrupted by an implementation
dependent interrupt (7.12 note c). In this case,
once(S) does not leave an observable effect.

We cannot handle the second variant 2) if we use sys_atomic/1
which would mask all interrupts. But since the standard proposal
says that a.2) is implementation dependent, using sys_atomic/1
is still fine.

Bye

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 9:25:54 AM2/26/16
to
%% redo_call_cleanup(:SetupRedo,:Goal,:Undo)
%
% Calls :SetupRedo before calling :Goal each time.
%
% Whether or not the goal succeeds,
% it will call :Undo after each attempt.
%
% If an exception is thrown during goal,
% it will call :Undo before exception handlers


The intent is similar to setup_call_cleanup/3. But allows code to stay scoped to only the Goal phase. So one can write..

%% Only sets a prolog flag during Goal
%
with_prolog_flag(Flag,Value,Goal):-
current_prolog_flag(Flag,Was),
redo_call_cleanup(
set_prolog_flag(Flag,Value),
Goal,
set_prolog_flag(Flag,Was)).


So this code can work:

:- set_prolog_flag(occurs_check,false),
with_prolog_flag(occurs_check,error,member(r,[A,B,C])),
X=x(X).



If someone replaced redo_call_cleanup/3 in with_prolog_flag/3 with setup_call_cleanup/3 The call above would throw an error.

Jan Burse

unread,
Feb 26, 2016, 9:32:30 AM2/26/16
to
Oops,

> It is used in the following way:
>
> preparation_step(S, C) :-
> sys_atomic((S,
> sys_undo(C))).


First a small correction. For setup_call_cleanup/1
the preparation step reads differently.

I was confused since you used the variable name Undo.
But the variable should really be named Cleanup,
so that there is no confusion with the
SICStus undo/1:

Variable naming proposal:

C, Cleanup: A handler that is created like
a choice point
U, Undo: A handler that is created like
a variable trail entry

So the correct preparation step for setup_call_cleanup
is as follows. It is used in the following way:

preparation_step(S, C) :-
sys_atomic((S,
sys_cleanup(C))).

Also there is something to know about sys_cleanup/1.
There are further decisions how to execute the
cleanup handler C.

If you use oncely here to execute the cleanup
handler C later on, you might be on the right track.
But things are complicated here.

First why would you be on the right track. Well
the standard draft says:

A processor may restrict execution of C in an
implementation defined manner. For example, a
processor may restrict the handling of interrupts
within C or limit resource consumption.
https://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup

In Jekejeke Prolog sys_cleanup/1 invokes the
cleanup handler C with the some interrupts
masked out. But there is a further issue.

When we have multiple handlers C1, ..., Cn. i.e.
a stacking of setup_call_cleanup, then these
should be called as if they were a large
conjunction:

Cn, ..., C1

And here we have again the issue of composing
an atomic transaction from smaller atomic
transaction. We would need a protection from
interupts in the following code positions:

Cn
, /* <<< here >>> */
...
, /* <<< here >>> */
C1

And this is what lead me to the decision to
implement the execution of the cleanup handlers
natively. Since I hardly see how we could
in normal Prolog implement this transaction
composition.

So the cutter, which either removes choice
points or executes cleanup handlers, is responsible
for performing this transaction. And this cutter
doesn't listen to interrupts when it goes from
choice point to choice point backwards.

So this is the reason, why most likely you
won't never see a full Prolog implementation
of setup_call_cleanup/3. There will be always
some native residual, such as the cutter.

Even if you can break down (*) setup_call_cleanup/3
and bootstrap it from sys_atomic/1 and sys_cleanup/1
there is still a native residual, such as the
cutter, which I don't see yet how this could
be eliminate as well.

Bye

(*)
Here is the Jekejeke Prolog breakdown,
http://www.jekejeke.ch/idatab/doclet/blog/en/docs/05_run/05_frequent/standard/signal.html
but it still relies on a cutter, i.e. native
support from the Prolog system.




Jan Burse schrieb:
> Douglas R. Miles/LogicMoo schrieb:
>> % Whenever I do a setup_* I wrap it in oncely/1
>> oncely(Goal):- must('$sig_atomic'(must_det(warn,Goal))).
>
> Usually in the implementation of setup_call_cleanup/3
> the '§sig_atomic' is not used this way. I can assure

Jan Burse

unread,
Feb 26, 2016, 9:33:52 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> The intent is similar to setup_call_cleanup/3. But
> allows code to stay scoped to only the Goal phase.
> So one can write..

What do you mean by "stay scoped" and "Goal phase".
I have never heard about that.

Bye

Jan Burse

unread,
Feb 26, 2016, 9:35:54 AM2/26/16
to
I am asking since there is a little chance that you
are exploring the differences between Undo and Cleanup.
But there are also chances that you are exploring
something different.

Jan Burse schrieb:

Jan Burse

unread,
Feb 26, 2016, 9:37:10 AM2/26/16
to
As a last resort. Could you describe what you want
to do in DCG? Some people would do that, others
not. Just a matter what helps you getting a spec
defined.

Jan Burse schrieb:

Jan Burse

unread,
Feb 26, 2016, 9:40:04 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> %% redo_call_cleanup(:SetupRedo,:Goal,:Undo)
> %
> % Calls :SetupRedo before calling :Goal each time.
> %
> % Whether or not the goal succeeds,
> % it will call :Undo after each attempt.
> %
> % If an exception is thrown during goal,
> % it will call :Undo before exception handlers

redo_call_cleanup(S, G, C) :-
call_cleanup((S,G,C), C).

Jan Burse

unread,
Feb 26, 2016, 9:41:10 AM2/26/16
to
Jan Burse schrieb:
> redo_call_cleanup(S, G, C) :-
> call_cleanup((S,G,C), C).

Sorry:

redo_call_cleanup(S, G, C) :-
catch((S, G, C), _, (C, throw(E))).

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 9:43:20 AM2/26/16
to

%% setup_call_cleanup(Setup,Call,Cleanup)

I am unable to use setup_call_cleanup/3 since it does not call the cleanup code every time the Goal succeeds .
Nor does it call Setup prior to each iteration to a non determinations goal

setup_call_cleanup/3 only calls Setup once and only calls Undo once regardless to the number of times the goal succeeds.

redo_call_cleanup/3 will only act the same if a Goal was deterministic of throws an exception in the first iteration of a nondet Goa.




On Friday, February 26, 2016 at 6:16:08 AM UTC-8, Jan Burse wrote:
> Douglas R. Miles/LogicMoo schrieb:
> > % Whenever I do a setup_* I wrap it in oncely/1
> > oncely(Goal):- must('$sig_atomic'(must_det(warn,Goal))).
>
> Usually in the implementation of setup_call_cleanup/3
> the '吱ig_atomic' is not used this way. I can assure
> you, it is NOT used this way.
>

Correct '吱ig_atomic'/1 as used in setup_call_cleanup/3 cuts choicepoints in Setup.. oncely/1 in my case job is to ensure the SetupupRedo/Undo is coded sanely as well as protect from interrupts

Jan Burse

unread,
Feb 26, 2016, 9:54:52 AM2/26/16
to
Jan Burse schrieb:
>
> Sorry:
>
> redo_call_cleanup(S, G, C) :-
> catch((S, G, C), _, (C, throw(E))).

Ok, I understand your requirement now.
Its like debugger ports. Here is a
solution (for simplicity not making use
of once/1):

sys_in(S, _) :- S.
sys_in(_, C) :- C, fail.

sys_out(_, C) :- C.
sys_out(S, _) :- S, fail.

redo_call_cleanup(S, G, C) :-
sys_in(S, C),
catch(G, E, (C, throw(E))).
sys_out(S, C).

But be warned, catch/3 is also tricky. It
doesn't catch all exceptions. For example some
system exceptions are passed down, with
calling the exception handler.

This is useful for predicates such
as call_with_timeout/2, where one doesn't
want to bother the application programmer
with handling the timeout throw ball.

So if you want still C to be executed for
system execeptions you need to have access to
the low level catch of the Prolog system.

In Jekejeke Prolog the low level catch is
called sys_trap/3 (*), so the solution would be
as follows. I guess SWI-Prolog has also
a low level sys_trap/3:

redo_call_cleanup(S, G, C) :-
sys_in(S, C),
sys_trap(G, E, (C, throw(E))).
sys_out(S, C).

BTW: There are papers about sys_in/2 and
sys_out/2. Its a debugger instrumentation
pattern for Prolog.

Bye

(*)
sys_trap/3:
http://www.jekejeke.ch/idatab/doclet/blog/en/docs/05_run/01_kernel/builtin/control.html

Jan Burse

unread,
Feb 26, 2016, 9:58:24 AM2/26/16
to
Jan Burse schrieb:
> BTW: There are papers about sys_in/2 and
> sys_out/2. Its a debugger instrumentation
> pattern for Prolog.

A debugger could work as follows:

sys_in :- write('call ').
sys_in :- write('fail '), fail.

sys_out :- write('exit ').
sys_out :- write('redo '), fail.

Jan Burse

unread,
Feb 26, 2016, 9:59:22 AM2/26/16
to
Jan Burse schrieb:
> redo_call_cleanup(S, G, C) :-
> sys_in(S, C),
> sys_trap(G, E, (C, throw(E))).
> sys_out(S, C).

Mind the comma:

redo_call_cleanup(S, G, C) :-
sys_in(S, C),
sys_trap(G, E, (C, throw(E))),
sys_out(S, C).

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 10:00:41 AM2/26/16
to
redo_call_cleanup(S, G, C) :-
call_cleanup((S,G,C), C).


?- redo_call_cleanup1(writeln('starting one goal iteration'),(member(X,[1,2,3]),writeln(X)),writeln('ending one goal iteration')),fail.

starting one goal iteration
1
ending one goal iteration
2
ending one goal iteration
3
ending one goal iteration
ending one goal iteration




redo_call_cleanup2(S, G, C) :-
catch((S, G, C), _, (C, throw(E))).


?- redo_call_cleanup2(writeln('starting one goal iteration'),(member(X,[1,2,3]),writeln(X)),writeln('ending one goal iteration')),fail.
starting one goal iteration
1
ending one goal iteration
2
ending one goal iteration
3
ending one goal iteration





?- '$attvar':redo_call_cleanup(writeln('starting one goal iteration'),(member(X,[1,2,3]),writeln(X)),writeln('ending one goal iteration')),fail.

starting one goal iteration
1
ending one goal iteration
starting one goal iteration
2
ending one goal iteration
starting one goal iteration
3
ending one goal iteration
starting one goal iteration

Oops

My first version was incorrect it should have been ...



redo_call_cleanup(SetupRedo,Goal,Undo):-
oncely(SetupRedo),
catch(
(Goal,
(deterministic(true)
-> (oncely(Undo),!)
; (oncely(Undo);(oncely(SetupRedo),fail)))),
E,
(oncely(Undo),throw(E))).



Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 10:10:36 AM2/26/16
to

> starting one goal iteration
> 1
> ending one goal iteration
> starting one goal iteration
> 2
> ending one goal iteration
> starting one goal iteration
> 3
> ending one goal iteration
> starting one goal iteration
>
> Oops
>
> My first version was incorrect it should have been ...
>
>
>

redo_call_cleanup(Setup,Goal,Undo):-
oncely_clean(Setup),
catch(
(Goal, deterministic(Det),
(Det == true
-> oncely_clean(Undo)
; (oncely_clean(Undo);(oncely_clean(Setup),fail)))),
E, (oncely_clean(Undo),throw(E))).

Jan Burse

unread,
Feb 26, 2016, 10:16:51 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> redo_call_cleanup(Setup,Goal,Undo):-
> oncely_clean(Setup),
> catch(
> (Goal, deterministic(Det),
> (Det == true
> -> oncely_clean(Undo)
> ; (oncely_clean(Undo);(oncely_clean(Setup),fail)))),
> E, (oncely_clean(Undo),throw(E))).
>

If it works, its not stupid.
Maybe equivalent to the sys_in/sys_out thing.

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 10:18:16 AM2/26/16
to
oncely_clean(Goal):- '$sig_atomic'((Goal,assertion(deterministic(true))))->true;throw(failed_oncely_clean(Goal)).


So with this call


?- redo_call_cleanup(writeln('starting one goal iteration'),
(member(X,[1,2,3]),writeln(X)),
writeln('ending one goal iteration')),fail.


I'd like to see:

starting one goal iteration
1
ending one goal iteration
starting one goal iteration
2
ending one goal iteration
starting one goal iteration
3
ending one goal iteration
false.

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 10:21:04 AM2/26/16
to
> If it works, its not stupid. Maybe equivalent to the sys_in/sys_out thing.

Please describe this thing :)

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 10:40:22 AM2/26/16
to

I should have started out simply by asking this question:


?- <What Should I name this?>(
writeln('starting one goal iteration'),
(member(X,[1,2,3]),writeln(X)),
writeln('ending one goal iteration')),fail.


I'd like to see:

starting one goal iteration
1
ending one goal iteration
starting one goal iteration
2
ending one goal iteration
starting one goal iteration
3
ending one goal iteration
false.

Has any prolog implementation or library provided such a predicate?

Jan Burse

unread,
Feb 26, 2016, 10:44:53 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
> I'd like to see:
>
> starting one goal iteration
> 1
> ending one goal iteration
> starting one goal iteration
> 2
> ending one goal iteration
> starting one goal iteration
> 3
> ending one goal iteration
> false.

I can provide something else:

starting one goal iteration
1
ending one goal iteration
starting one goal iteration
2
ending one goal iteration
starting one goal iteration
3
ending one goal iteration
starting one goal iteration
ending one goal iteration
false.

Look see:

Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.16)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam

?- instrument((write(start), nl),
(member(X, [1,2,3]), write(X), nl),
(write(end), nl)).
start
1
end
X = 1 ;
start
2
end
X = 2 ;
start
3
end
X = 3 ;
start
end
false.

The definition, with spec, is:

/**
* instrument(S, G, C):
* The predicate instruments the goal G such that S
* is called upon call and redo, and that C is called
* upon exit, fail and exception.
*/
% instrument(+Goal, +Goal, +Goal)
instrument(S, G, C) :-
sys_in(S, C),
catch(G, E, (C, throw(E))),
sys_out(S, C).

sys_in(S, _) :- S.
sys_in(_, C) :- C, fail.

sys_out(_, C) :- C.
sys_out(S, _) :- S, fail.

Can also be used for a form of hypothetical reasoning:

:- op(700, xfx, =>).

(H => G) :-
instrument(assertz(H), G, retract(H)).

Here is an example use of hypothetical reasoning:

?- (tor(G1, G2) :- write('g1 '), call(G1);
write('g2 '), call(G2))
=> tor((write(foo), nl), (write(bar), nl)).

g1 foo
true ;
g2 bar
true ;
false.

But strangely, I am using nevertheless undo/1 sometimes
for this form of hypothetical reasoning. Probably a matter
of code reuse and my lazyness.

undo/1 is only needed for hypothesis in the continuation.
The instrument/3 doesn't work then, and an alternative
I don't know. Has to do with the cut.

Bye



Jan Burse

unread,
Feb 26, 2016, 10:47:14 AM2/26/16
to
Douglas R. Miles/LogicMoo schrieb:
>
> I'd like to see:
>
> starting one goal iteration
> 1
> ending one goal iteration
> starting one goal iteration
> 2
> ending one goal iteration
> starting one goal iteration
> 3
> ending one goal iteration
> false.

I don't think the above is possible. The
Prolog interpreter can not foresee that a
goal does fail, but obviously you want the
S to be called when the goal is entered.

Otherwise see other post, my naming
proposal is instrument/3.

Bye

Jan Burse

unread,
Feb 26, 2016, 10:55:59 AM2/26/16
to
Jan Burse schrieb:
> I don't think the above is possible. The
> Prolog interpreter can not foresee that a
> goal does fail, but obviously you want the
> S to be called when the goal is entered.

A possibly optimization would though be
to see that the goal was deterministically
solved, and than spare the S in the redo port.

I have some ideas how to incorporate this
into sys_in and sys_out. But its possibly
very tricky if not impossible. At least
some low level tricks like in the implementation
of (*->)/2 would be needed, i.e. some
additional manipulation of choice points.

This hints that it is probably very difficult
to implement in ordinary Prolog without
the support of some new builtins, to break
down the problem into more manageable pieces.

On the other hand a solution with undo/1
automatically detects determinism somehow.
I would need to elaborate on this, to explain
more about this feature of undo/1.

But undo/1 works on the variable trail,
which does not influence determinancy. A
further reason why I use undo/1 for hypothetical
reasoning sometimes, and not instrument/3.

Bye

Jan Burse

unread,
Feb 26, 2016, 11:32:05 AM2/26/16
to
Jan Burse schrieb:
> I have some ideas how to incorporate this
> into sys_in and sys_out. But its possibly
> very tricky if not impossible. At least
> some low level tricks like in the implementation
> of (*->)/2 would be needed, i.e. some
> additional manipulation of choice points.

Problem is you have to remove the choice
point of sys_in/1 when G is determinstic.
sys_in/1 is responsible for the fail port
besides the call port.

In your solution the fail port is somehow
missing. I read, so I guess its not really
correcting the state in case the last
solution of a goal G is non-deterministic.

redo_call_cleanup(Setup,Goal,Undo):-
oncely_clean(Setup),
catch(
(Goal, deterministic(Det),
(Det == true
-> oncely_clean(Undo)
; (oncely_clean(Undo);(oncely_clean(Setup),fail)))),
E, (oncely_clean(Undo),throw(E))).

That the last solution of a goal G might
be non-determinstic can quite often happen.
Try for example the following variant:

?- redo_call_cleanup(..., (member(X, [1,2,3]); fail), ...).

Try if you get a balanced start and end pair
for the above example. If Setup does effect
some state, you will see that your code
doesn't undo the effect.

But I guess something can be really done,
maybe a solution as follows. Here is one
more take of mine:

instrument(S, G, C) :-
sys_in(S, C),
catch(G, E, (C, throw(E))),
sys_out(S, C),
(/* G was determinstic */ -> !; true).

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 5:01:39 PM2/26/16
to
On Friday, February 26, 2016 at 8:32:05 AM UTC-8, Jan Burse wrote:
> Jan Burse schrieb:

> In your solution the fail port is somehow
> missing.

Oh oops, it went AWOL at some point. it should read as:


redo_call_cleanup(Setup,Goal,Undo):-
oncely_clean(Setup),
catch(
( Goal
*->
(deterministic(Det),
(Det == true
-> oncely_clean(Undo)
; (oncely_clean(Undo);(oncely_clean(Setup),fail)))))
; (oncely_clean(Undo),fail)),
E, (oncely_clean(Undo),throw(E))).


Thank you! it was effecting something I've been doing the last 5 hours :(

Douglas R. Miles/LogicMoo

unread,
Feb 26, 2016, 5:07:40 PM2/26/16
to
Sorry one more try:

redo_call_cleanup_av(Setup,Goal,Undo):-
oncely_clean(Setup),
catch(
((Goal, deterministic(Det))
*->
(Det == true
-> oncely_clean(Undo)
; (oncely_clean(Undo);(oncely_clean(Setup),fail)))
; (oncely_clean(Undo),fail)),
E, (oncely_clean(Undo),throw(E))).

Jan Burse

unread,
Feb 27, 2016, 6:09:30 AM2/27/16
to
Douglas R. Miles/LogicMoo schrieb:
> Sorry one more try:
>
> redo_call_cleanup_av(Setup,Goal,Undo):-
> oncely_clean(Setup),
> catch(
> ((Goal, deterministic(Det))
> *->
> (Det == true
> -> oncely_clean(Undo)
> ; (oncely_clean(Undo);(oncely_clean(Setup),fail)))
> ; (oncely_clean(Undo),fail)),
> E, (oncely_clean(Undo),throw(E))).

Here is the code transformation that leads to
sys_in/sys_out. First we see that your code
can be written without the oncely_clean/1
as follows, and using S, G, C variables:

redo_call_cleanup_av(S,G,C):-
S,
catch(
((G, deterministic(Det))
*->
(Det == true
-> C
; (C;S,fail)))
; (C,fail)),
E, (C,throw(E))).

Now observe the C;S,fail part, such a part
can be rewritten as:

sys_out(_,C) :- C.
sys_out(S,_) :- S, fail.

So we can replace the C;S,fail part by
sys_out(S,C) which gives:

redo_call_cleanup_av(S,G,C):-
S,
catch(
((G, deterministic(Det))
*->
(Det == true
-> C
; sys_out(S,C)))
; (C,fail)),
E, (C,throw(E))).

Now for simplicity lets look at it without
the determinism check and the error handling:

redo_call_cleanup_av(S,G,C):-
S,
((G, sys_out(S,C)) ; (C,fail))).

So basically you whant to branch into C,fail
when G, sys_out(S,C) fails. You can do this also
as follows:

redo_call_cleanup_av(S,G,C):-
(S; C, fail),
G,
sys_out(S,C).

The above code transformation works since if
G, sys_out(S,C) fails, it will go into the
redo port of the preceding sub goal. And
if the preceding is a disjunction, and the
left part fails it will go into the right part.

Now we can define:

sys_in(S, _) :- S.
sys_in(_, C) :- C, fail.

This sys_in(S,C) is equivalent to (S; C, fail).
So that finally we have the instrument/3
predicate again:

redo_call_cleanup_av(S,G,C):-
sys_in(S,C),
G,
sys_out(S,C).

Now add execption handling a non-determinism
again, you get what you want with probably less
use of inline disjunction and more use of clause
disjunction, i.e. the definition of sys_in/2 and
sys_out/2.

Currently I don't find the old reference which
even shows sys_in and sys_out. But you find the
pattern, with the (S; C, fail) moved to the
front also in this paper:

Mireille Ducass ea, Jacques Noy eb, 2000
Tracing Prolog programs by source
instrumentation is e…ient enough
https://pdfs.semanticscholar.org/2e24/73e838858273ff67790c9dbf58a4358a1917.pdf

Ulrich Neumerkels Suggestion for a predicate
name, without the S and C parameter, was
tr/1. See for example here:

Hooking into prolog's unification trace output

http://stackoverflow.com/questions/15713010/hooking-into-prologs-unification-trace-output

If you look closely at Mireille Ducass ea paper
you find another instrumentation point, namely
when the a clause ins invoked, when the head of
the clause unifies with the given goal. Could
happen multiple times for the same goal.

This is the perfect moment for executing goals
of freeze/2 and when/2 that have been woken up
by some variable instantiation during the unification
of the head with goal. Right?

Bye






Jan Burse

unread,
Feb 27, 2016, 6:11:57 AM2/27/16
to
Jan Burse schrieb:
> If you look closely at Mireille Ducass ea paper
> you find another instrumentation point, namely
> when the a clause ins invoked, when the head of
> the clause unifies with the given goal. Could
> happen multiple times for the same goal.

This instrumentation point cannot so easily
be wrapped into a meta predicate. It would
need to have access to the clauses and probably
amount to a meta interpreter.

Douglas R. Miles/LogicMoo

unread,
Feb 27, 2016, 12:11:34 PM2/27/16
to
On Saturday, February 27, 2016 at 3:09:30 AM UTC-8, Jan Burse wrote:

> This is the perfect moment for executing goals
> of freeze/2 and when/2 that have been woken up
> by some variable instantiation during the unification
> of the head with goal. Right?
>

Yes, makes sense.

Jan Burse

unread,
Mar 1, 2016, 4:11:47 AM3/1/16
to
But beware of neck cut optimization:

http://eclipseclp.org/doc/userman/umsroot115.html#toc144

Possibly not yet doing it right on my side, neck cut
turns a call of the woken goals into a once of the woken
goals, right?

Meaning we loose non-determinism, so for example the
expected behaviour for the following would be to
give only one solution:

p(the(_)) :- !.

?- freeze(X, member(X, [the(1), the(2), the(3)])), p(X).

Works correctly in SWI-Prolog:

Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.17)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam

?- freeze(X, member(X, [the(1), the(2), the(3)])), p(X).
X = the(1).

Not yet correct behaviour on my side:

Jekejeke Prolog 2, Development Environment 1.1.2
(c) 1985-2016, XLOG Technologies GmbH, Switzerland

?- freeze(X, member(X, [the(1), the(2), the(3)])), p(X).
X = the(1) ;
X = the(2) ;
X = the(3)

Still some work to do...

Bye

Douglas R. Miles/LogicMoo schrieb:

Jan Burse

unread,
Mar 1, 2016, 6:05:30 AM3/1/16
to
Jan Burse schrieb:
> Works correctly in SWI-Prolog:
>
> Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.17)
> Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam
>
> ?- freeze(X, member(X, [the(1), the(2), the(3)])), p(X).
> X = the(1).

Doesn't work in ECLiPSe, but is documented by ECLiPSe:

?- freeze(X, member(X, [the(1), the(2), the(3)])), p(X).

X = the(1)
Yes (0.00s cpu, solution 1, maybe more) ? ;

X = the(2)
Yes (0.00s cpu, solution 2, maybe more) ? ;

X = the(3)
Yes (0.00s cpu, solution 3)

Is related to the concept of an extended head:
http://eclipseclp.org/doc/userman/umsroot115.html#toc143

On the other hand if I do the following, I get the SWI-Prolog
behaviour as well in ECLiPSe:

?- abolish(p/1).

?- [user].
p(the(_)) :- true, !.

?- freeze(X, member(X, [the(1), the(2), the(3)])), p(X).

X = the(1)
Yes (0.00s cpu)

Why is this of concern? Well I was using the following definition
in the quines problem, there is a cut somewhere!

is_symbol([_|_]) :- !, fail.
is_symbol(_).

And yes there is freeze of a non-determinstic goal, namely the
lookup/3 is non-deterministic:

eval(S, E, R) -->
{freeze(S, is_symbol(S)), freeze(E, lookup(S, E, R))}.

lookup(S, [S-R|_], R).
lookup(S, [T-_|E], R) :-
neq(S, T),
freeze(E, lookup(S, E, R)).

So things could turn out differently on SWI-Prolog and
ECLiPSe prolog.

Bye


Jan Burse

unread,
Mar 6, 2016, 9:07:17 PM3/6/16
to
Douglas R. Miles/LogicMoo schrieb:
> redo_call_cleanup/3 is the wrong name.. What should the name be?

It could be that the following predicate does the same:

Ciao Prolog, try_finally/3:
Usage:try_finally(Start,Goal,End)

Calls initialization goal Start and then calls Goal Goal, but always
continues with the evaluation of End. If Goal is non-deterministic, in
case of backtracking Start is called again before redoing Goal.

Meta-predicate with arguments: try_finally(goal,goal,goal)
http://ciao-lang.org/docs/ciao/system_extra.html#try_finally/3

Douglas R. Miles/LogicMoo

unread,
Mar 10, 2016, 8:13:45 PM3/10/16
to
Jan B,

Good find.. thank you I like that.
I had finally named it locally

setup_call_cleaup_each(Setup,Call,Cleanup).
% Like setup_call_cleaup/3, however runs the Setup/Pair each iteration of Goal (adding _each at the end so I'd remember the difference)...


But like try_finnaly/3 better for a name since I dont like making up names. (Reminds me of teen years sitting around with my very *UN*talented music bands making up new names for ourselves rather than writting music)

In a way, I'd think though would have been arity 2 of this breakdown:

try_finally(G,F):- try_finally(true,G,F).


Next:

<What should this one be called?>(G,F):- try_finally(S,G,true).


Do you like try_finally/3 enough to add it to rename instrument/3 to try_finally/3?


j4n bur53

unread,
Mar 11, 2016, 4:40:46 AM3/11/16
to
Douglas R. Miles/LogicMoo schrieb:
>
> Do you like try_finally/3 enough to add it to rename instrument/3 to try_finally/3?

I don't provide instrument/3 right now
somewhere in the Prolog system.

Since it is a predicate than can be coded in
ordinary ISO Prolog.

As a rule only things that really need a genuine
native implementation go into the Prolog system.

Everything else goes into packages. And there
is a lot of freedom in naming.

Just saw you started already a package:
http://www.swi-prolog.org/pack/list?p=logicmoo_base

Looks interesting. The PFC is from Finin?

I should adopt the {}/1, since I am running(
currently into some problems. It is seen here.
Its one very big blob. Not sure whether it
would be better to split it up:

https://github.com/TeamSPoon/PrologMUD/blob/master/pack/logicmoo_base/t/examples/pfc/bc.pfc

But I don't know yet enough about it, to be sure that
what I see on the surface is what I expect
as a semantics. Also I see a (<-)/2 but in the main
page there is (==>)/2 and (<==>)/2.

I am using (<=)/2 for forward chaing rules, and (=>)/2
respectively (<=)/1 to start forward chaining, namely
forward chaining is started from hypothetical reasoning.

There is a relationship between try_finally and the
(=>)/2. It can be implemeted as follows:

C => G :-
compile_ref(C, R),
try_finally(recordz(R), G, erase(R)).

<= C :- ??

But try finally cannot be used for (<=)/1, the
continuation hypothessis assumption. More handy
are assume_ref/1 snd retire_ref/1.

C => G :-
compile_ref(C, R),
assume_ref(R),
call(G),
retire_ref(R).


<= C :-
compile_ref(C, R),
assume_ref(R).

But unless there isn't an undo/1, assume_ref/1 and
retire_ref/1 cannot be bootstrapped from recordz71
and erase/1. So still waiting for undo/1!

BTW, I alreardy posted in the SWI-Prolog mailing list
that undo/1 is found in Ciao Prolog. What is also
interesting ist the Andorra Model adoption in Ciao
Prolog, seen here:

Andorra execution
http://ciao-lang.org/docs/ciao/andorra_doc.html

I am thinking about enriching the when/2 predicate
accordingly. BTW: Ciao Prolog docu is much better readable
than any SWI-Prolog markup generated docu page. I always have
the feeling I bump my head when I see a colored bar spanning
the whole window width.

Bye

j4n bur53

unread,
Mar 11, 2016, 4:41:42 AM3/11/16
to
j4n bur53 schrieb:
> Its one very big blob. Not sure whether it
> would be better to split it up:

The logicmoo_base

j4n bur53

unread,
Mar 11, 2016, 4:44:22 AM3/11/16
to
j4n bur53 schrieb:
> BTW: Ciao Prolog docu is much better readable
> than any SWI-Prolog markup generated docu page. I always have
> the feeling I bump my head when I see a colored bar spanning
> the whole window width.

When its dark blue as in SWI-Prolog, and it is used
for headings. The light green boxes in Ciao Prolog
docu are not bad, and it is used for content. A light
yellow is also seen on SO.

Bye

Jan Burse

unread,
Mar 11, 2016, 5:43:46 AM3/11/16
to
Another trove of when/2 conditions:

ALE TRALE Co-routing:
http://www.ale.cs.toronto.edu/docs/man/ale_trale_man/ale_trale_man-node37.html

j4n bur53 schrieb:

Jan Burse

unread,
Mar 14, 2016, 6:04:18 AM3/14/16
to
Douglas R. Miles/LogicMoo schrieb:
> But like try_finnaly/3 better for a name since
> I dont like making up names. (Reminds me of teen
> years sitting around with my very *UN*talented music
> bands making up new names for ourselves rather
> than writting music)

I guess marketing departments all over the world would
like to hire you. :-)

> In a way, I'd think though would have been arity 2 of this breakdown:
>
> try_finally(G,F):- try_finally(true,G,F).

Over the last days I decided to probably make
try_finally/3 a system predicate.

Was redoing with_output_to/2 via memory streams and
thought making use of try_finally/3 to be handy. This
is for a new module charsio.

The only reason being that the following implementation
based on ISO prolog is not 100% correct:

/* try_finally/3 as a ordinary predicate */
try_finally(S, G, T) :-
( S; T, fail),
catch(G, E, ( T,
throw(E))),
( T; S, fail).

http://www.jekejeke.ch/idatab/doclet/blog/en/docs/10_dev/02_reference/testing/charsio.html?hash=try_finally/3

The problem is that the Jekejeke catch/3 lets usually automatically
pass by some system errors, this is explicitly coded here
for the upcoming release 1.1.3:

Upcoming catch/3 realization
http://www.jekejeke.ch/idatab/doclet/blog/docs/05_run/01_kernel/builtin/control.html?hash=catch/3

I see that SWI-Prolog isn't consistent here, maybe on purpose
or maybe by accident. For example a timeout from a
call_with_time_limit/2, can be catched, but an abort
cannot be catched:

?- call_with_time_limit(100,
catch((repeat, write(hello), nl, fail), E, true)).
[...]
hello
Action (h for help) ? abort

?- call_with_time_limit(1,
catch((repeat, write(hello), nl, fail), E, true)).
hello
E = time_limit_exceeded.

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
But I guess a try_finally/3 should always react on any execution
abortion. For example in the with_output_to/2 example it
should reset the output stream also in an abort.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

So the more correct implementation of try_finally/3 in
the case of Jekejeke Prolog would be, I guess there would
be also an equivalent implementation in SWI-Prolog:

/* try_finally/3 as an extra-ordinary predicate */
try_finally(S, G, T) :-
( S; T, fail),
sys_trap(G, E, ( T,
sys_raise(E))),
( T; S, fail).

Thus using the low level sys_trap/3 and sys_raise/1. The
use of sys_raise/1 is only a little bonus to speed up
any throw ball handling, since throw/1 does also extra
work such as filling the back trace. See link above.

What concerns naming, the move from try_finally/3 to
try_finally/2 is a little inconsistent with the move
from setup_call_cleanup/3 to call_cleanup/2.

The more logical variant with two arguments would
be something along finally/2. Or maybe start with
try_call_finally/3 and then move to call_finally/2.

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
So maybe go with:
try_call_finally/3: low level implementation that
also works for aborts (*)
call_finally/2: low level implementation that
also works for aborts (*)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Bye

(*) The analogue in Java would be for example the
exception class Throwable and in particular the
exception ThreadDeath:

https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadDeath.html

ThreadDeath is similar to a forceful abort of a
Prolog thread via the unsafe stop(). It can be handled
in Java code like any other exception. So both
codes will executed "some code" in case of a stop():

try {
} catch (ThreadDeath x) {
/* some code */
}

try {
} finally {
/* some code */
}

But stop() is usually not called on a Prolog thread
to abort it. Most likely an injection mechanism
is used, where at some safe points the thread death
is handled, probably combined with the Java interupt
flag. See also here for the SWI-Prolog variant of these
safe points:

http://www.swi-prolog.org/pldoc/doc_for?object=thread_signal/2

In Jekejeke signals are checked in call ports, and thus
FFI loops cannot be killed with the normal abort, here
one has to resort to the unsafe stop(). Therefore the
interpreter has two menu items "Kill" (unsafe) and
"Close Tab" (safe).

On the other hand the new continuation queue in Jekejeke
for suspended goals is checked in exit ports. Both checks
don't generate overhead, since they are basically volatile
variable null pointer checks without any explicit
synchronization. This seems to be really cheap.

Bye


Douglas R. Miles/LogicMoo

unread,
Mar 16, 2016, 7:29:46 AM3/16/16
to
Nice troves

Douglas R. Miles/LogicMoo

unread,
Mar 16, 2016, 7:35:01 AM3/16/16
to
> On Monday, March 14, 2016 at 3:04:18 AM UTC-7, Jan Burse wrote:
> vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
> So maybe go with:
> try_call_finally/3: low level implementation that
> also works for aborts (*)
> call_finally/2: low level implementation that
> also works for aborts (*)
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Good yeah, my brain couldn't accept call_finally/3

-Douglas

j4n bur53

unread,
Mar 23, 2016, 3:26:20 PM3/23/16
to
Name finding is also the new camouflage for
voting fraud and shenanigans on SO.

Personally I would more profit on good old style
Prolog community work suggested as here:
http://meta.stackoverflow.com/a/319603/502187

If ROK would not have moved to Erlang we would
have a couple of excellent documents on a variety
of newer aspects of Prolog.

But the new medias are tempting! And possibly
there is still a forming phase. Maybe some media
forms are still missing on the lazyweb?

Jan Burse schrieb:

j4n bur53

unread,
Mar 23, 2016, 4:08:08 PM3/23/16
to
j4n bur53 schrieb:
> Personally I would more profit on good old style
> Prolog community work suggested as here:
> http://meta.stackoverflow.com/a/319603/502187

I even dont know what to do if people post
longer and longer code snippets. See also here:

https://www.gitbook.com/book/tra38/essential-copying-and-pasting-from-stack-overflow/details

"Most code on Stack Overflow are "snippets". They do not
meet stsndards of originality and as result have no copy-
right protectuon. You are free to copy and paste them without
any worry about losing a lawsuit.

Some code on Stack Overflow are much longer than "snippets".
The therefore qualify for copyright protection. By default,
all content on Stack Overflow (including code) is licensed
under the CC-BY-SA 3.o license."

What then follows in the above link, I dont know
whether its a joke or not.


j4n bur53

unread,
Mar 23, 2016, 4:18:58 PM3/23/16
to
j4n bur53 schrieb:
> Some code on Stack Overflow are much longer than "snippets".
> They therefore qualify for copyright protection. By default,
> all content on Stack Overflow (including code) is licensed
> under the CC-BY-SA 3.o license.

Which allows commercial use as well by default. Right?

Douglas R. Miles/LogicMoo

unread,
Apr 17, 2016, 1:08:00 AM4/17/16
to
On Saturday, February 27, 2016 at 3:09:30 AM UTC-8, j4n bur53 wrote:
> Douglas R. Miles/LogicMoo schrieb:

Starting with this:

setup_call_cleanup_each(S,G,C):-
S,
catch(
((G, deterministic(Det))
*->
(Det == true
-> C
; (C;S,fail)))
; (C,fail)),
E, (C,throw(E))).


% This will work...

?- setup_call_cleanup_each(writeln(start),(between(1,3,X),writeln(X)), writeln(end)),fail.

start
1
end
start
2
end
start
3
end
false


But cant get this next to work:

?- setup_call_cleanup_each(asserta(scce0,REF),(between(1,3,X),writeln(X)),writeln(REF)),fail.

1
<clause>(0x3421210)
_G11412
ERROR: asserta/2: Uninstantiated argument expected, found <clause>(0x3421210) (2-nd argument)


this is because the first call to S is never undone :(

(I have a workarround to it.. @ https://github.com/TeamSPoon/PrologMUD/blob/master/pack/logicmoo_base/prolog/logicmoo/util/logicmoo_util_with_assertions.pl#L153-L157 <- but that code is only a rotten workaround)


Here is another testcase to show the effect I am going for:

?- dynamic(foo/0).
?- setup_call_cleanup_each(asserta(foo,Was), (member(Y,[1,2,3]),foo) ,erase(Was)), \+foo ,writeln(Y),fail.

1
2
3
false.

Douglas R. Miles/LogicMoo

unread,
Apr 17, 2016, 3:26:04 AM4/17/16
to
One question I have actually,
will reset/3 shift/1 help ?
(The prolog I am using has these two preds)

j4n bur53

unread,
Jun 27, 2016, 4:57:47 PM6/27/16
to
Beisdes the ambiguous case, we might also
ask whether we don't need new meta predicates
for the forceful deterministic case.

It is said that for example the following
programming pattern is an important use case for
a setup_call_cleanup/3 that can detect determinism:


** BEGIN ANTI PATTERN ** BEGIN ANTI PATTERN **

setup_call_cleanup(
open(File, write, Out),
<fill file>(Out),
close(Out)),
<use File>

** END ANTI PATTERN ** END ANTI PATTERN **

In my opinion it is an anti pattern, that should
be avoided. It will mostly not work in debugging,
since debuggers instrumentation often leaves choice points.

And it will mostly not work even if <fill file>
is logically deterministic, since it can be hard
to not have choice points in declarative code.

So what we need is a setup_call_cleanup/3 that doesn't
need a determinsim check, but forces the call to be
deterministic, and thats saves us some typing. Right?

Or should we just do:

setup_call_cleanup(
open(File, write, Out),
<fill file>(Out),
close(Out)), !, /* cut is not only for portability, but also
for debugging and as a safety measure
in case <fill file> leaves a choice point */
<use File>

Bye


Douglas R. Miles/LogicMoo schrieb:
> redo_call_cleanup/3 is the wrong name.. What should the name be?
>
> Does any implementations contain a predicate like this already?
>
>
>
>
> redo_call_cleanup(SetupRedo,Goal,Undo):-
> oncely(SetupRedo),
> catch(
> (Goal,
> (deterministic(true)
> -> oncely(Undo)
> ; (oncely(Undo);(oncely(SetupRedo),fail)))),
> E,
> (oncely(Undo),throw(E))).
>
>
> %% must(:Goal)
> %
> % like assertion/1 but runs even when in release mode
> % thus it may create bindings and be nondet
>
> must(Goal) :- Goal *-> true ; assertion_failed(fail, Goal).
>
> %% must_det(:Goal)
> %
> % must leave no choice points
>
> must_det(Level,Goal) :- Goal,
> (deterministic(true) -> true ;
> (print_message(Level, assertion_failed(deterministic, Goal)),
> (member(Level,[informational,warn]) -> !
> ; assertion_failed(deterministic, Goal)))).
>
>
> % Whenever I do a setup_* I wrap it in oncely/1
> oncely(Goal):- must('$sig_atomic'(must_det(warn,Goal))).
>
>
> / *Example usages: */
>
> with_prolog_flag(Flag,Value,Goal):-
> current_prolog_flag(Flag,Was),
> redo_call_cleanup(
> set_prolog_flag(Flag,Value),
> Goal,
> set_prolog_flag(Flag,Was)).
>
>
> % Trace that is not like once/1
> no_trace(Goal):-
> (
> notrace((tracing,notrace))
> ->
> ('$leash'(OldL, OldL),
> '$visible'(OldV, OldV),
> redo_call_cleanup(
> notrace((visible(-all),leash(-all),
> leash(+exception),visible(+exception)))
> Goal,
> notrace(('$leash'(_, OldL),'$visible'(_, OldV),trace))))
> ;
> Goal).
>
>
> % Trace non interactively
> with_trace_non_interactive(Goal):-
> ( tracing-> Undo=trace ; Undo = notrace ),
>
> '$leash'(OldL, OldL),
> '$visible'(OldV, OldV),
> redo_call_cleanup(
> notrace((visible(+all),leash(-all),leash(+exception),trace)),
> Goal,
> notrace(('$leash'(_, OldL),'$visible'(_, OldV),Undo)))
>
>

0 new messages