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

Final Disection of setup_call_cleanup/3

17 views
Skip to first unread message

Jan Burse

unread,
Sep 15, 2011, 10:25:13 AM9/15/11
to
Dear All,

I am currently introducing more behaviour for the cut in
my Prolog system. In particular choice points are allowed
to have call backs, which are called when choice points
are removed. Lets call these call backs "cutters".

One hope besides some better posibility to cleanup internal
storage is to be finally able to implement setup_call_cleanup/3.
Here is my new proposal of disecting setup_call_cleanup/3
into two primitive system predicates.

The primitive system predicates are:

sys_on_cleanup/1: It does the following:

On call: Creates a choice point with a special cutter.
Does nothing else.

On redo: Calls the argument once and then terminates
the argument search and fails.

On cut: Calls the argumet once and then terminates
the argument search.

sys_atomic_call/1: It does the following:

On call: Calls the argument for the first time with
signals masked out.
If the argument succeeds creates a choice
point with a special cutter and succeeds.
Otherwise fails.

On redo: Calls the argument one more time with
signals masked out.
If the argument fails remove the choice
point and fails. Otherwise succeeds.

On cut: Calls the cutters of the choice points
from the last success of the argument with
signals masked out.

The setup_call_cleanup can now be implemented straight
forward as follows:

setup_call_cleanup(S,G,C) :-
sys_atomic_call((once(S),sys_on_cleanup(C))), G.

Advantages/disadvantages of this approach:

Tail Recursion TRO:
-------------------

Prolog systems that allow tail recursion (or last
call optimization, or stack frame elimination) for
meta-calls will be able to apply this technique with
less effort to the above definition of setup_call_cleanup/3
for the goal G.

But before the goal there are two choice points, one
from sys_atomic_call/1 and one from sys_on_cleanup/1.
Eventually this could be reduced by the observation
that only once(S) is needed and by shifting the signal
masking out responsibility to the choice point of
sys_on_cleanup/1.

Of course Prolog systems that can inline choice points
could generate special code instead of using a
predicate definition with goal arguments, i.e. a meta-
predicate. Probably there is also something like
inlined cutters?

Variable Binding
----------------

A collected spec of setup_call_cleanup/1 (*) leaves
still some points open concerning the variable binding.
The problem can now be reduced to sys_on_cleanup/1.
Here we see its behaviour concerning variable binding:

?- sys_on_cleanup((write(G), nl)), G=2.
G = 2 ;
_1
No
?- sys_on_cleanup((write(G), nl)), G=2, !.
2
G = 2
?- sys_on_cleanup((write(G), nl)), G=2, throw(x).
2
G = 2
?- sys_on_cleanup((write(G), nl)), G=2.
G = 2
2

In the first case the binding of G is lost because of
backtracking. So write(G), nl is called when G=2 was
already undone. In the second case the cut causes the
cutter to be invoked without the G=2 already undone.
This makes sense since we might want to continue after
the cut.

The third and the last case on the other hand might be
disputable. Does then throw to invoke the cutters and
undo bindings at the same time, or does it like the cut
only invoke the cutters and the bindings are undone later.

Same question for the toplevel, does a termination of the
search, i.e. when no ";" is given, invoke the cutters and
unbinding simultaneously or in two phases. If the toplevel
uses a !, fail and the ! is implemented as in the second
case then two phases would be appropriate. But maybe a
!, fail is again a special case.

Maybe leaving the behaviour open as much as possible
gives the wides design space for Prolog systems
implementors. Most probably a variable communication
between the goal and the cleanup is not needed. Only
a variable communication between the setup and the
cleanup is probably needed. Could such a restriction
lead to a better sys_on_cleanup/1 besides a better
setup_call_cleanup/3?

Predicate Variations
--------------------

Suppose an end-user wants a setup_call_cleanup/3 that does
not mask out the signals, for example for better debugging.
By having the primitives this can be readily done:

setup_call_cleanup_nomask(S,G,C) :-
once(S), sys_on_cleanup(C), G.

Or supppose we want to profit from a redoable setup. This
can also be readily done by the two primitives:

setup_call_cleanup(S,G,C) :-
sys_atomic_call((S,sys_on_cleanup(C))), G.


Exception Handling
------------------

Would be inherited from the new primitives. During call
and redo sys_atomic_call/1 should rethrow exceptions.
A redo of sys_on_cleanup/1 will also rethrow exceptions.
On the other hand both cutters will not rethrow exceptions
but send them into nirvana. This is in accordance to
the collected spec.

A slight asymetry remains, a redo of sys_on_cleanup/1
will rethrow an exception whereas the cutter will not.
One way out would be to have chained exceptions. So that
for the following case:

setup_call_cleanup(true,throw(x),throw(y))

We would have an exception y that is caused by x. Not sure
whether chained exceptions are found in some Prolog system.
A chained exception has as implementation specific
information not only a back trace, but also an additional
exception, the "cause".

Somehow we now end up with setup_call_catcher_cleanup/4,
whereby we would need a variation on the primitive
sys_on_cleanup/1:

sys_on_cleanup/2: Same as sys_on_cleanup/1 but with
an additional first parameter for the reason. The
cutter will unify this parameter with the current
exception if the cutter is invoked from an exception.

