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
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…
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
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  asserta((try_c_f_state(_309080):-writeln(_309094=false),!),<clause>(0x55c88b06ce60)) ERROR: 14:02:15.231  <meta call> ERROR: 14:02:15.231  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  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  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  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
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
(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
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).
?- Holder = v(_), try_call_finally(in(Holder), ... out(Holder)).
The holder is created outside of try_call_finally/3.
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:
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.
~> must_come_down … eventually
:- 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])), !.
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.
You can check yourself
false ~> writeln(hello):
?- setup_call_cleanup(false, (true; false), writeln(hello)). false.
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.