Is try_call_finally/3 the same as setup_call_cleanup_each/3?

Since bilateral dialogical excursions are frowned upon by the SWI-Prolog discourse moderators, opening a new thread. Can you post your question again logicmoo? This was it, right?

I don’t know, maybe. But I rather expect undo/1 is not made for this use case.

You can implement setup_call_cleanup_each/3, with two call_cleanup/2 calls.
The sys_mask/1 and sys_cleanup/1 combo, is an inlined call_cleanup/2 in my Prolog system:

try_call_finally(A, G, C) :-
   sys_mask((must(A), sys_cleanup(C))),
   current_prolog_flag(sys_choices, X),
   G,
   current_prolog_flag(sys_choices, Y),
   (X == Y, !; sys_mask((must(C), sys_cleanup(A)))).

Prolog flag sys_choices, is how to check determinism in my Prolog system.
SWI-Prolog can query something else I guess, also I use a different predicate name.

Here is the behaviour I am looking for…

Does try_call_finally/3 preform like the following?

:- dynamic(state/1).
state(W):- writeln(W=true).

main:- 
  WriteFalse = ( state(W):- writeln(W=false), ! ),
  setup_call_cleanup_each(
       asserta(WriteFalse,Ref),
       ( (X=1;X=2), state(X) ),
       erase(Ref)), 
  state(X),
  fail.

?- main.

1=false
1=true
2=false
2=true

I assumed an undo/1 (in the above example) would hold the clause ref and keep it around (past even an exception)

Though another example…
(this sort of has an implicit repeat/0-like thing above the Setup)