We can then define:

setup_call_catcher_cleanup(S,G,E,C) :-
sys_atomic_call((once(S), sys_on_cleanup(E,C))), G.

But maybe an automatism that replaces the current exception
by any exception thrown by a cutter would be more useful. The
replacement should be done by setting the cause of the
cutter exception to the current exception.

This automatism would have a little disadvantage, since the
cutter cannot throw exceptions this way, that already have a
cause. Because the already existing cause would be overridden
and lost. So I am at loss with an automatism.

What else...?

Best Regards

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


Jan Burse

unread,
Sep 17, 2011, 10:46:08 AM9/17/11
to
Jan Burse schrieb:
> What else...?

An approach that is similar to the approach presented here
for setup_call_cleanup/3 can be used to implement catch/try
in a Prolog system that would not eat up native stack space
and where one catch would eat up one choice point.

The problem here is that the cutter needs to know whether
it is invoke inside the current call chain or by a goal
that is outside (to the left) of the current call chain.
Lets look at the following example:

u :- ..

s :- u
s :- ..

v :- ..

t :- v.
t :- ..

r :- s, t, throw(x).
r :- ..

q :- ..

p :- q, r
p :- ..

When throw(x) happens the cutter will cleanup the following
choice points in the following order. By true / false we
also indicate whether the choice point is in the call
chain leading of the throw that happened:

cleanup v false
cleanup t false
cleanup u false
cleanup s false
cleanup r true
cleanup q false
cleanup p true

Now how can we efficiently detect true/false? Searching
the full call chain each time a cleanup is called would
be inefficient. But we can device a kind of sliding
window algorithm. Assume that the choice points are
numbered. And that each frame in the call chain has
access to these numbers.

The sliding window starts with the current frame.
The window then moves along the call chain. Each
cutter does some steps of the move. A cutter has
therefore the following signature:

Frame cleanUp(Frame sliding_window)

And it does the following:

boolean in_call_chain;
for (;;) {
if (sliding_window.count < current_frame.count) {
in_call_chain = false;
break;
} else if (sliding_window.count == current_frame.count) {
in_call_chain = true;
break;
} else {
sliding_window=sliding_window.parent;
}
}

/* do something with the in_call_chain information */

return sliding_window;

I am currently using this sliding window approach
in the upcoming release 0.9.1 of Jekejeke Prolog to
do some additional freeing of memory resource for
defined predicates. The idea to use this also for
catch/try came only today. Up till now I was
using a processor invokation to implement catch/try
with is a little bit more expensive than a simple
choice point.

For catch/try the cutter would need to have yet
another signature. It would also need to return a
flag, that would express that the cutting should
be stopped, since a catch happened. This flag would
be only used by a choice point from a catch and
when the cutting was cause by a throw. And the
flag can only be set to true by a catch choice
point when in_call_chain is true.

Cutting by a cut, a super cut (a cut that goes
through naked variables, use to implement ;/2) or
a local cut (used to implement ->/2), the
stopping condition is different. And when the
search is terminated from the outside then the
sliding window is not used, and in_call_chain is
always assumed to be false. And in this case
the stopping condition is again different,
since all choice points inside the search context
are usually removed.

Any Prolog system that works with such a sliding
window approach for catch/try?

Would this explain the cryptic statement in ISO
section 7.8.10.1 (throw/1 use case description):

d) ... (3) the cutparent for the new
curract is less than CP ...

Is the ISO use case correct at all. It now reads:

c) if S empty then

d) else if (1) and (2) and (3) then

e) else replace CP and continue

Should we replace the CP only if (3) holds? Otherwise
we might not correctly detect in_call_chain? So
the correct use case would read:

c) if S empty then

d) else if (3) then

d.1) if (1) and (2) then

d.2) replace CP

c) continue

Is there a corrigenda for that? Or did I get something
totally wrong?

Best Regards


Jan Burse

unread,
Sep 17, 2011, 11:12:51 AM9/17/11
to
Jan Burse schrieb:
> Is there a corrigenda for that? Or did I get something
> totally wrong?

Oh, it was once on some todo list:

http://www.complang.tuwien.ac.at/ulrich/plstd/ST77.pdf

On Page 5:
7.8.10.1 e)
It does not seem right to replace CP by the
cut-parent of each execution state.

But it didn't make it into ISO/IEC 13211-1:1995/Cor 1:2007.

Oki, Doki.

Jan Burse

unread,
Sep 18, 2011, 10:32:10 PM9/18/11
to
Jan Burse schrieb:
> Predicate Variations
> --------------------

I guess the following predicate variations are
more interesting. Variantion 1 the goal is
cut-transparent. This can be done as follows:

setup_call_cleanup(S, G, C) :-
sys_atomic_call((once(S), sys_on_cleanup(C))),
G.
:- set_predicate_property(setup_call_cleanup/3, sys_noframe).

What should the meaning of the sys_noframe predicate
flag be? Here is a sketch in ISO core terminology:

sys_noframe: This flag indicates that body
the shares the cutparent of the call-site
when a clause of the predicate is invoked.

Without the flag the cut parent is increased. But
a cut transparent G is not necessary on the
top of the wish list. On the top of the wish list
could be the desired to cleanup resources as
early as possible. So lets try Variant 2:

setup_call_cleanup(S, G, C) :-
sys_atomic_call((once(S), sys_on_cleanup(C))),
current_prolog_flag(sys_choices, X),
G,
current_prolog_flag(sys_choices, Y),
(X==Y, !; true).

What should the meaning of the sys_choices Prolog
flag be? Here is a sketch in ISO core terminology:

sys_choices: The flag indicates the number
of current decorated sub goals on the
corresponding stack.

But we must think of an implementation of a
Prolog system that is not strictly ISO core (*).
Otherwise a goal of the form (A=1, !; A=2)
would leave a choice point and the above
optimization for setup_call_cleanup/3 with
this goal in the middle could not be applied.

The difference would be that the ! reduces
the stack of decorated sub goals not on redo
but already on call. This helps not only in
reducing the choices points in a goal early
on and letting the test X==Y succeed. But the
cut that follows the X==Y will also trigger
the cleanup C as desired.

Little disadvantage of the early clean up
solution here: The X==Y check needs two
place holders. But we can probably not help
it. Since sys_on_cleanup leaves a choice
point, we cannot use some deterministic/1
predicate that checks determinism up to the
bound of the start of the current clause.

Further disadvantage, we loose cut transparency,
since after the X==Y a non-local cut must follow
so as to awake the clean up. But we don't want
to see this non-local in the call-site of
setup_call_cleanup/3. So this time the
setup_call_cleanup/3 predicate does not have
the sys_noframe property.

Bye

(*)
I just see, there is a Note 2 in 7.8.4.1 which
exactly suggest to implement the cut as such.
But the use case for the cut does not say so.
But without Note 2 the sys_on_cleanup/2 would
also be much more delayed for cuts in the
continuation.

afa

unread,
Sep 20, 2011, 1:43:31 PM9/20/11
to
On Sep 17, 10:46 am, Jan Burse <janbu...@fastmail.fm> wrote:

I recently made setup_call_cleanup/3 in B-Prolog (version 7.6) to work
with cuts and exceptions. My approach is quite straightforward. The
call call_cleanup creates a special choice point frame which has an
argument for the cleanup term. The cleanup term is called whenever an
exception is thrown past the frame or a cut tries to cut off the
choice point. The overhead of the approach is that a deep cut needs to
crawl the choice point chain. It turned out that crawling does not
affect much the performance of normal Prolog programs since most cuts
are shallow not deep.

Cheers,
Neng-Fa

Jan Burse

unread,
Sep 20, 2011, 5:01:38 PM9/20/11
to
afa schrieb:
> I recently made setup_call_cleanup/3 in B-Prolog (version
> 7.6) to work with cuts and exceptions.

gr8, didn't notice, was only doing some testing
with version 7.5#8.

> It turned out that crawling does not affect much the
> performance of normal Prolog programs since most cuts
> are shallow not deep.

I agree. Actually one has to try hard to find examples
with a decent degree of cut crawl, i.e. number of
invoked cutters. Most often only the choice point of
the current clause is cut away, not more. Because the
goals before the cut have anyway been deterministic:

Here is a typical example (tak function):

fun(X,Y,Z,A) :-
X =< Y, !,
Z = A.
fun(X,Y,Z,A) :-
X1 is X - 1,
fun(X1,Y,Z,A1),
etc..

When I was looking for an example with "deep cuts" game
search came to my mind. So I tried tic-tac-toe as
an example for "deep cuts", i.e. doing min max search
via negation as failure:

% oposite(+Player,-Player).
oposite(o,x).
oposite(x,o).

% tie(+Board,+Player)
tie(X,P) :-
\+ move(X,P,_).

% best(+Board,+Player,-Board)
best(X,P,Y) :-
move(X,P,Y),
(win(Y,P) -> true;
oposite(P,Q),
\+ tie(Y,Q),
\+ best(Y,Q,_)).

But it seems that the above example does not have so deep
cuts, since best/3 has maximal 1 choice point, which is
cut away again and again by the last negation as failure
so that this number stays stable.

So in the end the above example did test for me other things:

- Advanced control constructs: \+/1, ;/2, ->/2, etc..

- Unification, i.e. patterns in facts, for example:

% move(+Board,+Player,-Board)
move([[-,B,C],[D,E,F],[G,H,I]],P,[[P,B,C],[D,E,F],[G,H,I]]).
move([[A,-,C],[D,E,F],[G,H,I]],P,[[A,P,C],[D,E,F],[G,H,I]]).
etc..

% win(+Board,+Player)
win([[P,P,P],[_,_,_],[_,_,_]],P).
win([[_,_,_],[P,P,P],[_,_,_]],P).
etc..

So I am still looking for a good "deep cut" example.

Best Regards

P.S.: One can check whether the game search works correct
in that the following should fail, i.e. no winning
strategy for the first player:

?- init(X), move(X,x,_).

"That scene from War Games"
http://www.youtube.com/watch?v=NHWjlCaIrQo

P.S.S.: Well a little bit more testing and/or thinking
would be necessary to see that the above code is correct,
I hope it is.