?- setup_call_cleanup_each(
      writeln('starting one goal iteration'),
      ( (X=1;X=2),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
false.

And perhaps the exception below

?- setup_call_cleanup_each(
       writeln('starting one goal iteration'),
        ( (X=1;X=2), writeln(X), 
           (X == 2 -> throw(abort) ; true)),
       writeln('ending one goal iteration')),
    fail.

starting one goal iteration
1
ending one goal iteration
starting one goal iteration
2
ending one goal iteration
ERROR: 13:23:58.456 Unhandled exception: abort

Aha, line 34 of logicmoo.pl · GitHub
allows you to connect any shared variables of the Setup A and Cleanup C !

Its pretty resilient. Also calls out during exceptions:

?- try_call_finally(writeln('in'), (X=1;throw(ball)), writeln('out')).
in
out
X = 1 ;
in
ERROR: Unhandled exception: ball
out

You can compare with setup_call_cleanup/3:

?- setup_call_cleanup(writeln('in'), (X=1;throw(ball)), writeln('out')).
in
X = 1 ;
ERROR: Unhandled exception: ball
out

That is pretty amazing thank you…

My version looked like this.

trusted_redo_call_cleanup(Setup,Goal,Cleanup):- 
   HdnCleanup = mquietly(Cleanup),   
   setup_call_cleanup(Setup, 
     ((Goal,deterministic(DET)),
        (notrace(DET == true) -> ! ; 
           ((HdnCleanup,notrace(nb_setarg(1,HdnCleanup,true)));
            (Setup,notrace(nb_setarg(1,HdnCleanup,Cleanup)),notrace(fail))))),
        HdnCleanup).

Still like your version as its readable

:- meta_predicate(try_call_finally(0,0,0)).
:- export(try_call_finally/3).
try_call_finally(A, G, C) :-
   call_cleanup_(A, C),
   deterministic_(G, F),
   (F == true, !; call_cleanup_(C, A)).

deterministic_(G, F) :-
   G,
   deterministic(F),
   otherwise. /* prevent tail recursion */

call_cleanup_(G, C) :-
   call_cleanup((G; fail), C). /* prevent early determinism */

Though both both our versions is plagued…

:- dynamic(try_c_f_state/1).
try_c_f_state(W):- writeln(W=true).

try_try_call_finally:-
  WriteFalse = ( try_c_f_state(W):- writeln(W=false), ! ),
  try_call_finally(
       asserta(WriteFalse,Ref),
       ( (X=1;X=2), try_c_f_state(X) ),
       erase(Ref)), 
  try_c_f_state(X),
  fail.      

Unfortounly by this…

?-   try_try_call_finally.
1=false
1=true
ERROR: 14:02:15.231 Uninstantiated argument expected, found <clause>(0x55c88b06ce60) (2-nd argument)
ERROR: 14:02:15.231 In:
ERROR: 14:02:15.231  [109] asserta((try_c_f_state(_309080):-writeln(_309094=false),!),<clause>(0x55c88b06ce60))
ERROR: 14:02:15.231  [108] <meta call>
ERROR: 14:02:15.231  [107] setup_call_catcher_cleanup(system:true,each_call_cleanup:(each_call_cleanup:erase(<clause>(0x55c88b06ce60));fail),fail,each_call_cleanup:asserta((try_c_f_state(_309200):-writeln(_309214=false),!),<clause>(0x55c88b06ce60))) <foreign>
ERROR: 14:02:15.231  [104] each_call_cleanup:try_call_finally(each_call_cleanup:asserta((try_c_f_state(_309272):-writeln(_309286=false),!),<clause>(0x55c88b06ce60)),each_call_cleanup:((1=1;1=2),try_c_f_state(1)),each_call_cleanup:erase(<clause>(0x55c88b06ce60))) at /opt/logicmoo_workspace/packs_sys/logicmoo_utils/prolog/logicmoo/each_call.pl:80
ERROR: 14:02:15.231  [103] each_call_cleanup:try_try_call_finally at /opt/logicmoo_workspace/packs_sys/logicmoo_utils/prolog/logicmoo/each_call.pl:99
ERROR: 14:02:15.231  [102] toplevel_call(each_call_cleanup:try_try_call_finally) at /opt/logicmoo_workspace/lib/swipl/boot/toplevel.pl:1115
ERROR: 14:02:15.231
ERROR: 14:02:15.231 Note: some frames are missing due to last-call optimization.
ERROR: 14:02:15.231 Re-run your program in debug mode (:- debug.) to get more detail.
^  Exception: (107) [system] setup_call_catcher_cleanup(system:true,each_call_cleanup:(each_call_cleanup:erase(<clause>(0x55c88b06ce60));fail),_20238,each_call_cleanup:asserta((try_c_f_state(_19614):-writeln(_19614=false),!),<clause>(0x55c88b06ce60))) ```


Seems we need a way to undo the variables before the next call

You cannot overwrite Ref, by a second asserta/2.

Ref is a logical variable.

Even if you use a second variable Ref2, it wont work.

Because backtracking will unbind it.

This is because we should have never persisted Ref between the calls. Likely that we need a fresh copy of our Setup/Cleanup goal for each call?

In my system I can nevertheless do it:

?- assumable_ref(SomeClause, Ref),
   try_call_finally(recordz_ref(Ref),
       ((X=1;X=2), SomeGoal)
       erase_ref(Ref)).

Create the clause reference outside of try_call_finally/3.

I have slightly different db-references, that can be

off database created and reused multiple times.

Good it works out for the database

Though it would be nice to write:

:-  try_call_finally(
       gensym(hi_,X),
       member(N,[1,2,3]),
       write(X=N)),
    fail.

hi_0 = 1
hi_1 = 2
hi_2 = 3
No.

You can use micro engines, Lean Prolog engine_next_reified/2.
But involves copy of the answer. Maybe not what you are looking for.
But I guess this is the only way to have two trails. A trail of the “call”,
and a trail of “finally” to “try”. Thats basically what you ask Prolog for,

to have two trails. I don’t think this matches the concept of Prolog.
You want a trail here in Byrds box model, while going from redo to
exit or fail. But redo involves backtracking through choice points,
which involves undoing the trail:

            +-------+
call ------>|       |------> exit
            |       |
fail <------|       |<------ redo
            +-------+

Yeah… I did try once with engines here with

https://groups.google.com/g/comp.lang.prolog/c/JToeE7Read8/m/K_NqpIhUDQAJ
(I suppose I needed to sync and the engine vars with the host vars)

You could also try something new, backtracking metainterpreter, see Power of Prolog by Markus Triska. One can not only build a solve/2 meta interpreter that profits from backtracking of clause/2, but also a meta interpreter that even does more explicitly the backtracking. But it might then only work for some goals, that are understood by your meta interpreter.

Well I did create a version that works here…

is is just slow :frowning:
As you can see why (I used the clausedb as a copy mechanism to create “sharing”, asserting a new pair each time)

Maybe it is needing two trails. But it’s odd we cant get a choice point positioned above the Setup
… something that goes like this pseudocode:

      Setup = gensym(hi_,X),
      Call =  member(N,[1,2,3]),
      Cleanup = writeln(X=N),
      try_call_finally(
               repeat,
               (Setup,Call),
               Cleanup),
     ...

That does not result in…

hi_1=1
hi_1=2
hi_1=3

The way this last example does

The easiest is to use nb_setarg/3, if you want a changing clause reference passed around.

in(Holder) :- assertz(SomeClause, Ref), nb_setarg(1, Holder, Ref).

out(Holder) :- arg(1, Holder, Ref), erase(Ref).

And then:

?-  Holder = v(_),
    try_call_finally(in(Holder),
       ...
       out(Holder)).

The holder is created outside of try_call_finally/3.

Edit 19.05.2021:
A more faithful translation of my sys_atomic/1 would be:

call_cleanup_(G, C) :-
   setup_call_cleanup(G, (true; fail), C).

This would make G non-interuptible.

Or use the nifty (~>)/2 from another thread here, it would also check that C succeeds.

Out of curiosity, what was the original ask and use-case?

I found the original Owicki & Lamport, 1982, paper, if you wish to see it.

Its use cases are a moving target, like the name of the predicate. Once you turn your back on, and whosh it has a different name. Recently, before this thread, it was called redo_call_cleanup/3:

Logicmoo_Utils
https://www.youtube.com/watch?v=2azII6zT5Tg

Edit 21.05.2021:
In my system I use try_call_finally/3 to realize with_output_to/2.

Hmmm. “If you don’t know where you’re going, then any road will take you there.” – after Lewis Carroll

The main difference between all of these other methods is that they are calling some predicate in the critical section between the setup and clean-up.

Operator ‘~>’ is in-line and cumulative. Due to its non-determinism, the clean-up trap persists even after the predicate has returned to it’s caller.

what_goes_up
~> must_come_down … eventually

Example:

:- use_module('Prolog/eventually_implies').
:- use_module(library(readutil)).

show_status(N) :-
	writeln(setup(N))
	  ~> writeln(cleanup(N)).

read_codes_from(File, Codes) :-
	open(File, read, In, [type(text)])
	  ~> close(In),

    show_status(1),

	repeat,

	read_line_to_codes(In, Tmp),

	(    Tmp == end_of_file ->
	       (!, fail); Codes = Tmp).

testit :-
	show_status(2),

	forall(read_codes_from('md5sum.hash', Codes),
	       format('~s~n', [Codes])), !.

testit/0 shows:

7 ?- testit.
setup(2)
setup(1)
e185fe402d6bb856bad55fd31c1845f8  201210_A00_MCB3.eep.hex
e4b703999217be3f337aacedc81a007d  201210_A00_MCB3.hex
cleanup(1)
cleanup(2)
true.

Setups are in Entropic Time (forward-time). Clean-ups are in Syntropic Time (backward-time).

Minimal coupling and maximal cohesion properties are preserved.

You can bootstrap this try_call_finally/3:

By means of (~>)/2:

try_call_finally(A, G, C) :-
   A ~> C,
   current_prolog_flag(sys_choices, X),
   G,
   current_prolog_flag(sys_choices, Y),
   (X == Y, !; C ~> A).

I am considering inclusion of (~>)/2 in my system.

But I guess its not from Owicki & Lamport, 1982. Because they have (page 468):

false ~> Q

Which isn’t the case for (~>)/2. I even added a must/1 in my try_call_finally/3.

Edit 21.05.2021:
You can check yourself false ~> writeln(hello):

?- setup_call_cleanup(false, (true; false), writeln(hello)).
false.

p. 463:

Caveat Emptor. Experiment with it and beware of its hazards. It’s easy to comprehend, write, hide, maintain, and it keeps me out of trouble. The biggest problem is ensuring that your program eventually ends deterministically. Or, if it never ends, as many of mine do not, then choice-points are well under control.