Ulrich Neumerkel

unread,
Sep 21, 2011, 5:26:06 AM9/21/11
to
afa <neng...@gmail.com> writes:

The overhead is limited by the number of choice points to be cut.
And each choice point can only be cut away once.

Seems B is making big progress towards
http://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup

Jan Wielemaker

unread,
Sep 21, 2011, 9:28:49 AM9/21/11
to
This is quite similar to how SWI-Prolog handles this. Only the goal is
stored elsewhere. The hardest bit was to be able to call arbitrary Prolog
code while walking the choice-points (which may cause GC or stack-shifts).

Cheers --- Jan

>
> Cheers,
> Neng-Fa
>

afa

unread,
Sep 21, 2011, 4:05:39 PM9/21/11
to
On Sep 21, 5:26 am, ulr...@mips.complang.tuwien.ac.at (Ulrich
Neumerkel) wrote:
Clearly I misunderstood the description. For example, for the query

?- setup_call_cleanup(true, X=1, X = 2).

B-Prolog gives 'no' while SWI-Prolog gives 'yes'. In the
specification, by "cleanup handler", do you mean a copy of cleanup?

Cheers,
Neng-Fa




Jan Wielemaker

unread,
Sep 21, 2011, 4:33:14 PM9/21/11
to
No, but ignores success/failure of the handler. Thus:

1 ?- setup_call_cleanup(true, true, fail).
true.

But not errors:

2 ?- setup_call_cleanup(true, true, throw(x)).
ERROR: Unhandled exception: Unknown message: x

Bindings are kept:

3 ?- setup_call_cleanup(true, true, X=4).
X = 4.

I think it makes sence if (1) would fail or if failure of the cleanup
handler is considered an error.

Cheers --- Jan

afa

unread,
Sep 21, 2011, 4:47:58 PM9/21/11
to
On Sep 21, 4:33 pm, Jan Wielemaker <j...@invalid.invalid> wrote:

>
> No, but ignores success/failure of the handler.  Thus:
>
I knew that:-)

Thanks!
Neng-Fa

Jan Burse

unread,
Sep 22, 2011, 3:23:36 AM9/22/11
to
Jan Wielemaker schrieb:
> I think it makes sence if (1) would fail or if failure of the cleanup
> handler is considered an error.

Would require to define a new error term or re-use some
sensible existing error term.

I took the following pick for a throw ball, out of the
possibility for easy reuse since it is already used
in my consult:

warning(system_error(directive_failed),_).

But seems to have two problems, first it is a warning
and not an error, second the error term belongs not to a known
error class:

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

When time permits I will choose a better throw ball.

Bye

Jan Burse

unread,
Sep 22, 2011, 6:23:56 AM9/22/11
to
Jan Burse schrieb:
> setup_call_cleanup(S, G, C) :-
> sys_atomic_call((once(S), sys_on_cleanup(C))),
> current_prolog_flag(sys_choices, X),
> G,
> current_prolog_flag(sys_choices, Y),
> (X==Y, !; true).

BTW: Concering a flag sys_choices, there is a
little challenge in itself, namely to have a
consistent value of the flag.

Problem can be +1 -1 difference during redo
and or cleanup. Or even a number that is updated
too late.

Here is some testing:

?- (X=1;X=2), numchk.
call number=1
X = 1 ;
redo number=1
X = 1
cleanup number=1

?- (X=1;X=2), numchk, !.
call number=1
cleanup number=1
X = 1

?- (X=1;X=2), numchk, throw(x).
call number=1
cleanup number=1
Unknown exception: x

Now if we call it from within a catch/3. What
will happen? It depends on how catch/3 is
implemented, i.e. whether it creates a choice
point before or after the goal is created.

If the choice point is created after the goal
we should the same numchk results as already
above for:

?- (X=1;X=2), catch(numchk, _, fail).
etc..

And for:

?- catch(((X=1;X=2), numchk), _, fail).
etc..

Best Regards

Jan Wielemaker

unread,
Sep 22, 2011, 7:31:57 AM9/22/11
to
On 2011-09-22, Jan Burse <janb...@fastmail.fm> wrote:
> Jan Burse schrieb:
>> setup_call_cleanup(S, G, C) :-
>> sys_atomic_call((once(S), sys_on_cleanup(C))),
>> current_prolog_flag(sys_choices, X),
>> G,
>> current_prolog_flag(sys_choices, Y),
>> (X==Y, !; true).
>
> BTW: Concering a flag sys_choices, there is a
> little challenge in itself, namely to have a
> consistent value of the flag.

I'm wondering why you would like to disect your implementation
of setup_call_cleanup/3 in public. I don't think anyone would
like to standardize access to the current choice-points and also
your cleanup primitives are results of implementation choices
you made. These might be the perfect solution to your Prolog,
but there might be other preferred solutions in another Prolog
implementation.

This forum is fine for discussion how to implement such a beast
in general terms or even how to get some nasty detail right.

This forum is also fine for discussing whether setup_call_cleanup/3
is a good idea in the first place. E.g. in an earlier discussion
Joachim disagreed because he thinks that the solution to the problem
setup_call_cleanup/3 addresses should be in GC. In part, I agree with
him. It is not hard to find cases where this rather procedural approach
to avoid leaking resources fails. The problem is that the a GC based
approach gives little control to make sure resources are closed at some
point in the execution and some applications have that requirement.

Cheers --- Jan

Jan Burse

unread,
Sep 22, 2011, 8:11:07 AM9/22/11
to
Jan Wielemaker schrieb:

> I don't think anyone would like to standardize access to
> the current choice-points and also your cleanup primitives
> are results of implementation choices you made.

I don't reflect on access to the current choice-points, although
this could also be a good idea. Just compare with ancestors/1,
which allowed access to the call chain, one could also do
something like choice_points/1.

I only reflected on access to the count of the current
choice-points. As an alternative to the deterministic/1
predicate.

Currently I am printing out the number via statistics/0
and I am also allow retrieving it via statistics/1.
Its very interesting to see the number go up when
debug/0 is on... Sniff.

The problem is that the following would not work:

setup_call_cleanup(S, G, C) :-
sys_atomic_call((once(S), sys_on_cleanup(C))),

G,
deterministic(X),
(X==true, !; true).

The above would not work, since deterministic cannot
return true, even when G deterministically succeeds,
since there is the choice point from sys_on_cleanup/1.
So that is why I came up with the following solution:

setup_call_cleanup(S, G, C) :-
sys_atomic_call((once(S), sys_on_cleanup(C))),
current_prolog_flag(sys_choices, X),
G,
current_prolog_flag(sys_choices, Y),
(X==Y, !; true).

> This forum is fine for ...

This wouldn't be the first time that somebody tries to
speak for a collective. Please feel free to continue and
divert from the subject matter.

Bye

Jan Burse

unread,
Sep 22, 2011, 8:20:14 AM9/22/11
to
Jan Wielemaker schrieb:
> ng whether setup_call_cleanup/3
> is a good idea in the first place. E.g. in an earlier discussion
> Joachim disagreed because he thinks that the solution to the problem
> setup_call_cleanup/3 addresses should be in GC.

Java has this finalizer() thing. The experience with
finalizer() is mixed. For example Cliff Click from
Azul calls them evil:

http://www.youtube.com/watch?v=uL2D3qzHtqY

I guess both would be needed, setup_call_cleanup/3
and finalizers. I cannot use finalizers when the
resource is for example a mutex. I want an immediate
effect.

But if the resource is a file handle, well a finalizer
might be feasible. I don't know what proposals for
finalizers are around. But I guess hooking into
a reference type with a skeletonized goal term
would be one way to go.

Best Regards

Jan Burse

unread,
Sep 22, 2011, 8:25:47 AM9/22/11
to
Jan Burse schrieb:
> I want an immediate effect.

Well it might come in disguise of with_mutex/2,
but I guess it can be bootstrapped via
setup_call_cleanup/3 and some operations on the
mutex.

Ulrich Neumerkel

unread,
Sep 22, 2011, 8:20:06 AM9/22/11
to
afa <neng...@gmail.com> writes:
>On Sep 21, 5:26=A0am, ulr...@mips.complang.tuwien.ac.at (Ulrich Neumerkel) wrote:
>> Seems B is making big progress towards
>> http://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup
>
>Clearly I misunderstood the description. For example, for the query
>
>?- setup_call_cleanup(true, X=1, X = 2).
>
>B-Prolog gives 'no' while SWI-Prolog gives 'yes'. In the
>specification, by "cleanup handler", do you mean a copy of cleanup?

post-N215 reads:

7.8.11.1 Description

setup_call_cleanup(S, G, C) is true iff once(S), call(G) is true.


To take your example - which is the 5th in 7.8.11.4,

once(true), call(X=1) is true.

Thus setup_call_cleanup(true, X=1, X = 2). has to be true as well.
I cannot see an ambiguity here. Do you?

What is indeed a bit irritating is that while the cleanup C does
not influence success or failure of setup_call_cleanup/3, C is still
able to produce substitutions as in

setup_call_cleanup(true, true, X = 1).

There is no copy of C involved. See 7.8.11.1 d:

d) C is called as once(C). Failure of C is ignored. ...

... C shares bindings and variables with S, with G and with the continuation.


There are cleanups where you do need access to other variables.
Another case is to be able to indicate that the cleanup has been performed.



Ulrich Neumerkel

unread,
Sep 22, 2011, 8:39:30 AM9/22/11
to
ulr...@mips.complang.tuwien.ac.at (Ulrich Neumerkel) writes:
>What is indeed a bit irritating is that while the cleanup C does
>not influence success or failure of setup_call_cleanup/3, C is still
>able to produce substitutions as in
>
> setup_call_cleanup(true, true, X = 1).

This is a bit misleading. The reason is that while all implementations
I know of produce the substitution as expected, an implementation
does not have to detect determinism. So the cleanup handler may
be executed upon success of true, but does not have to.

Jan Burse

unread,
Sep 22, 2011, 9:00:41 AM9/22/11
to
Ulrich Neumerkel schrieb:
> d) C is called as once(C). Failure of C is ignored. ...
>
> ... C shares bindings and variables with S, with G and with the continuation.

I guess only in reading mode and not in persistent
writing mode, persisting over the call of
setup_call_cleanup/3.

The cleanup C can establish bindings, but it is rather
called as follows:

call_and_fail(C) :- C, !, fail.
call_and_fail(_) :- fail.

At least I am assuming that. So if you have a cleanup
X = 2, it can bind X to 2, provided X is not yet
instantiated, but the binding will be undone when
the cleanup has finished.

Maybe the post-N215 needs a little polish here. I am
not sure, depends on what implementations you want to
reflect.

If there are really implementations around, that
performe a once, then the call_and_fail could
be mentioned as an alternative.

My view of a cleanup sweep is as follows:

cleanup_sweep :-
(choice_points(P), get_cutter(P,C), call_and_fail(C); true).

Choice_points/1 would be required to enumerate
from youngest to oldest choice point. The above
cleanup sweep would be invoked when the Prolog
processor search is terminated. Cut and exceptions
do sweeps that terminate earlier on.

But the processor termination shares with exceptions
that it also undoes binding. Whereby the Cut would
not. Processor termination, i.e. the processor as
a component that is used from the outside, via some
language binding is not yet found in iso.

Besides Processor termination, one could also define
Processor cleanup, which would only do the choice
point sweep, but keep the bindings. This all gives
a nice API.

Best Regards

Jan Burse

unread,
Sep 22, 2011, 9:04:14 AM9/22/11
to
Jan Burse schrieb:
>
> The cleanup C can establish bindings, but it is rather
> called as follows:
>
> call_and_fail(C) :- C, !, fail.
> call_and_fail(_) :- fail.

Or if we throw in also exception suppression, then it
would be called as follows:

call_and_fail(C) :- catch(C,_,fail), !, fail.
call_and_fail(_) :- fail.

Ulrich Neumerkel

unread,
Sep 22, 2011, 9:08:08 AM9/22/11
to
Jan Burse <janb...@fastmail.fm> writes:
>Ulrich Neumerkel schrieb:
>> d) C is called as once(C). Failure of C is ignored. ...
>>
>> ... C shares bindings and variables with S, with G and with the continuation.
>
>I guess only in reading mode and not in persistent
>writing mode, persisting over the call of
>setup_call_cleanup/3.
>
>The cleanup C can establish bindings, but it is rather
>called as follows:
>
> call_and_fail(C) :- C, !, fail.
> call_and_fail(_) :- fail.
>
>At least I am assuming that. So if you have a cleanup
>X = 2, it can bind X to 2, provided X is not yet
>instantiated, but the binding will be undone when
>the cleanup has finished.
>
>Maybe the post-N215 needs a little polish here. I am
>not sure, depends on what implementations you want to
>reflect.

The bindings of C are required and needed in several uses. The most
obvious use is to detect determinism of a goal:

setup_call_cleanup(true, Goal, Det=yes)

Which is indeed the most procedural usage.

Other uses are when you want to construct a term lazily, but
after cleanup the term must look like a normal term. In that case,
the remaining variables are unified by the cleanup.

>If there are really implementations around, that
>performe a once, then the call_and_fail could
>be mentioned as an alternative.

It is not clear what you are discussing now: The sharing, or
having C executed as once(C0). Two different issues.

(I don't discuss the code you post as it seems to be very
specific to your implementation. It would help to discuss
properties observed, actual test code, as this would ve
of interest to others, too.)

Jan Burse

unread,
Sep 22, 2011, 9:51:50 AM9/22/11
to
Ulrich Neumerkel schrieb:
> (I don't discuss the code you post as it seems to
> be very specific to your implementation. It would help
> to discuss

Well this is the point of the whole exercise, if you did
not yet notice, to discuss a reference model. But it is
not code from my implementation. My implementation is in
Java, and I did not post any Java here. This would involve
posting details of the current cutter mechanism in
Java code.

Instead I am going into lengths to post high level
specs in Prolog itself. For example call_and_fail/1
and cleanup_sweep/0. I guess you did not have any problems
with understanding them, they were in pure Prolog.
Or did you?

> The bindings of C are required and needed in several uses. The most
> obvious use is to detect determinism of a goal:
>
> setup_call_cleanup(true, Goal, Det=yes)
>
> Which is indeed the most procedural usage.

Probably this is an example which can anyway not be sustained.
Since it depends on the moment when the clean up is called.
But yes this would obvisously only work with the once/1
semantincs and not with the call_and_fail/1 semantics.

> Other uses are when you want to construct a term lazily,
> but after cleanup the term must look like a normal term.
> In that case, the remaining variables are unified by the
> cleanup.

Can you say more about this example? I don't see what
you mean, how lazily now comes into play. Since
setup_call_cleanup deals more with events in the
continuation. How is lazy and events in the continuation
related.

Would this mean that you build up cleanups and then
fail so that the cleanups do their bindings? Very
interesting idea. Here the moment of the clean up
is not relevant, so the example can probably be
sustained.

But why exactly?

But I think the once semantics is conflicting. Because
you can then not decide anymore between failure and
failure with some bindings done. The interpreter would
need to show something like:

No, but cleanup has X1 = T1, .., Xn = Tn

Or when the event was an exception:

Exception x, but cleanup has X1 = T1, .., Xn = Tn

Namely if we do:

setup_call_cleanup(true, ..
setup_call_cleanup(true, fail, Xn = Tn) .., Xn = Tn)

Respectively:

setup_call_cleanup(true, ..
setup_call_cleanup(true, throw(x), Xn = Tn) .., Xn = Tn)

So the event can only be a cut or determinism, otherwise
we cannot make use of the cleanup result. But this looks
overly complicated. But I have an idea what it could
be about. Collecting some constraints information or
completing some constraints?

Ok, interesting.

Bye



Jan Burse

unread,
Sep 22, 2011, 10:04:25 AM9/22/11
to
Jan Burse schrieb:

> So the event can only be a cut or determinism, otherwise
> we cannot make use of the cleanup result.

The post-N215 could say it is once/1 when coming
from cut or determinism and it is call_and_fail/1
when coming from fail or exception.

So that no uncessary bindings are compiled in fail
or exception.

Or the better the once/1 semantics is something
only for setup_call_catcher_cleanup/4, and the default
for setup_call_cleanup/3 is call_and_fail/1.

And the beast setup_call_catcher_cleanup/4
needs also be dealt with in ISO!

So:
setup_call_cleanup/3 would do call_and_fail/1

setup_call_catcher_cleanup/3 would do once/1,
but the cleanup goal can decide whether it
will turn the once/1 into a call_and_fail/1.

Best Regards

Ulrich Neumerkel

unread,
Sep 22, 2011, 10:43:21 AM9/22/11
to
Jan Burse <janb...@fastmail.fm> writes:
>Ulrich Neumerkel schrieb:
>Instead I am going into lengths to post high level
>specs in Prolog itself. For example call_and_fail/1
>and cleanup_sweep/0. I guess you did not have any problems
>with understanding them, they were in pure Prolog.
>Or did you?

I do not understand cleanup_sweep/0 nor get_cutter/2. Your
code may look high-level to you. In any case, it is not pure
Prolog. Also sys_atomic_call/1, sys_on_cleanup/1 mentioned in
other posts may be clear to you, not to me. I suspect they
impose a lot of restrictions to an implementation. These
restrictions may or may not be defeasable.

>> setup_call_cleanup(true, Goal, Det=yes)
>>
>> Which is indeed the most procedural usage.
>
>Probably this is an example which can anyway not be sustained.
>Since it depends on the moment when the clean up is called.
>But yes this would obvisously only work with the once/1
>semantincs and not with the call_and_fail/1 semantics.

Detecting determinism is one issue, but getting the
feedback that the cleanup has actually been executed, and
thus the resource is freed, is related and maybe even
more interesting. Often, this is written like:

setup_call_cleanup(open(S),..., ( close(S), Closed = yes ) ).

Idioms like that are in use since end 1997.

There are more uses. The things you want to perform before
the resource is no longer available.

Ulrich Neumerkel

unread,
Sep 22, 2011, 11:31:32 AM9/22/11
to
Jan Burse <janb...@fastmail.fm> writes:
>Jan Burse schrieb:
>> So the event can only be a cut or determinism, otherwise
>> we cannot make use of the cleanup result.
>
>The post-N215 could say it is once/1 when coming
>from cut or determinism and it is call_and_fail/1
>when coming from fail or exception.
>
>So that no uncessary bindings are compiled in fail
>or exception.

You are free to implement things in that way. In fact, one
of the goals of post-N15 is to have a specification that
covers all implementation dependent optimizations.

>And the beast setup_call_catcher_cleanup/4
>needs also be dealt with in ISO!

A can of worms - with even more complex issues to
deal with.

Jan Burse

unread,
Sep 22, 2011, 12:23:04 PM9/22/11
to
Ulrich Neumerkel schrieb:

> I do not understand cleanup_sweep/0 nor get_cutter/2.

This is not my problem.

> Often, this is written like:
>
> setup_call_cleanup(open(S),..., ( close(S), Closed = yes ) ).
>
> Idioms like that are in use since end 1997.

The value of Closed can only be used after cut or
determinism. So maybe the concept of this kind of
usage of a cleanup is flawed, and it would be money
invested better in some exception handling of
cleanup. Things allong the following lines:

- Cleanup is not allowed to fail.

- Cleanup is not allowed to do bindings.

- Cleanup exceptions are not suppressed.

- Cleanup exceptions are accumulated and not overshadowed.

- What else...?

Only the more general setup_call_catcher_cleanup/4 is
allowed to divert from these rules. So setup_call_cleanup/3
would be a specialized form of setup_call_cleanup/3
for the average user with a lot of safety built-in.

This could be appropriate for 2012! Yes we can.

Best Regards


Jan Burse

unread,
Sep 22, 2011, 12:41:49 PM9/22/11
to
Ulrich Neumerkel schrieb:
> setup_call_cleanup(open(S),..., ( close(S), Closed = yes ) ).
> Idioms like that are in use since end 1997.

All the setup_call_cleanup/3 examples on the following
page don't use this idiom:

SWI-Prolog C-library
6 Socket library
http://www.swi-prolog.org/pldoc/doc_for?object=section%282,%276%27,swi%28%27/doc/packages/clib.html%27%29%29

But there is also an example of the usage of
setup_call_catcher_cleanup/4 which I don't understand fully:

create_client(Host, Port) :-
setup_call_catcher_cleanup(tcp_socket(Socket),
tcp_connect(Socket, Host:Port),
exception(_),
tcp_close_socket(Socket)),
setup_call_cleanup(tcp_open_socket(Socket, In, Out),
chat_to_server(In, Out),
close_connection(In, Out)).

Jan Burse schrieb:
> Ulrich Neumerkel schrieb:
>> I do not understand cleanup_sweep/0 nor get_cutter/2.
> This is not my problem.

Ok, acordingly it would be my problem that I don't understand
the above code snippet. I will check back in a while,
maybe I understand it then.

Can the snippet be solved alone with setup_call_cleanup/3.
Otherwise it would maybe make a case for setup_call_catcher_cleanup/4
in the ISO proposal.

Actually I am lost how important setup_call_catcher_cleanup/4 in
practice is. From an implementation point of view it looks to
me that the additional overhead for setup_call_catcher_cleanup/4
is not that big. One would typically first implement
setup_call_catcher_cleanup/4 and then simply define:

/* without safety */
setup_call_cleanup(S,G,C) :-
setup_call_catcher_cleanup(S,G,_,C).

Or when we want more safety, it depends on whether
setup_call_catcher_cleanup/4 has already some safety. Lets assume
setup_call_catcher_cleanup/4 does already exception accumulation
and not suppress exceptions, then we would only need to add
failure check and unbinding. So the definition would be:

/* with safety */
setup_call_cleanup(S,G,C) :-
setup_call_catcher_cleanup(S,G,_,call_checked(C)).

call_checked(G) :- gcc(G), !.
call_checked(_) :- sys_throw_error(system_error(cleanup_failed))).

gcc(G) :- \+ \+ G.

Bye

Jan Wielemaker

unread,
Sep 22, 2011, 4:12:58 PM9/22/11
to
On 2011-09-22, Jan Burse <janb...@fastmail.fm> wrote:
> Ulrich Neumerkel schrieb:
>> setup_call_cleanup(open(S),..., ( close(S), Closed = yes ) ).
>> Idioms like that are in use since end 1997.
>
> All the setup_call_cleanup/3 examples on the following
> page don't use this idiom:
>
> SWI-Prolog C-library
> 6 Socket library
> http://www.swi-prolog.org/pldoc/doc_for?object=section%282,%276%27,swi%28%27/doc/packages/clib.html%27%29%29
>
> But there is also an example of the usage of
> setup_call_catcher_cleanup/4 which I don't understand fully:
>
> create_client(Host, Port) :-
> setup_call_catcher_cleanup(tcp_socket(Socket),
> tcp_connect(Socket, Host:Port),
> exception(_),
> tcp_close_socket(Socket)),

This says: create a socket and connect it. If connect raises an error,
close the socket, else keep it. Note that tcp_connect/2 cannot fail;
it succeeds or raises an error. Thus, you can do

...,
tcp_socket(Socket),
catch(tcp_connect(Socket, Host:Port), E,
( close(Socket),
throw(E))),

Which is almost the same, except that a timeout or error between
a succeeding tcp_socket and the start of tcp_connect will leave
the socket open. setup_call_catcher_cleanup/4 should be safe.

Cheers --- Jan

Ulrich Neumerkel

unread,
Sep 22, 2011, 4:23:06 PM9/22/11
to
Jan Wielemaker <j...@invalid.invalid> writes:
>On 2011-09-22, Jan Burse <janb...@fastmail.fm> wrote:
>> Ulrich Neumerkel schrieb:
>>> setup_call_cleanup(open(S),..., ( close(S), Closed = yes ) ).
>>> Idioms like that are in use since end 1997.
>>
>> All the setup_call_cleanup/3 examples on the following
>> page don't use this idiom:
>>...
>
>This says: create a socket and connect it. If connect raises an error,
>close the socket, else keep it. Note that tcp_connect/2 cannot fail;
>it succeeds or raises an error. Thus, you can do

Most of the code which uses setup_call_cleanup/3 assumes that
the goal (second argument) will not be nondetermiate. That is, if
it is not - maybe due to an accidental choice point - say by
wrapping ( Goal ; fail ) around Goal the code would not run correctly.


Cases in SWI where the second argument is nondetermiate on purpose are
very rare.

library(pio), library(tipc) come to mind. I think that's it. Other
systems use the mechanism for debuggers.

Jan Wielemaker

unread,
Sep 23, 2011, 3:44:35 AM9/23/11
to

I fully agree with you, although the cases where you want cleanup with
non-determinism is exactly the reason for which you need setup_call_cleanup/3.
I.e., in the deterministic case it is merely syntactic sugar.

Otherwise, I do not see the relation to my statement. I was saying that
setup_call_catcher_cleanup/4 can be used as syntactic sugar for the case
where you only want cleanup if the main goal raises an error. In addition
to being easier to read than the corresponding catch-cleanup-rethrow
alternative, it has the advantage of signal safety (I agree if
you say this is not an ISO issue).

Cheers --- Jan

0 new messages